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

encoder.mddocs/

Encoding System

Experimental encoding system for transforming data between different representations.

Capabilities

Encoder Interface

The core Encoder interface for data transformation.

/**
 * Encoder interface for transforming data to different representations
 * @template O - Output type after encoding
 * @template A - Input type to encode
 */
interface Encoder<O, A> {
  readonly encode: (a: A) => O;
}

Combinator Functions

Functions for creating and composing encoders.

/**
 * Create struct encoder from property encoders
 * @param encoders - Object mapping property names to encoders
 */
function struct<P>(encoders: { [K in keyof P]: Encoder<P[K], P[K]> }): Encoder<P, P>;

/**
 * Create partial struct encoder where properties may be undefined
 * @param encoders - Object mapping property names to encoders
 */
function partial<P>(encoders: { [K in keyof P]: Encoder<P[K], P[K]> }): Encoder<Partial<P>, Partial<P>>;

/**
 * Create array encoder with element encoder
 * @param item - Encoder for array elements
 */
function array<O, A>(item: Encoder<O, A>): Encoder<Array<O>, Array<A>>;

/**
 * Create record encoder with value encoder
 * @param codomain - Encoder for record values
 */
function record<O, A>(codomain: Encoder<O, A>): Encoder<Record<string, O>, Record<string, A>>;

/**
 * Create tuple encoder with component encoders
 * @param components - Array of encoders for each tuple position
 */
function tuple<C extends ReadonlyArray<Encoder<any, any>>>(
  ...components: C
): Encoder<{ [K in keyof C]: OutputOf<C[K]> }, { [K in keyof C]: TypeOf<C[K]> }>;

/**
 * Create sum encoder for discriminated unions
 * @param tag - Discriminant property name
 */
function sum<T extends string>(tag: T): <MS extends Record<string, Encoder<any, any>>>(
  members: MS
) => Encoder<OutputOf<MS[keyof MS]>, TypeOf<MS[keyof MS]>>;

/**
 * Make encoder nullable
 * @param encoder - Base encoder to make nullable
 */
function nullable<O, A>(encoder: Encoder<O, A>): Encoder<O | null, A | null>;

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

/**
 * Make encoder readonly
 * @param encoder - Base encoder to make readonly
 */
function readonly<O, A>(encoder: Encoder<O, A>): Encoder<Readonly<O>, Readonly<A>>;

/**
 * Intersect two encoders
 * @param right - Second encoder to intersect
 */
function intersect<P, B>(
  right: Encoder<P, B>
): <O, A>(left: Encoder<O, A>) => Encoder<O & P, A & B>;

Usage Examples:

import * as E from "io-ts/Encoder";

// Struct encoder
const UserEncoder = E.struct({
  name: E.id<string>(),
  age: E.id<number>(),
  email: E.id<string>()
});

// Array encoder
const NumbersEncoder = E.array(E.id<number>());

// Custom encoder that transforms dates to ISO strings
const DateToStringEncoder: E.Encoder<string, Date> = {
  encode: (date: Date) => date.toISOString()
};

// Compose encoders
const TimestampedDataEncoder = E.struct({
  data: E.id<string>(),
  timestamp: DateToStringEncoder
});

const result = TimestampedDataEncoder.encode({
  data: "hello",
  timestamp: new Date("2023-01-01")
});
// result: { data: "hello", timestamp: "2023-01-01T00:00:00.000Z" }

Functional Composition

Functional programming utilities for encoder composition.

/**
 * Contramap over encoder (transform input before encoding)
 * @param f - Function to transform input
 */
function contramap<A, B>(f: (b: B) => A): <O>(encoder: Encoder<O, A>) => Encoder<O, B>;

/**
 * Compose two encoders
 * @param to - Target encoder
 */
function compose<E, A>(to: Encoder<E, A>): <O>(from: Encoder<A, O>) => Encoder<E, O>;

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

Usage Examples:

import * as E from "io-ts/Encoder";
import { pipe } from "fp-ts/function";

// Contramap example: encode Person as string representation
interface Person {
  firstName: string;
  lastName: string;
  age: number;
}

const PersonToStringEncoder = pipe(
  E.id<string>(),
  E.contramap((person: Person) => 
    `${person.firstName} ${person.lastName} (${person.age})`
  )
);

const person: Person = {
  firstName: "Alice",
  lastName: "Smith", 
  age: 30
};

const encoded = PersonToStringEncoder.encode(person);
// result: "Alice Smith (30)"

// Compose encoders
const NumberToStringEncoder = pipe(
  E.id<string>(),
  E.contramap(String)
);

const result = NumberToStringEncoder.encode(42);
// result: "42"

Type Utilities

Type extraction utilities for encoders.

/** Extract output type from encoder */
type OutputOf<E> = E extends Encoder<infer O, any> ? O : never;

/** Extract input type from encoder */
type TypeOf<E> = E extends Encoder<any, infer A> ? A : never;

Functional Programming Support

Instances for functional programming patterns.

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

/** Contravariant functor instance */
const Contravariant: Contravariant1<typeof URI>;

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

Advanced Usage Patterns

Data Transformation Pipeline

import * as E from "io-ts/Encoder";
import { pipe } from "fp-ts/function";

// Transform complex objects for API serialization
interface User {
  id: number;
  profile: {
    firstName: string;
    lastName: string;
    birthDate: Date;
  };
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
}

interface UserApiFormat {
  id: number;
  fullName: string;
  birthYear: number;
  theme: string;
  notificationsEnabled: boolean;
}

const UserToApiEncoder: E.Encoder<UserApiFormat, User> = {
  encode: (user: User) => ({
    id: user.id,
    fullName: `${user.profile.firstName} ${user.profile.lastName}`,
    birthYear: user.profile.birthDate.getFullYear(),
    theme: user.preferences.theme,
    notificationsEnabled: user.preferences.notifications
  })
};

const user: User = {
  id: 123,
  profile: {
    firstName: "Alice",
    lastName: "Johnson",
    birthDate: new Date("1990-05-15")
  },
  preferences: {
    theme: 'dark',
    notifications: true
  }
};

const apiFormat = UserToApiEncoder.encode(user);
// result: {
//   id: 123,
//   fullName: "Alice Johnson", 
//   birthYear: 1990,
//   theme: "dark",
//   notificationsEnabled: true
// }

Database Entity Encoding

import * as E from "io-ts/Encoder";

// Encode domain objects for database storage
interface Product {
  id: string;
  name: string;
  price: number;
  categories: string[];
  metadata: Record<string, unknown>;
  createdAt: Date;
  updatedAt: Date;
}

interface ProductRow {
  id: string;
  name: string;
  price_cents: number;
  categories_json: string;
  metadata_json: string;
  created_at: string;
  updated_at: string;
}

const ProductToRowEncoder: E.Encoder<ProductRow, Product> = {
  encode: (product: Product) => ({
    id: product.id,
    name: product.name,
    price_cents: Math.round(product.price * 100),
    categories_json: JSON.stringify(product.categories),
    metadata_json: JSON.stringify(product.metadata),
    created_at: product.createdAt.toISOString(),
    updated_at: product.updatedAt.toISOString()
  })
};

const product: Product = {
  id: "prod-123",
  name: "Laptop",
  price: 999.99,
  categories: ["electronics", "computers"],
  metadata: { brand: "TechCorp", model: "X1" },
  createdAt: new Date("2023-01-01"),
  updatedAt: new Date("2023-01-15")
};

const dbRow = ProductToRowEncoder.encode(product);

Format Conversion

import * as E from "io-ts/Encoder";
import { pipe } from "fp-ts/function";

// Convert between different date/time formats
const IsoDateEncoder = pipe(
  E.id<string>(),
  E.contramap((date: Date) => date.toISOString())
);

const UnixTimestampEncoder = pipe(
  E.id<number>(),
  E.contramap((date: Date) => Math.floor(date.getTime() / 1000))
);

const LocaleDateEncoder = pipe(
  E.id<string>(),
  E.contramap((date: Date) => date.toLocaleDateString())
);

// Create different representations of the same data
const now = new Date();

const formats = {
  iso: IsoDateEncoder.encode(now),
  unix: UnixTimestampEncoder.encode(now),
  locale: LocaleDateEncoder.encode(now)
};

console.log(formats);
// Output: {
//   iso: "2023-01-01T12:00:00.000Z",
//   unix: 1672574400,
//   locale: "1/1/2023"
// }

Configuration Serialization

import * as E from "io-ts/Encoder";

// Encode configuration objects for storage or transmission
interface AppConfig {
  database: {
    host: string;
    port: number;
    ssl: boolean;
  };
  features: {
    enableAnalytics?: boolean;
    maxConnections?: number;
  };
  environment: 'development' | 'staging' | 'production';
}

interface ConfigFile {
  database_url: string;
  analytics_enabled: boolean;
  max_connections: number;
  env: string;
}

const ConfigToFileEncoder: E.Encoder<ConfigFile, AppConfig> = {
  encode: (config: AppConfig) => ({
    database_url: `${config.database.ssl ? 'postgresql' : 'postgres'}://${config.database.host}:${config.database.port}`,
    analytics_enabled: config.features.enableAnalytics ?? false,
    max_connections: config.features.maxConnections ?? 10,
    env: config.environment
  })
};

const appConfig: AppConfig = {
  database: {
    host: "localhost",
    port: 5432,
    ssl: true
  },
  features: {
    enableAnalytics: true,
    maxConnections: 20
  },
  environment: 'production'
};

const configFile = ConfigToFileEncoder.encode(appConfig);
// result: {
//   database_url: "postgresql://localhost:5432",
//   analytics_enabled: true,
//   max_connections: 20,
//   env: "production"
// }

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