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

decoder.mddocs/

Modern Decoder API

Experimental modern decoder system with enhanced error reporting and functional composition patterns.

Capabilities

Decoder Interface

The core Decoder interface with functional programming patterns.

/**
 * Kleisli decoder interface for functional composition
 * @template I - Input type
 * @template A - Output type after successful decoding
 */
interface Decoder<I, A> {
  readonly decode: (i: I) => Either<DecodeError, A>;
}

/** Decoder error type using FreeSemigroup for error accumulation */
type DecodeError = FreeSemigroup<DE.DecodeError<string>>;

Error Handling

Enhanced error system with detailed decode error information.

/**
 * Create a decode error
 * @param actual - The actual value that failed
 * @param message - Error message
 */
function error(actual: unknown, message: string): DecodeError;

/**
 * Create a successful decoder result
 * @param value - The successful value
 */
function success<A>(value: A): Either<DecodeError, A>;

/**
 * Create a failed decoder result
 * @param error - The decode error
 */
function failure<A>(error: DecodeError): Either<DecodeError, A>;

Constructor Functions

Functions for creating decoders from various sources.

/**
 * Create decoder from a refinement function
 * @param refinement - Type refinement function
 * @param expected - Expected type description
 */
function fromRefinement<I, A>(
  refinement: Refinement<I, A>,
  expected: string
): Decoder<I, A>;

/**
 * Create decoder from a Guard
 * @param guard - Guard instance
 * @param expected - Expected type description
 */
function fromGuard<I, A>(guard: G.Guard<I, A>, expected: string): Decoder<I, A>;

/**
 * Create a literal value decoder
 * @param value - The literal value to match
 */
function literal<A extends Literal>(value: A): Decoder<unknown, A>;

type Literal = string | number | boolean | null;

Primitive Decoders

Built-in decoders for basic types.

/** String decoder */
const string: Decoder<unknown, string>;

/** Number decoder */
const number: Decoder<unknown, number>;

/** Boolean decoder */
const boolean: Decoder<unknown, boolean>;

/** Unknown array decoder */
const UnknownArray: Decoder<unknown, Array<unknown>>;

/** Unknown record decoder */
const UnknownRecord: Decoder<unknown, Record<string, unknown>>;

Usage Examples:

import * as D from "io-ts/Decoder";
import { pipe } from "fp-ts/function";
import { fold } from "fp-ts/Either";

// Basic decoding
const result = D.string.decode("hello");
pipe(
  result,
  fold(
    (error) => console.error("Decode failed:", D.draw(error)),
    (value) => console.log("Success:", value)
  )
);

// Literal decoding
const Status = D.literal('active');
const statusResult = Status.decode('active');
// result: Right('active')

Combinator Functions

Functions for composing complex decoders.

/**
 * Create struct decoder from property decoders with typed inputs
 * @param properties - Object mapping property names to decoders
 */
function fromStruct<P extends Record<string, Decoder<any, any>>>(
  properties: P
): Decoder<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>;

/**
 * Create struct decoder from property decoders (from unknown input)
 * @param properties - Object mapping property names to decoders
 */
function struct<A>(properties: { [K in keyof A]: Decoder<unknown, A[K]> }): Decoder<unknown, A>;

/**
 * Create partial struct decoder with typed inputs where all properties are optional
 * @param properties - Object mapping property names to decoders
 */
function fromPartial<P extends Record<string, Decoder<any, any>>>(
  properties: P
): Decoder<Partial<{ [K in keyof P]: InputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>>;

/**
 * Create partial struct decoder where all properties are optional
 * @param properties - Object mapping property names to decoders
 */
function partial<A>(properties: { [K in keyof A]: Decoder<unknown, A[K]> }): Decoder<unknown, Partial<A>>;

/**
 * Create array decoder with typed inputs
 * @param item - Decoder for array elements
 */
function fromArray<I, A>(item: Decoder<I, A>): Decoder<Array<I>, Array<A>>;

/**
 * Create array decoder with element decoder
 * @param item - Decoder for array elements
 */
function array<A>(item: Decoder<unknown, A>): Decoder<unknown, Array<A>>;

/**
 * Create record decoder with typed inputs
 * @param codomain - Decoder for record values
 */
function fromRecord<I, A>(codomain: Decoder<I, A>): Decoder<Record<string, I>, Record<string, A>>;

/**
 * Create record decoder with value decoder
 * @param codomain - Decoder for record values
 */
function record<A>(codomain: Decoder<unknown, A>): Decoder<unknown, Record<string, A>>;

/**
 * Create tuple decoder with typed inputs
 * @param components - Array of decoders for each tuple position
 */
function fromTuple<C extends ReadonlyArray<Decoder<any, any>>>(
  ...components: C
): Decoder<{ [K in keyof C]: InputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;

/**
 * Create tuple decoder with component decoders
 * @param components - Array of decoders for each tuple position
 */
function tuple<A extends ReadonlyArray<unknown>>(
  ...components: { [K in keyof A]: Decoder<unknown, A[K]> }
): Decoder<unknown, A>;

/**
 * Create union decoder that tries multiple decoders
 * @param members - Array of decoder alternatives
 */
function union<MS extends [Decoder<unknown, any>, ...Array<Decoder<unknown, any>>]>(
  ...members: MS
): Decoder<unknown, TypeOf<MS[number]>>;

/**
 * Create intersection decoder that applies all decoders
 * @param right - Second decoder to intersect
 */
function intersect<B>(right: Decoder<unknown, B>): <A>(left: Decoder<unknown, A>) => Decoder<unknown, A & B>;

/**
 * Create tagged union decoder from member decoders with typed inputs
 * @param tag - The discriminator property key
 */
function fromSum<T extends string>(
  tag: T
): <MS extends Record<string, Decoder<any, any>>>(
  members: MS
) => Decoder<InputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;

/**
 * Create tagged union decoder from member decoders
 * @param tag - The discriminator property key
 */
function sum<T extends string>(
  tag: T
): <A>(members: { [K in keyof A]: Decoder<unknown, A[K] & Record<T, K>> }) => Decoder<unknown, A[keyof A]>;

Deprecated Functions

The following functions are deprecated and should be avoided in new code:

/**
 * @deprecated Use fromStruct instead
 */
const fromType: typeof fromStruct;

/**
 * @deprecated Use struct instead
 */
const type: typeof struct;

Usage Examples:

import * as D from "io-ts/Decoder";

// Struct decoder
const User = D.struct({
  name: D.string,
  age: D.number,
  email: D.string
});

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

// Array decoder
const Numbers = D.array(D.number);
const numbersResult = Numbers.decode([1, 2, 3]);

// Union decoder
const StringOrNumber = D.union(D.string, D.number);
const unionResult1 = StringOrNumber.decode("hello");
const unionResult2 = StringOrNumber.decode(42);

// Tuple decoder
const Coordinate = D.tuple(D.number, D.number);
const coordResult = Coordinate.decode([10, 20]);

// Tagged union decoder
const Shape = D.sum('type')({
  circle: D.struct({
    type: D.literal('circle'),
    radius: D.number
  }),
  rectangle: D.struct({
    type: D.literal('rectangle'),
    width: D.number,
    height: D.number
  })
});

const shapeResult = Shape.decode({ type: 'circle', radius: 5 });

Advanced Combinators

More sophisticated decoder combinators for complex scenarios.

/**
 * Add custom error message to decoder failures
 * @param message - Custom error message
 */
function withMessage<I>(message: string): <A>(decoder: Decoder<I, A>) => Decoder<I, A>;

/**
 * Refine a decoder with additional predicate
 * @param predicate - Predicate function for refinement
 * @param expected - Expected type description
 */
function refine<A, B extends A>(
  predicate: Refinement<A, B>,
  expected: string
): (decoder: Decoder<unknown, A>) => Decoder<unknown, B>;

/**
 * Parse decoder result with custom function
 * @param parser - Function to transform decoded value (returns Either<DecodeError, B>)
 */
function parse<A, B>(
  parser: (a: A) => Either<DecodeError, B>
): <I>(from: Decoder<I, A>) => Decoder<I, B>;

/**
 * Make decoder nullable (accepts null values)
 * @param decoder - Base decoder to make nullable
 */
function nullable<A>(decoder: Decoder<unknown, A>): Decoder<unknown, A | null>;

/**
 * Create lazy decoder for recursive types
 * @param f - Function that returns the decoder
 */
function lazy<A>(f: () => Decoder<unknown, A>): Decoder<unknown, A>;

/**
 * Map left side with input for better error messages
 * @param f - Function to transform errors with input context
 */
function mapLeftWithInput<I>(
  f: (input: I, error: DecodeError) => DecodeError
): <A>(decoder: Decoder<I, A>) => Decoder<I, A>;

Usage Examples:

import * as D from "io-ts/Decoder";
import { pipe } from "fp-ts/function";

// Custom error messages
const EmailWithMessage = pipe(
  D.string,
  D.refine((s): s is string => /\S+@\S+\.\S+/.test(s), 'valid email'),
  D.withMessage('Please provide a valid email address')
);

// Parse transformation
const StringToNumber = pipe(
  D.string,
  D.parse((s) => {
    const n = parseFloat(s);
    return isNaN(n) ? left(`Cannot parse "${s}" as number`) : right(n);
  })
);

// Nullable decoder
const OptionalName = D.nullable(D.string);
const result1 = OptionalName.decode("Alice"); // Right("Alice")
const result2 = OptionalName.decode(null);    // Right(null)

// Recursive decoder
interface Category {
  name: string;
  subcategories: Category[];
}

const Category: D.Decoder<unknown, Category> = D.lazy(() =>
  D.struct({
    name: D.string,
    subcategories: D.array(Category)
  })
);

Functional Composition

Functional programming utilities for decoder composition.

/**
 * Map over successful decoder results
 * @param f - Transformation function
 */
function map<A, B>(f: (a: A) => B): (decoder: Decoder<unknown, A>) => Decoder<unknown, B>;

/**
 * Alternative decoder (try second if first fails)
 * @param that - Alternative decoder
 */
function alt<A>(that: () => Decoder<unknown, A>): (decoder: Decoder<unknown, A>) => Decoder<unknown, A>;

/**
 * Compose two decoders
 * @param to - Target decoder
 */
function compose<A, B>(to: Decoder<A, B>): (from: Decoder<unknown, A>) => Decoder<unknown, B>;

/**
 * Identity decoder
 */
function id<A>(): Decoder<A, A>;

Type Utilities and Instances

Type extraction and functional programming instances.

/** Extract input type from decoder */
type InputOf<D> = D extends Decoder<infer I, any> ? I : never;

/** Extract output type from decoder */
type TypeOf<D> = D extends Decoder<any, infer A> ? A : never;

/** Module URI for HKT support */
const URI = 'io-ts/Decoder';

/** Functor instance */
const Functor: Functor1<typeof URI>;

/** Alt instance */
const Alt: Alt1<typeof URI>;

/** Category instance */
const Category: Category1<typeof URI>;

Error Formatting

Utilities for formatting and displaying decode errors.

/**
 * Draw decode error as readable string
 * @param error - The decode error to format
 */
function draw(error: DecodeError): string;

/**
 * Format Either result as string
 * @param result - Either result to stringify
 */
function stringify<A>(result: Either<DecodeError, A>): string;

Usage Example:

import * as D from "io-ts/Decoder";

const User = D.struct({
  name: D.string,
  age: D.number
});

const result = User.decode({ name: 123, age: "thirty" });

if (result._tag === "Left") {
  console.log("Formatted error:");
  console.log(D.draw(result.left));
  
  console.log("Stringified result:");
  console.log(D.stringify(result));
}

Advanced Usage Patterns

Complex Validation Pipeline

import * as D from "io-ts/Decoder";
import { pipe } from "fp-ts/function";
import { fold } from "fp-ts/Either";

const UserData = pipe(
  D.struct({
    name: D.string,
    email: D.string,
    age: D.number
  }),
  D.refine(
    (user): user is typeof user => user.age >= 18,
    'adult user'
  ),
  D.withMessage('User must be an adult with valid contact info')
);

const processUser = (input: unknown) =>
  pipe(
    UserData.decode(input),
    fold(
      (error) => ({
        success: false,
        error: D.draw(error)
      }),
      (user) => ({
        success: true,
        user: {
          ...user,
          displayName: user.name.toUpperCase()
        }
      })
    )
  );

Tagged Union Decoding

import * as D from "io-ts/Decoder";

const Shape = D.union(
  D.struct({
    type: D.literal('circle'),
    radius: D.number
  }),
  D.struct({
    type: D.literal('rectangle'),
    width: D.number,
    height: D.number
  }),
  D.struct({
    type: D.literal('triangle'),
    base: D.number,
    height: D.number
  })
);

const shapes = [
  { type: 'circle', radius: 5 },
  { type: 'rectangle', width: 10, height: 20 },
  { type: 'triangle', base: 8, height: 12 }
];

shapes.forEach(shape => {
  const result = Shape.decode(shape);
  if (result._tag === 'Right') {
    console.log('Valid shape:', result.right);
  }
});

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