TypeScript runtime type system for IO decoding/encoding
72
⚠️ EXPERIMENTAL: These modules are experimental infrastructure for error handling and composition in the modern io-ts API.
Core infrastructure modules that provide error handling and functional composition utilities for the experimental decoder system.
Structured error system for representing validation failures with detailed context information.
Algebraic data type representing different kinds of decode errors with structured information.
/**
* Union type representing all possible decode error variants
*/
type DecodeError<E> = Leaf<E> | Key<E> | Index<E> | Member<E> | Lazy<E> | Wrap<E>;
/**
* Leaf error - basic validation failure
*/
interface Leaf<E> {
readonly _tag: 'Leaf';
readonly actual: unknown;
readonly error: E;
}
/**
* Key error - object property validation failure
*/
interface Key<E> {
readonly _tag: 'Key';
readonly key: string;
readonly kind: Kind;
readonly errors: FreeSemigroup<DecodeError<E>>;
}
/**
* Index error - array element validation failure
*/
interface Index<E> {
readonly _tag: 'Index';
readonly index: number;
readonly kind: Kind;
readonly errors: FreeSemigroup<DecodeError<E>>;
}
/**
* Member error - union member validation failure
*/
interface Member<E> {
readonly _tag: 'Member';
readonly index: number;
readonly errors: FreeSemigroup<DecodeError<E>>;
}
/**
* Lazy error - recursive decoder validation failure
*/
interface Lazy<E> {
readonly _tag: 'Lazy';
readonly id: string;
readonly errors: FreeSemigroup<DecodeError<E>>;
}
/**
* Wrap error - custom message wrapper for errors
*/
interface Wrap<E> {
readonly _tag: 'Wrap';
readonly error: E;
readonly errors: FreeSemigroup<DecodeError<E>>;
}
/**
* Kind of property/element validation
*/
type Kind = 'required' | 'optional';
/**
* Required property/element constant
*/
const required: 'required';
/**
* Optional property/element constant
*/
const optional: 'optional';Functions to create different types of decode errors.
/**
* Create a leaf error for basic validation failure
* @param actual - The actual value that failed validation
* @param error - The error message or object
*/
function leaf<E>(actual: unknown, error: E): DecodeError<E>;
/**
* Create a key error for object property validation failure
* @param key - The property key that failed
* @param kind - Whether the property is required or optional
* @param errors - Nested errors from property validation
*/
function key<E>(key: string, kind: Kind, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
/**
* Create an index error for array element validation failure
* @param index - The array index that failed
* @param kind - Whether the element is required or optional
* @param errors - Nested errors from element validation
*/
function index<E>(index: number, kind: Kind, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
/**
* Create a member error for union validation failure
* @param index - The union member index that failed
* @param errors - Nested errors from member validation
*/
function member<E>(index: number, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
/**
* Create a lazy error for recursive decoder failure
* @param id - Identifier for the recursive decoder
* @param errors - Nested errors from recursive validation
*/
function lazy<E>(id: string, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;
/**
* Create a wrap error to add custom message to existing errors
* @param error - Custom error message or object
* @param errors - The wrapped errors
*/
function wrap<E>(error: E, errors: FreeSemigroup<DecodeError<E>>): DecodeError<E>;Functions for pattern matching and processing decode errors.
/**
* Pattern match on DecodeError types
* @param patterns - Object with handlers for each error variant
*/
function fold<E, R>(patterns: {
Leaf: (input: unknown, error: E) => R;
Key: (key: string, kind: Kind, errors: FreeSemigroup<DecodeError<E>>) => R;
Index: (index: number, kind: Kind, errors: FreeSemigroup<DecodeError<E>>) => R;
Member: (index: number, errors: FreeSemigroup<DecodeError<E>>) => R;
Lazy: (id: string, errors: FreeSemigroup<DecodeError<E>>) => R;
Wrap: (error: E, errors: FreeSemigroup<DecodeError<E>>) => R;
}): (error: DecodeError<E>) => R;
/**
* Get Semigroup instance for DecodeError accumulation
*/
function getSemigroup<E = never>(): Semigroup<FreeSemigroup<DecodeError<E>>>;Usage Examples:
import * as DE from "io-ts/DecodeError";
import * as FS from "io-ts/FreeSemigroup";
// Create basic leaf error
const leafError = DE.leaf(42, "Expected string");
// Create nested object error
const keyError = DE.key(
"username",
DE.required,
FS.of(DE.leaf("", "Username cannot be empty"))
);
// Create array validation error
const indexError = DE.index(
2,
DE.required,
FS.of(DE.leaf("invalid", "Expected number"))
);
// Pattern match on error types
const formatError = DE.fold<string, string>({
Leaf: (actual, error) => `Invalid value ${JSON.stringify(actual)}: ${error}`,
Key: (key, kind, errors) => `Property '${key}' (${kind}): ${/* format nested errors */}`,
Index: (index, kind, errors) => `Element [${index}] (${kind}): ${/* format nested errors */}`,
Member: (index, errors) => `Union member ${index}: ${/* format nested errors */}`,
Lazy: (id, errors) => `Recursive type '${id}': ${/* format nested errors */}`,
Wrap: (error, errors) => `${error}: ${/* format nested errors */}`
});
console.log(formatError(leafError));
// Output: "Invalid value 42: Expected string"Free semigroup implementation for accumulating validation errors in a structured way.
Algebraic data type representing a free semigroup structure.
/**
* Free semigroup type - either a single value or concatenation of two free semigroups
*/
type FreeSemigroup<A> = Of<A> | Concat<A>;
/**
* Single value in free semigroup
*/
interface Of<A> {
readonly _tag: 'Of';
readonly value: A;
}
/**
* Concatenation of two free semigroups
*/
interface Concat<A> {
readonly _tag: 'Concat';
readonly left: FreeSemigroup<A>;
readonly right: FreeSemigroup<A>;
}Functions to create FreeSemigroup values.
/**
* Create FreeSemigroup from single value
* @param value - The value to wrap
*/
function of<A>(value: A): FreeSemigroup<A>;
/**
* Concatenate two FreeSemigroup values
* @param left - Left FreeSemigroup
* @param right - Right FreeSemigroup
*/
function concat<A>(left: FreeSemigroup<A>, right: FreeSemigroup<A>): FreeSemigroup<A>;Functions for processing FreeSemigroup structures.
/**
* Fold over FreeSemigroup structure
* @param onOf - Handler for single values
* @param onConcat - Handler for concatenations
*/
function fold<A, R>(
onOf: (value: A) => R,
onConcat: (left: FreeSemigroup<A>, right: FreeSemigroup<A>) => R
): (fs: FreeSemigroup<A>) => R;
/**
* Get Semigroup instance for FreeSemigroup
*/
function getSemigroup<A = never>(): Semigroup<FreeSemigroup<A>>;Usage Examples:
import * as FS from "io-ts/FreeSemigroup";
import { pipe } from "fp-ts/function";
// Create single value
const single = FS.of("First error");
// Create another value
const another = FS.of("Second error");
// Concatenate errors
const combined = FS.concat(single, another);
// Process the structure
const collectErrors = FS.fold<string, string[]>(
(value) => [value], // Single error becomes array with one element
(left, right) => [
...collectErrors(left),
...collectErrors(right)
] // Concatenation becomes merged arrays
);
console.log(collectErrors(combined));
// Output: ["First error", "Second error"]
// Use with Semigroup instance
const semigroup = FS.getSemigroup<string>();
const result = semigroup.concat(
FS.of("Error 1"),
semigroup.concat(
FS.of("Error 2"),
FS.of("Error 3")
)
);
console.log(collectErrors(result));
// Output: ["Error 1", "Error 2", "Error 3"]How DecodeError and FreeSemigroup work together in validation:
import * as DE from "io-ts/DecodeError";
import * as FS from "io-ts/FreeSemigroup";
import * as D from "io-ts/Decoder";
// Simulate validation of a user object
const validateUser = (input: unknown): string[] => {
// This would typically be done by a decoder, but shown here for illustration
if (typeof input !== 'object' || input === null) {
const error = DE.leaf(input, "Expected object");
return [formatDecodeError(error)];
}
const obj = input as Record<string, unknown>;
const errors: FS.FreeSemigroup<DE.DecodeError<string>>[] = [];
// Validate name property
if (typeof obj.name !== 'string') {
errors.push(FS.of(DE.key("name", DE.required, FS.of(DE.leaf(obj.name, "Expected string")))));
}
// Validate age property
if (typeof obj.age !== 'number') {
errors.push(FS.of(DE.key("age", DE.required, FS.of(DE.leaf(obj.age, "Expected number")))));
}
// Combine all errors
if (errors.length === 0) return [];
const combined = errors.reduce((acc, err) =>
acc ? FS.concat(acc, err) : err
);
return combined ? flattenErrors(combined) : [];
};
// Helper to format individual decode errors
const formatDecodeError = DE.fold<string, string>({
Leaf: (actual, error) => `${error} (got ${JSON.stringify(actual)})`,
Key: (key, kind, errors) => `${key}: ${flattenErrors(errors).join(', ')}`,
Index: (index, kind, errors) => `[${index}]: ${flattenErrors(errors).join(', ')}`,
Member: (index, errors) => `member ${index}: ${flattenErrors(errors).join(', ')}`,
Lazy: (id, errors) => `${id}: ${flattenErrors(errors).join(', ')}`,
Wrap: (error, errors) => `${error} (${flattenErrors(errors).join(', ')})`
});
// Helper to flatten FreeSemigroup of errors into array
const flattenErrors = (fs: FS.FreeSemigroup<DE.DecodeError<string>>): string[] => {
return FS.fold<DE.DecodeError<string>, string[]>(
(error) => [formatDecodeError(error)],
(left, right) => [...flattenErrors(left), ...flattenErrors(right)]
)(fs);
};
// Usage
console.log(validateUser({ name: 123, age: "thirty" }));
// Output: [
// "name: Expected string (got 123)",
// "age: Expected number (got \"thirty\")"
// ]For complex error processing, you can build tree structures and traverse them:
import * as DE from "io-ts/DecodeError";
import * as FS from "io-ts/FreeSemigroup";
interface ErrorTree {
message: string;
path: string[];
children: ErrorTree[];
}
const buildErrorTree = (
fs: FS.FreeSemigroup<DE.DecodeError<string>>,
path: string[] = []
): ErrorTree[] => {
return FS.fold<DE.DecodeError<string>, ErrorTree[]>(
(error) => [
DE.fold<string, ErrorTree>({
Leaf: (actual, msg) => ({
message: msg,
path,
children: []
}),
Key: (key, kind, errors) => ({
message: `Property '${key}' ${kind === DE.required ? '(required)' : '(optional)'}`,
path: [...path, key],
children: buildErrorTree(errors, [...path, key])
}),
Index: (index, kind, errors) => ({
message: `Element [${index}] ${kind === DE.required ? '(required)' : '(optional)'}`,
path: [...path, `[${index}]`],
children: buildErrorTree(errors, [...path, `[${index}]`])
}),
Member: (index, errors) => ({
message: `Union member ${index}`,
path: [...path, `<member-${index}>`],
children: buildErrorTree(errors, [...path, `<member-${index}>`])
}),
Lazy: (id, errors) => ({
message: `Recursive type '${id}'`,
path: [...path, `<${id}>`],
children: buildErrorTree(errors, [...path, `<${id}>`])
}),
Wrap: (msg, errors) => ({
message: msg,
path,
children: buildErrorTree(errors, path)
})
})(error)
],
(left, right) => [...buildErrorTree(left, path), ...buildErrorTree(right, path)]
)(fs);
};
// This enables rich error reporting with full context pathsInstall with Tessl CLI
npx tessl i tessl/npm-io-tsdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10