TypeScript runtime type system for IO decoding/encoding
72
⚠️ 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.
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;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>;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));
}
});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>>;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>;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] }>>;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>;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>>;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>;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>;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));
}
});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));
}
});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);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 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-tsdocs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10