CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-io-ts

TypeScript runtime type system for IO decoding/encoding

72

1.14x
Overview
Eval results
Files

schema.mddocs/

Schema & Type Classes

Advanced schema-based type construction and functional programming abstractions for maximum composability.

Capabilities

Schema Interface

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;

Schema Constructor

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" });

Schemable Type Classes

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>>;
}

Extended Schemable Interfaces

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>;
}

Utility Functions

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;

Advanced Usage Patterns

Multi-Implementation Schema

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); // false

Complex Nested Schema

import * 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);

Recursive Schema

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);

Schema with Refinements

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-ts

docs

codec.md

combinators.md

core-types.md

decoder.md

encoder.md

index.md

infrastructure.md

primitives.md

refinement.md

reporters.md

schema.md

task-decoder.md

validation.md

tile.json