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

task-decoder.mddocs/

Task Decoder API (Experimental)

⚠️ EXPERIMENTAL: This module is experimental and in high state of flux. Features may change without notice.

Asynchronous decoder system based on TaskEither for validating and decoding data with async operations and enhanced error handling.

Capabilities

TaskDecoder Interface

Core interface for asynchronous decoders that return TaskEither for composable async validation.

/**
 * Asynchronous decoder interface built on TaskEither
 * @template I - Input type
 * @template A - Output type after successful decoding
 */
interface TaskDecoder<I, A> extends Kleisli<TaskEither.URI, I, DecodeError, A> {
  /** Decode function that returns TaskEither<DecodeError, A> */
  readonly decode: (i: I) => TaskEither<DecodeError, A>;
}

/**
 * Extract the decoded type from TaskDecoder
 */
type TypeOf<KTD> = KTD extends TaskDecoder<any, infer A> ? A : never;

/**
 * Extract the input type from TaskDecoder  
 */
type InputOf<KTD> = KTD extends TaskDecoder<infer I, any> ? I : never;

Error Handling

TaskDecoder uses the same DecodeError system as the regular Decoder but wrapped in TaskEither.

/**
 * DecodeError type (same as Decoder module)
 */
type DecodeError = FreeSemigroup<DecodeError<string>>;

/**
 * Create an error for async validation
 */
function error(actual: unknown, message: string): DecodeError;

/**
 * Create a successful async validation result
 */
function success<A>(a: A): TaskEither<DecodeError, A>;

/**
 * Create a failed async validation result
 */
function failure<A = never>(actual: unknown, message: string): TaskEither<DecodeError, A>;

Constructors

Build TaskDecoders from various sources.

/**
 * Convert a synchronous Decoder to TaskDecoder
 */
function fromDecoder<I, A>(decoder: Decoder<I, A>): TaskDecoder<I, A>;

/**
 * Create TaskDecoder from refinement function
 */
function fromRefinement<I, A extends I>(
  refinement: Refinement<I, A>, 
  expected: string
): TaskDecoder<I, A>;

/**
 * Create TaskDecoder from Guard
 */
function fromGuard<I, A extends I>(
  guard: Guard<I, A>, 
  expected: string
): TaskDecoder<I, A>;

/**
 * Create literal value TaskDecoder
 */
function literal<A extends readonly [L, ...ReadonlyArray<L>], L extends Literal = Literal>(
  ...values: A
): TaskDecoder<unknown, A[number]>;

Usage Examples:

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

// Convert sync decoder to async
const StringDecoder = TD.fromDecoder(D.string);

// Create async validation workflow
const validateAsync = (input: unknown) =>
  pipe(
    StringDecoder.decode(input),
    TE.chain((str) => 
      // Simulate async operation (e.g., database check)
      TE.fromTask(async () => {
        await new Promise(resolve => setTimeout(resolve, 100));
        return str.toUpperCase();
      })
    )
  );

// Usage
validateAsync("hello")().then(result => {
  if (result._tag === 'Right') {
    console.log('Result:', result.right); // "HELLO"
  } else {
    console.error('Error:', TD.draw(result.left));
  }
});

Primitive TaskDecoders

Async versions of basic type validators.

/**
 * String TaskDecoder
 */
const string: TaskDecoder<unknown, string>;

/**
 * Number TaskDecoder
 */
const number: TaskDecoder<unknown, number>;

/**
 * Boolean TaskDecoder
 */
const boolean: TaskDecoder<unknown, boolean>;

/**
 * Array TaskDecoder (unknown elements)
 */
const UnknownArray: TaskDecoder<unknown, Array<unknown>>;

/**
 * Record TaskDecoder (unknown values)
 */
const UnknownRecord: TaskDecoder<unknown, Record<string, unknown>>;

Combinators

Functions for composing complex async validations.

/**
 * Map over validation errors with access to input
 */
function mapLeftWithInput<I>(
  f: (input: I, e: DecodeError) => DecodeError
): <A>(decoder: TaskDecoder<I, A>) => TaskDecoder<I, A>;

/**
 * Add custom error message
 */
function withMessage<I>(
  message: (input: I, e: DecodeError) => string
): <A>(decoder: TaskDecoder<I, A>) => TaskDecoder<I, A>;

/**
 * Add refinement constraint
 */
function refine<A, B extends A>(
  refinement: Refinement<A, B>,
  id: string
): <I>(from: TaskDecoder<I, A>) => TaskDecoder<I, B>;

/**
 * Chain with async parser
 */
function parse<A, B>(
  parser: (a: A) => TaskEither<DecodeError, B>
): <I>(from: TaskDecoder<I, A>) => TaskDecoder<I, B>;

/**
 * Make TaskDecoder nullable
 */
function nullable<I, A>(or: TaskDecoder<I, A>): TaskDecoder<null | I, null | A>;

Object Validation

Async object and struct validation.

/**
 * Create TaskDecoder for struct from property TaskDecoders
 */
function fromStruct<P extends Record<string, TaskDecoder<any, any>>>(
  properties: P
): TaskDecoder<{ [K in keyof P]: InputOf<P[K]> }, { [K in keyof P]: TypeOf<P[K]> }>;

/**
 * Create TaskDecoder for struct from unknown input
 */
function struct<A>(
  properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
): TaskDecoder<unknown, { [K in keyof A]: A[K] }>;

/**
 * Create TaskDecoder for partial struct
 */
function fromPartial<P extends Record<string, TaskDecoder<any, any>>>(
  properties: P
): TaskDecoder<Partial<{ [K in keyof P]: InputOf<P[K]> }>, Partial<{ [K in keyof P]: TypeOf<P[K]> }>>;

/**
 * Create TaskDecoder for partial struct from unknown input
 */
function partial<A>(
  properties: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
): TaskDecoder<unknown, Partial<{ [K in keyof A]: A[K] }>>;

Collection Validation

Async array and record validation.

/**
 * Create TaskDecoder for arrays with typed inputs
 */
function fromArray<I, A>(item: TaskDecoder<I, A>): TaskDecoder<Array<I>, Array<A>>;

/**
 * Create TaskDecoder for arrays from unknown input
 */
function array<A>(item: TaskDecoder<unknown, A>): TaskDecoder<unknown, Array<A>>;

/**
 * Create TaskDecoder for records with typed inputs
 */
function fromRecord<I, A>(codomain: TaskDecoder<I, A>): TaskDecoder<Record<string, I>, Record<string, A>>;

/**
 * Create TaskDecoder for records from unknown input
 */
function record<A>(codomain: TaskDecoder<unknown, A>): TaskDecoder<unknown, Record<string, A>>;

/**
 * Create TaskDecoder for tuples with typed inputs
 */
function fromTuple<C extends ReadonlyArray<TaskDecoder<any, any>>>(
  components: C
): TaskDecoder<{ [K in keyof C]: InputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;

/**
 * Create TaskDecoder for tuples from unknown input
 */
function tuple<A extends ReadonlyArray<unknown>>(
  components: { [K in keyof A]: TaskDecoder<unknown, A[K]> }
): TaskDecoder<unknown, A>;

Advanced Combinators

Union, intersection, and recursive async validation.

/**
 * Create union TaskDecoder (tries each decoder in sequence)
 */
function union<MS extends readonly [TaskDecoder<any, any>, ...Array<TaskDecoder<any, any>>]>(
  members: MS
): TaskDecoder<InputOf<MS[number]>, TypeOf<MS[number]>>;

/**
 * Create intersection TaskDecoder
 */
function intersect<IB, B>(
  right: TaskDecoder<IB, B>
): <IA, A>(left: TaskDecoder<IA, A>) => TaskDecoder<IA & IB, A & B>;

/**
 * Create tagged union TaskDecoder
 */
function fromSum<T extends string>(
  tag: T
): <MS extends Record<string, TaskDecoder<any, any>>>(
  members: MS
) => TaskDecoder<InputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;

/**
 * Create tagged union TaskDecoder from unknown input
 */
function sum<T extends string>(
  tag: T
): <A>(members: { [K in keyof A]: TaskDecoder<unknown, A[K] & Record<T, K>> }) => TaskDecoder<unknown, A[keyof A]>;

/**
 * Create recursive TaskDecoder
 */
function lazy<I, A>(id: string, f: () => TaskDecoder<I, A>): TaskDecoder<I, A>;

/**
 * Make TaskDecoder readonly
 */
function readonly<I, A>(decoder: TaskDecoder<I, A>): TaskDecoder<I, Readonly<A>>;

Functional Operations

Higher-order functions for TaskDecoder composition.

/**
 * Map over successful TaskDecoder result
 */
function map<A, B>(f: (a: A) => B): <I>(fa: TaskDecoder<I, A>) => TaskDecoder<I, B>;

/**
 * Alternative TaskDecoder (fallback on failure)
 */
function alt<I, A>(that: () => TaskDecoder<I, A>): (me: TaskDecoder<I, A>) => TaskDecoder<I, A>;

/**
 * Compose TaskDecoders
 */
function compose<A, B>(to: TaskDecoder<A, B>): <I>(from: TaskDecoder<I, A>) => TaskDecoder<I, B>;

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

Error Utilities

Functions for handling and displaying async validation errors.

/**
 * Convert DecodeError to human-readable string
 */
function draw(e: DecodeError): string;

/**
 * Convert TaskEither result to human-readable string
 */
function stringify<A>(e: TaskEither<DecodeError, A>): Task<string>;

Usage Examples

Basic Async Validation

import * as TD from "io-ts/TaskDecoder";
import { pipe } from "fp-ts/function";
import * as TE from "fp-ts/TaskEither";

const User = TD.struct({
  id: TD.number,
  name: TD.string,
  email: TD.string
});

// Async validation with custom logic
const validateUser = (data: unknown) =>
  pipe(
    User.decode(data),
    TE.chain(user => 
      // Add async email validation
      user.email.includes('@') 
        ? TD.success(user)
        : TD.failure(user.email, 'Invalid email format')
    )
  );

// Usage
validateUser({ id: 1, name: "Alice", email: "alice@example.com" })()
  .then(result => {
    if (result._tag === 'Right') {
      console.log('Valid user:', result.right);
    } else {
      console.error('Validation error:', TD.draw(result.left));
    }
  });

Database Validation

import * as TD from "io-ts/TaskDecoder";
import { pipe } from "fp-ts/function";
import * as TE from "fp-ts/TaskEither";

// Simulate database check
const checkUserExists = (id: number): TE.TaskEither<string, boolean> =>
  TE.fromTask(async () => {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 100));
    return id > 0; // Simple validation logic
  });

const ExistingUser = pipe(
  TD.number,
  TD.parse(id => 
    pipe(
      checkUserExists(id),
      TE.mapLeft(error => TD.error(id, `User validation failed: ${error}`)),
      TE.chain(exists => 
        exists 
          ? TE.right(id)
          : TE.left(TD.error(id, 'User does not exist'))
      )
    )
  )
);

// Usage
ExistingUser.decode(123)().then(result => {
  if (result._tag === 'Right') {
    console.log('User ID valid:', result.right);
  } else {
    console.error('User validation failed:', TD.draw(result.left));
  }
});

Complex Async Workflow

import * as TD from "io-ts/TaskDecoder";
import { pipe } from "fp-ts/function";
import * as TE from "fp-ts/TaskEither";
import * as T from "fp-ts/Task";

// Multi-step async validation
const ComplexValidation = TD.struct({
  username: pipe(
    TD.string,
    TD.refine(s => s.length >= 3, 'Username too short'),
    TD.parse(username => 
      // Check username availability
      TE.fromTask(async () => {
        await new Promise(resolve => setTimeout(resolve, 50));
        if (username === 'admin') {
          throw TD.error(username, 'Username not available');
        }
        return username;
      })
    )
  ),
  email: pipe(
    TD.string,
    TD.refine(s => s.includes('@'), 'Invalid email'),
    TD.parse(email => 
      // Validate email format async
      TE.fromTask(async () => {
        await new Promise(resolve => setTimeout(resolve, 50));
        return email.toLowerCase();
      })
    )
  )
});

// Usage with error handling
const validateComplexData = (data: unknown) =>
  pipe(
    ComplexValidation.decode(data),
    TE.fold(
      error => T.of(`Validation failed: ${TD.draw(error)}`),
      success => T.of(`Validation succeeded: ${JSON.stringify(success)}`)
    )
  );

validateComplexData({ username: "alice", email: "ALICE@EXAMPLE.COM" })()
  .then(console.log);

Type Class Instances

TaskDecoder implements several functional programming type classes:

/**
 * Functor instance for mapping over successful results
 */
const Functor: Functor2<TaskDecoder.URI>;

/**
 * Alt instance for providing alternative decoders
 */
const Alt: Alt2<TaskDecoder.URI>;

/**
 * Category instance for composition
 */
const Category: Category2<TaskDecoder.URI>;

/**
 * Schemable instance for building schemas
 */
const Schemable: Schemable2C<TaskDecoder.URI, unknown>;

/**
 * WithUnknownContainers instance
 */
const WithUnknownContainers: WithUnknownContainers2C<TaskDecoder.URI, unknown>;

/**
 * WithUnion instance for union types
 */
const WithUnion: WithUnion2C<TaskDecoder.URI, unknown>;

/**
 * WithRefine instance for refinement types
 */
const WithRefine: WithRefine2C<TaskDecoder.URI, unknown>;

Deprecated Functions

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

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

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