TypeScript runtime type system for IO decoding/encoding
72
Advanced schema-based type construction and functional programming abstractions for maximum composability.
The core Schema interface for creating reusable, composable type definitions.
/**
* Schema interface for creating composable type definitions
* @template A - The runtime type
*/
interface Schema<A> {
<S>(S: Schemable<S>): HKT<S, A>;
}
/** Extract type from Schema */
type TypeOf<S> = S extends Schema<infer A> ? A : never;Function for creating memoized schemas.
/**
* Create a memoized schema
* @param schema - The schema function
*/
function make<A>(schema: Schema<A>): Schema<A>;
/**
* Interpret a schema with a specific Schemable instance
* @param S - The Schemable instance
*/
function interpreter<S>(S: Schemable<S>): <A>(schema: Schema<A>) => HKT<S, A>;Usage Example:
import * as S from "io-ts/Schema";
import * as D from "io-ts/Decoder";
import * as G from "io-ts/Guard";
// Create a reusable schema
const PersonSchema = S.make(S =>
S.struct({
name: S.string,
age: S.number,
email: S.string
})
);
// Interpret schema as different implementations
const PersonDecoder = S.interpreter(D.Schemable)(PersonSchema);
const PersonGuard = S.interpreter(G.Schemable)(PersonSchema);
// Use the generated decoder and guard
const result = PersonDecoder.decode({ name: "Alice", age: 30, email: "alice@example.com" });
const isValid = PersonGuard.is({ name: "Bob", age: 25, email: "bob@example.com" });Core type classes that define the capability interfaces for different implementations.
/** Literal value types */
type Literal = string | number | boolean | null;
/**
* Base Schemable interface for type-level programming
* @template S - The HKT parameter
*/
interface Schemable<S> {
readonly URI: S;
readonly literal: <A extends [Literal, ...Array<Literal>]>(...values: A) => HKT<S, A[number]>;
readonly string: HKT<S, string>;
readonly number: HKT<S, number>;
readonly boolean: HKT<S, boolean>;
readonly nullable: <A>(or: HKT<S, A>) => HKT<S, A | null>;
readonly struct: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>;
readonly partial: <A>(properties: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, Partial<A>>;
readonly record: <A>(codomain: HKT<S, A>) => HKT<S, Record<string, A>>;
readonly array: <A>(item: HKT<S, A>) => HKT<S, Array<A>>;
readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: HKT<S, A[K]> }) => HKT<S, A>;
readonly intersect: <B>(right: HKT<S, B>) => <A>(left: HKT<S, A>) => HKT<S, A & B>;
readonly lazy: <A>(f: () => HKT<S, A>) => HKT<S, A>;
readonly readonly: <A>(inner: HKT<S, A>) => HKT<S, Readonly<A>>;
}
/**
* Schemable interface for URIS (Higher-Kinded Types with single parameter)
*/
interface Schemable1<S extends URIS> {
readonly URI: S;
readonly literal: <A extends [Literal, ...Array<Literal>]>(...values: A) => Kind<S, A[number]>;
readonly string: Kind<S, string>;
readonly number: Kind<S, number>;
readonly boolean: Kind<S, boolean>;
readonly nullable: <A>(or: Kind<S, A>) => Kind<S, A | null>;
readonly struct: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>;
readonly partial: <A>(properties: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, Partial<A>>;
readonly record: <A>(codomain: Kind<S, A>) => Kind<S, Record<string, A>>;
readonly array: <A>(item: Kind<S, A>) => Kind<S, Array<A>>;
readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Kind<S, A[K]> }) => Kind<S, A>;
readonly intersect: <B>(right: Kind<S, B>) => <A>(left: Kind<S, A>) => Kind<S, A & B>;
readonly lazy: <A>(f: () => Kind<S, A>) => Kind<S, A>;
readonly readonly: <A>(inner: Kind<S, A>) => Kind<S, Readonly<A>>;
}
/**
* Schemable interface for URIS2 with error parameter (Higher-Kinded Types with two parameters)
*/
interface Schemable2C<S extends URIS2, E> {
readonly URI: S;
readonly _E: E;
readonly literal: <A extends [Literal, ...Array<Literal>]>(...values: A) => Kind2<S, E, A[number]>;
readonly string: Kind2<S, E, string>;
readonly number: Kind2<S, E, number>;
readonly boolean: Kind2<S, E, boolean>;
readonly nullable: <A>(or: Kind2<S, E, A>) => Kind2<S, E, A | null>;
readonly struct: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, A>;
readonly partial: <A>(properties: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, Partial<A>>;
readonly record: <A>(codomain: Kind2<S, E, A>) => Kind2<S, E, Record<string, A>>;
readonly array: <A>(item: Kind2<S, E, A>) => Kind2<S, E, Array<A>>;
readonly tuple: <A extends ReadonlyArray<unknown>>(...components: { [K in keyof A]: Kind2<S, E, A[K]> }) => Kind2<S, E, A>;
readonly intersect: <B>(right: Kind2<S, E, B>) => <A>(left: Kind2<S, E, A>) => Kind2<S, E, A & B>;
readonly lazy: <A>(f: () => Kind2<S, E, A>) => Kind2<S, E, A>;
readonly readonly: <A>(inner: Kind2<S, E, A>) => Kind2<S, E, Readonly<A>>;
}Additional interfaces for unknown containers, unions, and refinements.
/**
* Schemable interface with unknown container support
*/
interface WithUnknownContainers<S> {
readonly UnknownArray: HKT<S, Array<unknown>>;
readonly UnknownRecord: HKT<S, Record<string, unknown>>;
}
interface WithUnknownContainers1<S extends URIS> {
readonly UnknownArray: Kind<S, Array<unknown>>;
readonly UnknownRecord: Kind<S, Record<string, unknown>>;
}
interface WithUnknownContainers2C<S extends URIS2, E> {
readonly UnknownArray: Kind2<S, E, Array<unknown>>;
readonly UnknownRecord: Kind2<S, E, Record<string, unknown>>;
}
/**
* Schemable interface with union support
*/
interface WithUnion<S> {
readonly union: <MS extends [HKT<S, any>, HKT<S, any>, ...Array<HKT<S, any>>]>(...members: MS) => HKT<S, TypeOf<MS[number]>>;
}
interface WithUnion1<S extends URIS> {
readonly union: <MS extends [Kind<S, any>, Kind<S, any>, ...Array<Kind<S, any>>]>(...members: MS) => Kind<S, TypeOf<MS[number]>>;
}
interface WithUnion2C<S extends URIS2, E> {
readonly union: <MS extends [Kind2<S, E, any>, Kind2<S, E, any>, ...Array<Kind2<S, E, any>>]>(...members: MS) => Kind2<S, E, TypeOf<MS[number]>>;
}
/**
* Schemable interface with refinement support
*/
interface WithRefine<S> {
readonly refine: <A, B extends A>(refinement: Refinement<A, B>, id: string) => (from: HKT<S, A>) => HKT<S, B>;
}
interface WithRefine1<S extends URIS> {
readonly refine: <A, B extends A>(refinement: Refinement<A, B>, id: string) => (from: Kind<S, A>) => Kind<S, B>;
}
interface WithRefine2C<S extends URIS2, E> {
readonly refine: <A, B extends A>(refinement: Refinement<A, B>, id: string) => (from: Kind2<S, E, A>) => Kind2<S, E, B>;
}Helper functions for schema operations.
/**
* Memoization utility for expensive computations
* @param f - Function to memoize
*/
function memoize<A, B>(f: (a: A) => B): (a: A) => B;
/**
* Internal intersection utility
* @param a - First value
* @param b - Second value
*/
function intersect_<A, B>(a: A, b: B): A & B;import * as S from "io-ts/Schema";
import * as D from "io-ts/Decoder";
import * as G from "io-ts/Guard";
import * as Eq from "io-ts/Eq";
// Define a schema once
const UserSchema = S.make(S =>
S.struct({
id: S.number,
name: S.string,
email: S.string,
age: S.number,
isActive: S.boolean
})
);
// Generate multiple implementations
const UserDecoder = S.interpreter(D.Schemable)(UserSchema);
const UserGuard = S.interpreter(G.Schemable)(UserSchema);
const UserEq = S.interpreter(Eq.Schemable)(UserSchema);
// Use each implementation for its purpose
const userData = { id: 1, name: "Alice", email: "alice@example.com", age: 30, isActive: true };
// Decode untrusted data
const decodeResult = UserDecoder.decode(userData);
// Type guard for runtime checks
if (UserGuard.is(userData)) {
console.log("Valid user data");
}
// Compare users for equality
const user1 = userData;
const user2 = { ...userData, name: "Bob" };
const areEqual = UserEq.equals(user1, user2); // falseimport * as S from "io-ts/Schema";
const AddressSchema = S.make(S =>
S.struct({
street: S.string,
city: S.string,
country: S.string,
postalCode: S.string
})
);
const CompanySchema = S.make(S =>
S.struct({
name: S.string,
industry: S.string,
employees: S.number,
address: S.interpreter(S)(AddressSchema)
})
);
const PersonSchema = S.make(S =>
S.struct({
id: S.number,
profile: S.struct({
firstName: S.string,
lastName: S.string,
email: S.string
}),
addresses: S.array(S.interpreter(S)(AddressSchema)),
company: S.nullable(S.interpreter(S)(CompanySchema)),
preferences: S.partial({
theme: S.union(S.literal('light'), S.literal('dark')),
notifications: S.boolean,
language: S.string
})
})
);
// Generate decoder for the complex nested structure
const PersonDecoder = S.interpreter(D.Schemable)(PersonSchema);import * as S from "io-ts/Schema";
interface TreeNode {
value: number;
children: TreeNode[];
}
const TreeSchema: S.Schema<TreeNode> = S.make(S =>
S.lazy(() =>
S.struct({
value: S.number,
children: S.array(S.interpreter(S)(TreeSchema))
})
)
);
const TreeDecoder = S.interpreter(D.Schemable)(TreeSchema);
const treeData = {
value: 1,
children: [
{ value: 2, children: [] },
{ value: 3, children: [{ value: 4, children: [] }] }
]
};
const result = TreeDecoder.decode(treeData);import * as S from "io-ts/Schema";
import * as D from "io-ts/Decoder";
const EmailSchema = S.make(S =>
S.refine(
(s: string): s is string => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s),
'Email'
)(S.string)
);
const PositiveNumberSchema = S.make(S =>
S.refine(
(n: number): n is number => n > 0,
'PositiveNumber'
)(S.number)
);
const ValidatedUserSchema = S.make(S =>
S.struct({
name: S.refine(
(s: string): s is string => s.length > 0,
'NonEmptyString'
)(S.string),
email: S.interpreter(S)(EmailSchema),
age: S.interpreter(S)(PositiveNumberSchema)
})
);
const ValidatedUserDecoder = S.interpreter(D.Schemable)(ValidatedUserSchema);Install 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