TypeScript runtime type system for IO decoding/encoding
72
Experimental unified codec system combining decoding and encoding operations with enhanced composability.
The core Codec interface that combines Decoder and Encoder functionality.
/**
* Codec interface combining decoding and encoding operations
* @template I - Input type for decoding
* @template O - Output type for encoding
* @template A - The runtime type
*/
interface Codec<I, O, A> extends Decoder<I, A>, Encoder<O, A> {}Functions for creating codecs from decoders and encoders.
/**
* Create a codec from separate decoder and encoder
* @param decoder - The decoder for input validation
* @param encoder - The encoder for output transformation
*/
function make<I, O, A>(decoder: Decoder<I, A>, encoder: Encoder<O, A>): Codec<I, O, A>;
/**
* Create a codec from decoder with identity encoder
* @param decoder - The decoder to wrap
*/
function fromDecoder<I, A>(decoder: Decoder<I, A>): Codec<I, A, A>;
/**
* Create a literal value codec
* @param value - The literal value to encode/decode
*/
function literal<A extends Literal>(value: A): Codec<unknown, A, A>;
type Literal = string | number | boolean | null;Usage Examples:
import * as C from "io-ts/Codec";
import * as D from "io-ts/Decoder";
import * as E from "io-ts/Encoder";
// Create codec from decoder and encoder
const TimestampCodec = C.make(
D.number, // Decode from number
E.id<Date>().contramap((date: Date) => date.getTime()) // Encode Date to number
);
// Create codec from decoder only (identity encoding)
const StringCodec = C.fromDecoder(D.string);
// Literal codec
const StatusCodec = C.literal('active');Built-in codecs for basic types.
/** String codec */
const string: Codec<unknown, string, string>;
/** Number codec */
const number: Codec<unknown, number, number>;
/** Boolean codec */
const boolean: Codec<unknown, boolean, boolean>;
/** Unknown array codec */
const UnknownArray: Codec<unknown, Array<unknown>, Array<unknown>>;
/** Unknown record codec */
const UnknownRecord: Codec<unknown, Record<string, unknown>, Record<string, unknown>>;Functions for composing complex codecs.
/**
* Create struct codec from property codecs
* @param properties - Object mapping property names to codecs
*/
function struct<P>(properties: { [K in keyof P]: Codec<unknown, P[K], P[K]> }): Codec<unknown, P, P>;
/**
* Create partial struct codec where all properties are optional
* @param properties - Object mapping property names to codecs
*/
function partial<P>(properties: { [K in keyof P]: Codec<unknown, P[K], P[K]> }): Codec<unknown, Partial<P>, Partial<P>>;
/**
* Create array codec with element codec
* @param item - Codec for array elements
*/
function array<O, A>(item: Codec<unknown, O, A>): Codec<unknown, Array<O>, Array<A>>;
/**
* Create record codec with value codec
* @param codomain - Codec for record values
*/
function record<O, A>(codomain: Codec<unknown, O, A>): Codec<unknown, Record<string, O>, Record<string, A>>;
/**
* Create tuple codec with component codecs
* @param components - Array of codecs for each tuple position
*/
function tuple<C extends ReadonlyArray<Codec<unknown, any, any>>>(
...components: C
): Codec<unknown, { [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;
/**
* Create sum codec for discriminated unions
* @param tag - Discriminant property name
*/
function sum<T extends string>(tag: T): <MS extends Record<string, Codec<unknown, any, any>>>(
members: MS
) => Codec<unknown, OutputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;Usage Examples:
import * as C from "io-ts/Codec";
// Struct codec
const User = C.struct({
name: C.string,
age: C.number,
email: C.string
});
// Array codec
const Numbers = C.array(C.number);
// Record codec
const Scores = C.record(C.number);
// Tuple codec
const Point = C.tuple(C.number, C.number);
// Sum codec for discriminated unions
const Shape = C.sum('type')({
circle: C.struct({
type: C.literal('circle'),
radius: C.number
}),
rectangle: C.struct({
type: C.literal('rectangle'),
width: C.number,
height: C.number
})
});More sophisticated codec combinators.
/**
* Refine a codec 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
): <I, O>(codec: Codec<I, O, A>) => Codec<I, O, B>;
/**
* Make codec nullable (accepts null values)
* @param codec - Base codec to make nullable
*/
function nullable<I, O, A>(codec: Codec<I, O, A>): Codec<I, O | null, A | null>;
/**
* Create lazy codec for recursive types
* @param f - Function that returns the codec
*/
function lazy<I, O, A>(f: () => Codec<I, O, A>): Codec<I, O, A>;
/**
* Make codec readonly
* @param codec - Base codec to make readonly
*/
function readonly<I, O, A>(codec: Codec<I, O, A>): Codec<I, Readonly<O>, Readonly<A>>;
/**
* Compose two codecs
* @param to - Target codec
*/
function compose<L, A, P, B>(
from: Codec<L, A, A>,
to: Codec<A, P, B>
): Codec<L, P, B>;
/**
* Intersect two codecs
* @param right - Second codec to intersect
*/
function intersect<IB, OB, B>(
right: Codec<IB, OB, B>
): <IA, OA, A>(left: Codec<IA, OA, A>) => Codec<IA & IB, OA & OB, A & B>;
/**
* Map left side with input for better error handling
* @param f - Function to transform errors with input
*/
function mapLeftWithInput<I>(
f: (input: I, error: DecodeError) => DecodeError
): <O, A>(codec: Codec<I, O, A>) => Codec<I, O, A>;Usage Examples:
import * as C from "io-ts/Codec";
import { pipe } from "fp-ts/function";
// Refined codec
const PositiveNumber = pipe(
C.number,
C.refine((n): n is number => n > 0, 'positive number')
);
// Nullable codec
const OptionalString = C.nullable(C.string);
// Recursive codec
interface Category {
name: string;
subcategories: Category[];
}
const Category: C.Codec<unknown, Category, Category> = C.lazy(() =>
C.struct({
name: C.string,
subcategories: C.array(Category)
})
);
// Composition
const StringToNumber = C.compose(
C.string,
C.make(
pipe(D.string, D.parse(s => {
const n = parseFloat(s);
return isNaN(n) ? left(`Invalid number: ${s}`) : right(n);
})),
E.id<number>().contramap(String)
)
);Type extraction utilities for codecs.
/** Extract input type from codec */
type InputOf<C> = C extends Codec<infer I, any, any> ? I : never;
/** Extract output type from codec */
type OutputOf<C> = C extends Codec<any, infer O, any> ? O : never;
/** Extract runtime type from codec */
type TypeOf<C> = C extends Codec<any, any, infer A> ? A : never;Instances and utilities for functional programming.
/** Module URI for HKT support */
const URI = 'io-ts/Codec';
/** Invariant functor instance */
const Invariant: Invariant1<typeof URI>;
/**
* Invariant map over codec
* @param f - Function from A to B
* @param g - Function from B to A
*/
function imap<I, O, A, B>(
f: (a: A) => B,
g: (b: B) => A
): (codec: Codec<I, O, A>) => Codec<I, O, B>;import * as C from "io-ts/Codec";
import { pipe } from "fp-ts/function";
import { fold } from "fp-ts/Either";
// Create a codec that can round-trip between JSON and typed data
const UserCodec = C.struct({
id: C.number,
name: C.string,
createdAt: C.make(
pipe(
D.string,
D.parse(s => {
const date = new Date(s);
return isNaN(date.getTime()) ? left('Invalid date') : right(date);
})
),
E.id<Date>().contramap(d => d.toISOString())
)
});
// Decode from JSON
const jsonData = {
id: 123,
name: "Alice",
createdAt: "2023-01-01T00:00:00.000Z"
};
const decodeResult = UserCodec.decode(jsonData);
if (decodeResult._tag === 'Right') {
const user = decodeResult.right;
console.log('Decoded user:', user);
// Encode back to JSON-serializable format
const encoded = UserCodec.encode(user);
console.log('Encoded back:', encoded);
// Output: { id: 123, name: "Alice", createdAt: "2023-01-01T00:00:00.000Z" }
}import * as C from "io-ts/Codec";
// Request codec
const CreateUserRequest = C.struct({
name: C.string,
email: C.string,
age: C.number
});
// Response codec with additional server fields
const UserResponse = C.struct({
id: C.number,
name: C.string,
email: C.string,
age: C.number,
createdAt: C.string,
updatedAt: C.string
});
// API client function
async function createUser(request: C.TypeOf<typeof CreateUserRequest>) {
const encodedRequest = CreateUserRequest.encode(request);
const response = await fetch('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(encodedRequest)
});
const responseData = await response.json();
const decodedResponse = UserResponse.decode(responseData);
if (decodedResponse._tag === 'Left') {
throw new Error('Invalid response format');
}
return decodedResponse.right;
}import * as C from "io-ts/Codec";
// Environment-specific codec
const DatabaseConfig = C.struct({
host: C.string,
port: C.number,
database: C.string,
ssl: C.boolean
});
const AppConfig = C.struct({
environment: C.union(
C.literal('development'),
C.literal('staging'),
C.literal('production')
),
database: DatabaseConfig,
features: C.partial({
enableAnalytics: C.boolean,
enableCache: C.boolean,
maxConnections: C.number
})
});
// Load and validate configuration
function loadConfig(): C.TypeOf<typeof AppConfig> {
const rawConfig = {
environment: process.env.NODE_ENV || 'development',
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'myapp',
ssl: process.env.DB_SSL === 'true'
},
features: {
enableAnalytics: process.env.ENABLE_ANALYTICS === 'true',
enableCache: process.env.ENABLE_CACHE !== 'false'
}
};
const result = AppConfig.decode(rawConfig);
if (result._tag === 'Left') {
throw new Error(`Invalid configuration: ${D.draw(result.left)}`);
}
return result.right;
}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