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

combinators.mddocs/

Combinators

Functions for composing complex types from simpler ones, enabling validation of objects, arrays, unions, intersections, and more.

Capabilities

Object Type Combinator

Creates a codec for objects with required properties.

/**
 * Create an interface codec from properties
 * @param props - Object mapping property names to their codecs
 * @param name - Optional name for the codec
 */
function type<P extends Props>(props: P, name?: string): TypeC<P>;

interface TypeC<P extends Props> extends InterfaceType<P, 
  { [K in keyof P]: TypeOf<P[K]> }, 
  { [K in keyof P]: OutputOf<P[K]> }, 
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const User = t.type({
  name: t.string,
  age: t.number,
  email: t.string
});

type User = t.TypeOf<typeof User>;
// User = { name: string; age: number; email: string }

const result = User.decode({
  name: "Alice",
  age: 30,
  email: "alice@example.com"
});
// result: Right({ name: "Alice", age: 30, email: "alice@example.com" })

Partial Type Combinator

Creates a codec for objects with optional properties.

/**
 * Create a partial interface codec where all properties are optional
 * @param props - Object mapping property names to their codecs
 * @param name - Optional name for the codec
 */
function partial<P extends Props>(props: P, name?: string): PartialC<P>;

interface PartialC<P extends Props> extends PartialType<P,
  { [K in keyof P]?: TypeOf<P[K]> },
  { [K in keyof P]?: OutputOf<P[K]> },
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const PartialUser = t.partial({
  name: t.string,
  age: t.number,
  email: t.string
});

type PartialUser = t.TypeOf<typeof PartialUser>;
// PartialUser = { name?: string; age?: number; email?: string }

const result = PartialUser.decode({ name: "Alice" });
// result: Right({ name: "Alice" })

Array Combinator

Creates a codec for arrays with elements of a specific type.

/**
 * Create an array codec with elements of type C
 * @param item - Codec for array elements
 * @param name - Optional name for the codec
 */
function array<C extends Mixed>(item: C, name?: string): ArrayC<C>;

interface ArrayC<C extends Mixed> extends ArrayType<C, 
  Array<TypeOf<C>>, 
  Array<OutputOf<C>>, 
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const NumberArray = t.array(t.number);
const StringArray = t.array(t.string);

const numbers = NumberArray.decode([1, 2, 3]);
// result: Right([1, 2, 3])

const mixed = NumberArray.decode([1, "2", 3]);
// result: Left([validation error for index 1])

Union Combinator

Creates a codec that validates against one of several possible types.

/**
 * Create a union codec that validates against one of the provided codecs
 * @param codecs - Array of at least 2 codecs to try
 * @param name - Optional name for the codec
 */
function union<CS extends [Mixed, Mixed, ...Array<Mixed>]>(
  codecs: CS, 
  name?: string
): UnionC<CS>;

interface UnionC<CS extends [Mixed, Mixed, ...Array<Mixed>]> extends UnionType<CS,
  TypeOf<CS[number]>,
  OutputOf<CS[number]>,
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const StringOrNumber = t.union([t.string, t.number]);
const Status = t.union([
  t.literal('pending'),
  t.literal('completed'),
  t.literal('failed')
]);

const result1 = StringOrNumber.decode("hello");
// result: Right("hello")

const result2 = StringOrNumber.decode(42);
// result: Right(42)

const status = Status.decode('pending');
// result: Right('pending')

Intersection Combinator

Creates a codec that validates against all of the provided types.

/**
 * Create an intersection codec that validates against all provided codecs
 * @param codecs - Array of at least 2 codecs to intersect
 * @param name - Optional name for the codec
 */
function intersection<CS extends [Mixed, Mixed, ...Array<Mixed>]>(
  codecs: CS,
  name?: string
): IntersectionC<CS>;

interface IntersectionC<CS extends [Mixed, Mixed, ...Array<Mixed>]> extends IntersectionType<CS,
  // Complex conditional type for intersection result
  unknown,
  unknown,
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const Named = t.type({ name: t.string });
const Aged = t.type({ age: t.number });
const Person = t.intersection([Named, Aged]);

type Person = t.TypeOf<typeof Person>;
// Person = { name: string } & { age: number }

const result = Person.decode({ name: "Alice", age: 30 });
// result: Right({ name: "Alice", age: 30 })

Tuple Combinator

Creates a codec for fixed-length arrays with specific types at each position.

/**
 * Create a tuple codec with specific types at each position
 * @param codecs - Array of codecs for each tuple position
 * @param name - Optional name for the codec
 */
function tuple<CS extends [Mixed, ...Array<Mixed>]>(
  codecs: CS,
  name?: string
): TupleC<CS>;

interface TupleC<CS extends [Mixed, ...Array<Mixed>]> extends TupleType<CS,
  // Complex conditional type for tuple result
  unknown,
  unknown,
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const Coordinate = t.tuple([t.number, t.number]);
const NamedPoint = t.tuple([t.string, t.number, t.number]);

const point = Coordinate.decode([10, 20]);
// result: Right([10, 20])

const namedPoint = NamedPoint.decode(["origin", 0, 0]);
// result: Right(["origin", 0, 0])

Record Combinator

Creates a codec for objects with dynamic keys and uniform value types.

/**
 * Create a record codec with domain keys and codomain values
 * @param domain - Codec for the keys
 * @param codomain - Codec for the values
 * @param name - Optional name for the codec
 */
function record<D extends Mixed, C extends Mixed>(
  domain: D, 
  codomain: C, 
  name?: string
): RecordC<D, C>;

interface RecordC<D extends Mixed, C extends Mixed> extends DictionaryType<D, C,
  { [K in TypeOf<D>]: TypeOf<C> },
  { [K in OutputOf<D>]: OutputOf<C> },
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const StringRecord = t.record(t.string, t.number);
const Scores = t.record(t.keyof({ alice: null, bob: null, charlie: null }), t.number);

const scores = StringRecord.decode({ math: 95, science: 87 });
// result: Right({ math: 95, science: 87 })

const playerScores = Scores.decode({ alice: 100, bob: 85 });
// result: Right({ alice: 100, bob: 85 })

Literal Combinator

Creates a codec for exact literal values.

/**
 * Create a literal value codec
 * @param value - The exact value to match
 * @param name - Optional name for the codec
 */
function literal<V extends LiteralValue>(value: V, name?: string): LiteralC<V>;

interface LiteralC<V extends LiteralValue> extends LiteralType<V> {}

type LiteralValue = string | number | boolean;

Usage Example:

import * as t from "io-ts";

const StatusPending = t.literal('pending');
const Version = t.literal(1);
const IsActive = t.literal(true);

const result = StatusPending.decode('pending');
// result: Right('pending')

const invalid = StatusPending.decode('completed');
// result: Left([validation error])

KeyOf Combinator

Creates a codec for keys of an object type.

/**
 * Create a keyof codec from object keys
 * @param keys - Object whose keys define the valid values
 * @param name - Optional name for the codec
 */
function keyof<D extends { [key: string]: unknown }>(
  keys: D,
  name?: string
): KeyofC<D>;

interface KeyofC<D extends { [key: string]: unknown }> extends KeyofType<D> {}

Usage Example:

import * as t from "io-ts";

const Color = t.keyof({ red: null, green: null, blue: null });

type Color = t.TypeOf<typeof Color>;
// Color = "red" | "green" | "blue"

const result = Color.decode('red');
// result: Right('red')

const invalid = Color.decode('yellow');
// result: Left([validation error])

Readonly Combinators

Make codecs readonly to prevent mutations.

/**
 * Make a codec readonly
 * @param codec - The codec to make readonly
 * @param name - Optional name for the codec
 */
function readonly<C extends Mixed>(codec: C, name?: string): ReadonlyC<C>;

interface ReadonlyC<C extends Mixed> extends ReadonlyType<C,
  Readonly<TypeOf<C>>,
  Readonly<OutputOf<C>>,
  unknown
> {}

/**
 * Create a readonly array codec
 * @param item - Codec for array elements
 * @param name - Optional name for the codec
 */
function readonlyArray<C extends Mixed>(item: C, name?: string): ReadonlyArrayC<C>;

interface ReadonlyArrayC<C extends Mixed> extends ReadonlyArrayType<C,
  ReadonlyArray<TypeOf<C>>,
  ReadonlyArray<OutputOf<C>>,
  unknown
> {}

Usage Example:

import * as t from "io-ts";

const ReadonlyUser = t.readonly(t.type({
  name: t.string,
  age: t.number
}));

const ReadonlyNumbers = t.readonlyArray(t.number);

type ReadonlyUser = t.TypeOf<typeof ReadonlyUser>;
// ReadonlyUser = Readonly<{ name: string; age: number }>

Exact and Strict Combinators

Strip additional properties from objects.

/**
 * Strip additional properties from a codec
 * @param codec - The codec to make exact
 * @param name - Optional name for the codec
 */
function exact<C extends HasProps>(codec: C, name?: string): ExactC<C>;

interface ExactC<C extends HasProps> extends ExactType<C, TypeOf<C>, OutputOf<C>, InputOf<C>> {}

/**
 * Create a strict interface codec (strips extra properties)
 * @param props - Object mapping property names to their codecs
 * @param name - Optional name for the codec
 */
function strict<P extends Props>(props: P, name?: string): ExactC<TypeC<P>>;

Usage Example:

import * as t from "io-ts";

const User = t.type({
  name: t.string,
  age: t.number
});

const StrictUser = t.strict({
  name: t.string,
  age: t.number
});

const data = { name: "Alice", age: 30, extra: "ignored" };

const lenient = User.decode(data);
// result: Right({ name: "Alice", age: 30, extra: "ignored" })

const strict = StrictUser.decode(data);
// result: Right({ name: "Alice", age: 30 }) - extra property stripped

Recursive Combinator

Create recursive type definitions for self-referential data structures.

/**
 * Create a recursive codec for self-referential types
 * @param name - Name for the recursive type
 * @param definition - Function that defines the recursive structure
 */
function recursion<A, O = A, I = unknown, C extends Type<A, O, I> = Type<A, O, I>>(
  name: string,
  definition: (self: C) => C
): RecursiveType<C, A, O, I>;

Usage Example:

import * as t from "io-ts";

interface Category {
  name: string;
  subcategories: Category[];
}

const Category: t.Type<Category> = t.recursion('Category', Self =>
  t.type({
    name: t.string,
    subcategories: t.array(Self)
  })
);

const data = {
  name: "Electronics",
  subcategories: [
    {
      name: "Computers", 
      subcategories: []
    },
    {
      name: "Phones",
      subcategories: []
    }
  ]
};

const result = Category.decode(data);
// result: Right(nested category structure)

Advanced Usage Patterns

Complex Object Validation

import * as t from "io-ts";

const User = t.type({
  id: t.number,
  profile: t.type({
    name: t.string,
    email: t.string,
    preferences: t.partial({
      theme: t.union([t.literal('light'), t.literal('dark')]),
      notifications: t.boolean
    })
  }),
  roles: t.array(t.string),
  metadata: t.record(t.string, t.unknown)
});

type User = t.TypeOf<typeof User>;

Discriminated Unions

import * as t from "io-ts";

const Shape = t.union([
  t.type({
    kind: t.literal('circle'),
    radius: t.number
  }),
  t.type({
    kind: t.literal('rectangle'), 
    width: t.number,
    height: t.number
  }),
  t.type({
    kind: t.literal('triangle'),
    base: t.number,
    height: t.number
  })
]);

type Shape = t.TypeOf<typeof Shape>;
// Shape = { kind: 'circle'; radius: number } 
//       | { kind: 'rectangle'; width: number; height: number }
//       | { kind: 'triangle'; base: number; height: number }

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