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

codec.mddocs/

Codec System

Experimental unified codec system combining decoding and encoding operations with enhanced composability.

Capabilities

Codec Interface

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> {}

Constructor Functions

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');

Primitive Codecs

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>>;

Combinator Functions

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
  })
});

Advanced Combinators

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 Utilities

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;

Functional Programming Support

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>;

Advanced Usage Patterns

Round-trip Encoding/Decoding

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" }
}

API Request/Response Codecs

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;
}

Configuration Management

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-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