CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-io-ts-types

A collection of codecs and combinators for use with io-ts

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

functional-programming-types.mddocs/

Functional Programming Types

Codecs for fp-ts Option and Either types with JSON serialization support, enabling functional programming patterns in API communication. These codecs allow safe serialization and deserialization of functional programming constructs to and from JSON representations.

Capabilities

Option Type Codec

Creates codecs for fp-ts Option types with JSON serialization, allowing None and Some values to be transmitted over networks and stored in databases.

/**
 * Output type for None values in Option serialization
 */
type NoneOutput = { _tag: 'None' };

/**
 * Output type for Some values in Option serialization
 */
type SomeOutput<A> = { _tag: 'Some'; value: A };

/**
 * Union type for Option serialization output
 */
type OptionOutput<A> = NoneOutput | SomeOutput<A>;

/**
 * Type interface for Option codec
 */
interface OptionC<C extends t.Mixed> 
  extends t.Type<Option<t.TypeOf<C>>, OptionOutput<t.OutputOf<C>>, unknown> {}

/**
 * Creates a codec for Option<A> that can serialize/deserialize JSON representations
 * @param codec - The inner codec for the Some value type
 * @param name - Optional name for the codec
 * @returns Option codec with JSON serialization support
 */
function option<C extends t.Mixed>(codec: C, name?: string): OptionC<C>;

Usage Examples:

import { option } from "io-ts-types";
import * as t from "io-ts";
import * as O from "fp-ts/lib/Option";

// Create codec for optional string
const OptionalString = option(t.string);

// Decode Some value
const someResult = OptionalString.decode({ _tag: "Some", value: "hello" });
// Right(some("hello"))

// Decode None value
const noneResult = OptionalString.decode({ _tag: "None" });
// Right(none)

// Invalid structure
const invalidResult = OptionalString.decode({ _tag: "Invalid" });
// Left([ValidationError])

// Encoding Some to JSON
const someValue = O.some("world");
const encodedSome = OptionalString.encode(someValue);
// { _tag: "Some", value: "world" }

// Encoding None to JSON
const noneValue = O.none;
const encodedNone = OptionalString.encode(noneValue);
// { _tag: "None" }

Option From Nullable

Creates Option codecs that treat null/undefined as None and other values as Some, providing a bridge between nullable values and Option types.

/**
 * Type interface for OptionFromNullable codec
 */
interface OptionFromNullableC<C extends t.Mixed> 
  extends t.Type<Option<t.TypeOf<C>>, t.OutputOf<C> | null, unknown> {}

/**
 * Creates a codec for Option<A> that treats null/undefined as None
 * @param codec - The inner codec for the Some value type
 * @param name - Optional name for the codec
 * @returns Option codec that handles nullable inputs
 */
function optionFromNullable<C extends t.Mixed>(
  codec: C, 
  name?: string
): OptionFromNullableC<C>;

Usage Examples:

import { optionFromNullable } from "io-ts-types";
import * as t from "io-ts";
import * as O from "fp-ts/lib/Option";

const OptionalStringFromNull = optionFromNullable(t.string);

// Decode regular value as Some
const result1 = OptionalStringFromNull.decode("hello");
// Right(some("hello"))

// Decode null as None
const result2 = OptionalStringFromNull.decode(null);
// Right(none)

// Decode undefined as None
const result3 = OptionalStringFromNull.decode(undefined);
// Right(none)

// Invalid inner type
const result4 = OptionalStringFromNull.decode(123);
// Left([ValidationError]) - number is not a string

// Encoding Some back to regular value
const someValue = O.some("world");
const encoded1 = OptionalStringFromNull.encode(someValue);
// "world"

// Encoding None back to null
const noneValue = O.none;
const encoded2 = OptionalStringFromNull.encode(noneValue);
// null

Either Type Codec

Creates codecs for fp-ts Either types with JSON serialization, allowing Left and Right values to be transmitted and stored.

/**
 * Output type for Left values in Either serialization
 */
type LeftOutput<L> = { _tag: 'Left'; left: L };

/**
 * Output type for Right values in Either serialization
 */
type RightOutput<R> = { _tag: 'Right'; right: R };

/**
 * Union type for Either serialization output
 */
type EitherOutput<L, R> = LeftOutput<L> | RightOutput<R>;

/**
 * Type interface for Either codec
 */
interface EitherC<L extends t.Mixed, R extends t.Mixed> 
  extends t.Type<Either<t.TypeOf<L>, t.TypeOf<R>>, EitherOutput<t.OutputOf<L>, t.OutputOf<R>>, unknown> {}

/**
 * Creates a codec for Either<L, R> that can serialize/deserialize JSON representations
 * @param leftCodec - Codec for the Left value type
 * @param rightCodec - Codec for the Right value type  
 * @param name - Optional name for the codec
 * @returns Either codec with JSON serialization support
 */
function either<L extends t.Mixed, R extends t.Mixed>(
  leftCodec: L, 
  rightCodec: R, 
  name?: string
): EitherC<L, R>;

Usage Examples:

import { either } from "io-ts-types";
import * as t from "io-ts";
import * as E from "fp-ts/lib/Either";

// Create codec for Either<string, number>
const StringOrNumber = either(t.string, t.number);

// Decode Right value
const rightResult = StringOrNumber.decode({ _tag: "Right", right: 42 });
// Right(right(42))

// Decode Left value
const leftResult = StringOrNumber.decode({ _tag: "Left", left: "error" });
// Right(left("error"))

// Invalid structure
const invalidResult = StringOrNumber.decode({ _tag: "Invalid" });
// Left([ValidationError])

// Invalid Right type
const invalidRight = StringOrNumber.decode({ _tag: "Right", right: true });
// Left([ValidationError]) - boolean is not a number

// Encoding Right to JSON
const rightValue = E.right(100);
const encodedRight = StringOrNumber.encode(rightValue);
// { _tag: "Right", right: 100 }

// Encoding Left to JSON
const leftValue = E.left("failure");
const encodedLeft = StringOrNumber.encode(leftValue);
// { _tag: "Left", left: "failure" }

Common Usage Patterns

API Error Handling

import * as t from "io-ts";
import { either, option } from "io-ts-types";
import * as E from "fp-ts/lib/Either";
import * as O from "fp-ts/lib/Option";

const ApiError = t.type({
  code: t.string,
  message: t.string,
  details: option(t.string)
});

const UserData = t.type({
  id: t.number,
  name: t.string,
  email: t.string
});

const ApiResponse = either(ApiError, UserData);

// Success response
const successResponse = {
  _tag: "Right",
  right: {
    id: 1,
    name: "Alice",
    email: "alice@example.com"
  }
};

const parsedSuccess = ApiResponse.decode(successResponse);
// Right(right({ id: 1, name: "Alice", email: "alice@example.com" }))

// Error response
const errorResponse = {
  _tag: "Left",
  left: {
    code: "USER_NOT_FOUND",
    message: "User not found",
    details: { _tag: "Some", value: "User ID 123 does not exist" }
  }
};

const parsedError = ApiResponse.decode(errorResponse);
// Right(left({ code: "USER_NOT_FOUND", message: "User not found", details: some("...") }))

Optional Configuration Fields

import * as t from "io-ts";
import { option, optionFromNullable } from "io-ts-types";
import * as O from "fp-ts/lib/Option";

const DatabaseConfig = t.type({
  host: t.string,
  port: t.number,
  ssl: optionFromNullable(t.boolean),      // null becomes None
  timeout: option(t.number),               // Explicit Option structure
  credentials: optionFromNullable(t.type({
    username: t.string,
    password: t.string
  }))
});

// Configuration with mixed optional fields
const config1 = {
  host: "localhost",
  port: 5432,
  ssl: true,                    // Some(true)
  timeout: { _tag: "None" },    // Explicit None
  credentials: {                // Some(credentials)
    username: "admin", 
    password: "secret"
  }
};

const parsed1 = DatabaseConfig.decode(config1);

// Configuration with null values
const config2 = {
  host: "remote.db.com", 
  port: 5432,
  ssl: null,                    // None (from null)
  timeout: { _tag: "Some", value: 30000 }, // Explicit Some
  credentials: null             // None (from null)
};

const parsed2 = DatabaseConfig.decode(config2);

Form Validation with Optional Fields

import * as t from "io-ts";
import { optionFromNullable } from "io-ts-types";
import { pipe } from "fp-ts/lib/function";
import * as O from "fp-ts/lib/Option";

const UserProfile = t.type({
  username: t.string,
  email: t.string,
  bio: optionFromNullable(t.string),
  website: optionFromNullable(t.string),
  avatar: optionFromNullable(t.string)
});

const formData = {
  username: "alice_dev",
  email: "alice@example.com", 
  bio: "Full-stack developer",  // Some("Full-stack developer")
  website: null,                // None
  avatar: undefined             // None
};

const validated = UserProfile.decode(formData);

if (validated._tag === "Right") {
  const profile = validated.right;
  
  // Safe handling of optional fields
  const displayBio = pipe(
    profile.bio,
    O.getOrElse(() => "No bio provided")
  );
  
  const hasWebsite = O.isSome(profile.website);
  
  console.log(`Bio: ${displayBio}`);         // Bio: Full-stack developer
  console.log(`Has website: ${hasWebsite}`); // Has website: false
}

Result Type Pattern

import * as t from "io-ts";
import { either } from "io-ts-types";
import * as E from "fp-ts/lib/Either";
import { pipe } from "fp-ts/lib/function";

const ValidationError = t.type({
  field: t.string,
  message: t.string
});

const ProcessingResult = either(
  t.array(ValidationError),  // Left: array of validation errors
  t.type({                   // Right: success result
    id: t.string,
    processed: t.boolean,
    timestamp: t.number
  })
);

// Success case
const successData = {
  _tag: "Right",
  right: {
    id: "task_123",
    processed: true,
    timestamp: 1703505000000
  }
};

// Error case
const errorData = {
  _tag: "Left", 
  left: [
    { field: "email", message: "Invalid email format" },
    { field: "age", message: "Must be a positive number" }
  ]
};

function handleResult(data: unknown) {
  return pipe(
    ProcessingResult.decode(data),
    E.fold(
      (decodeErrors) => `Decode error: ${decodeErrors}`,
      E.fold(
        (validationErrors) => `Validation failed: ${validationErrors.map(e => e.message).join(", ")}`,
        (success) => `Processed successfully: ${success.id}`
      )
    )
  );
}

const result1 = handleResult(successData);
// "Processed successfully: task_123"

const result2 = handleResult(errorData);  
// "Validation failed: Invalid email format, Must be a positive number"

Database Query Results

import * as t from "io-ts";
import { option, either } from "io-ts-types";
import * as O from "fp-ts/lib/Option";
import * as E from "fp-ts/lib/Either";

const User = t.type({
  id: t.number,
  name: t.string,
  lastLogin: option(t.string)  // Some(date) or None if never logged in
});

const DatabaseError = t.type({
  code: t.string,
  query: t.string
});

const QueryResult = either(DatabaseError, option(User));

// User found
const userFound = {
  _tag: "Right",
  right: {
    _tag: "Some",
    value: {
      id: 1,
      name: "Alice",
      lastLogin: { _tag: "Some", value: "2023-12-25T10:30:00Z" }
    }
  }
};

// User not found (query succeeded, but no result)
const userNotFound = {
  _tag: "Right", 
  right: { _tag: "None" }
};

// Database error
const dbError = {
  _tag: "Left",
  left: {
    code: "CONNECTION_TIMEOUT",
    query: "SELECT * FROM users WHERE id = 1"
  }
};

function processQueryResult(result: unknown) {
  return pipe(
    QueryResult.decode(result),
    E.fold(
      (errors) => `Invalid result format: ${errors}`,
      E.fold(
        (dbErr) => `Database error ${dbErr.code}: ${dbErr.query}`,
        O.fold(
          () => "User not found",
          (user) => `Found user: ${user.name} (ID: ${user.id})`
        )
      )
    )
  );
}

docs

branded-types-validation.md

collection-codecs.md

date-handling.md

functional-programming-types.md

index.md

json-handling.md

string-number-transformations.md

utility-functions-codec-modifiers.md

tile.json