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

collection-codecs.mddocs/

Collection Codecs

Codecs for working with arrays, sets, and maps including non-empty arrays and proper serialization of complex collection types. These codecs enable type-safe handling of collections with specialized constraints and serialization formats.

Capabilities

NonEmptyArray

Creates codecs for fp-ts NonEmptyArray types that ensure arrays always contain at least one element.

/**
 * Type interface for NonEmptyArray codec
 */
interface NonEmptyArrayC<C extends t.Mixed> 
  extends t.Type<NonEmptyArray<t.TypeOf<C>>, NonEmptyArray<t.OutputOf<C>>, unknown> {}

/**
 * Creates a codec for NonEmptyArray<A> that validates arrays are non-empty
 * @param codec - The codec for individual array elements
 * @param name - Optional name for the codec
 * @returns NonEmptyArray codec that ensures at least one element
 */
function nonEmptyArray<C extends t.Mixed>(
  codec: C, 
  name?: string
): NonEmptyArrayC<C>;

Usage Examples:

import { nonEmptyArray } from "io-ts-types";
import * as t from "io-ts";
import { NonEmptyArray } from "fp-ts/lib/NonEmptyArray";

// Create codec for non-empty array of strings
const NonEmptyStringArray = nonEmptyArray(t.string);

const result1 = NonEmptyStringArray.decode(["hello", "world"]);
// Right(NonEmptyArray<string>)

const result2 = NonEmptyStringArray.decode(["single"]);
// Right(NonEmptyArray<string>)

const result3 = NonEmptyStringArray.decode([]);
// Left([ValidationError]) - empty array not allowed

const result4 = NonEmptyStringArray.decode(["hello", 123]);
// Left([ValidationError]) - invalid element type

// Encoding back to regular array
const nonEmptyArray = result1.right;
const encoded = NonEmptyStringArray.encode(nonEmptyArray);
// ["hello", "world"]

ReadonlyNonEmptyArray

Creates codecs for readonly non-empty arrays, providing immutable collection guarantees.

/**
 * Interface for readonly non-empty arrays
 */
interface ReadonlyNonEmptyArray<A> extends ReadonlyArray<A> {
  readonly 0: A;
}

/**
 * Type interface for ReadonlyNonEmptyArray codec
 */
interface ReadonlyNonEmptyArrayC<C extends t.Mixed> 
  extends t.Type<ReadonlyNonEmptyArray<t.TypeOf<C>>, ReadonlyNonEmptyArray<t.OutputOf<C>>, unknown> {}

/**
 * Creates a codec for ReadonlyNonEmptyArray<A>
 * @param codec - The codec for individual array elements
 * @param name - Optional name for the codec
 * @returns ReadonlyNonEmptyArray codec
 */
function readonlyNonEmptyArray<C extends t.Mixed>(
  codec: C, 
  name?: string
): ReadonlyNonEmptyArrayC<C>;

Usage Examples:

import { readonlyNonEmptyArray } from "io-ts-types";
import * as t from "io-ts";

const ReadonlyNumbers = readonlyNonEmptyArray(t.number);

const result1 = ReadonlyNumbers.decode([1, 2, 3]);
// Right(ReadonlyNonEmptyArray<number>)

const result2 = ReadonlyNumbers.decode([]);
// Left([ValidationError]) - empty array

// The result is readonly - no mutation methods available
if (result1._tag === "Right") {
  const numbers = result1.right;
  const first = numbers[0]; // 1 (guaranteed to exist)
  // numbers.push(4); // TypeScript error - readonly array
}

Set from Array

Creates codecs for Set types that serialize as arrays, with uniqueness validation and ordering support.

/**
 * Type interface for Set codec
 */
interface SetFromArrayC<C extends t.Mixed> 
  extends t.Type<Set<t.TypeOf<C>>, Array<t.OutputOf<C>>, unknown> {}

/**
 * Creates a codec for Set<A> that serializes as an array
 * @param codec - The codec for individual set elements
 * @param O - Ord instance for element comparison and ordering
 * @param name - Optional name for the codec
 * @returns Set codec that validates uniqueness and requires Ord instance
 */
function setFromArray<C extends t.Mixed>(
  codec: C, 
  O: Ord<t.TypeOf<C>>, 
  name?: string
): SetFromArrayC<C>;

Usage Examples:

import { setFromArray } from "io-ts-types";
import * as t from "io-ts";
import { Ord } from "fp-ts/lib/Ord";
import * as S from "fp-ts/lib/string";
import * as N from "fp-ts/lib/number";

// String set with string ordering
const StringSet = setFromArray(t.string, S.Ord);

const result1 = StringSet.decode(["apple", "banana", "cherry"]);
// Right(Set<string>)

const result2 = StringSet.decode(["apple", "banana", "apple"]);
// Right(Set<string>) - duplicates removed

const result3 = StringSet.decode([]);
// Right(Set<string>) - empty set is valid

// Number set with number ordering
const NumberSet = setFromArray(t.number, N.Ord);

const result4 = NumberSet.decode([3, 1, 4, 1, 5]);
// Right(Set<number>) - duplicates removed, order determined by Ord

// Encoding back to array (ordered by Ord)
const stringSet = new Set(["zebra", "apple", "banana"]);
const encoded = StringSet.encode(stringSet);
// ["apple", "banana", "zebra"] - sorted by string Ord

ReadonlySet from Array

Creates codecs for ReadonlySet types with immutable guarantees.

/**
 * Type interface for ReadonlySet codec
 */
interface ReadonlySetFromArrayC<C extends t.Mixed> 
  extends t.Type<ReadonlySet<t.TypeOf<C>>, ReadonlyArray<t.OutputOf<C>>, unknown> {}

/**
 * Creates a codec for ReadonlySet<A>
 * @param codec - The codec for individual set elements
 * @param O - Ord instance for element comparison and ordering
 * @param name - Optional name for the codec
 * @returns ReadonlySet codec
 */
function readonlySetFromArray<C extends t.Mixed>(
  codec: C, 
  O: Ord<t.TypeOf<C>>, 
  name?: string
): ReadonlySetFromArrayC<C>;

Map from Entries

Creates codecs for Map types that serialize as arrays of key-value pairs.

/**
 * Type interface for Map codec
 */
interface MapFromEntriesC<K extends t.Mixed, V extends t.Mixed> 
  extends t.Type<Map<t.TypeOf<K>, t.TypeOf<V>>, Array<[t.OutputOf<K>, t.OutputOf<V>]>, unknown> {}

/**
 * Creates a codec for Map<K, V> that serializes as an array of key-value pairs
 * @param keyCodec - The codec for map keys
 * @param KO - Ord instance for key comparison and ordering
 * @param valueCodec - The codec for map values
 * @param name - Optional name for the codec
 * @returns Map codec that requires Ord instance for keys
 */
function mapFromEntries<K extends t.Mixed, V extends t.Mixed>(
  keyCodec: K, 
  KO: Ord<t.TypeOf<K>>, 
  valueCodec: V, 
  name?: string
): MapFromEntriesC<K, V>;

Usage Examples:

import { mapFromEntries } from "io-ts-types";
import * as t from "io-ts";
import * as S from "fp-ts/lib/string";
import * as N from "fp-ts/lib/number";

// Map with string keys and number values
const StringNumberMap = mapFromEntries(t.string, S.Ord, t.number);

const result1 = StringNumberMap.decode([
  ["apple", 5],
  ["banana", 3], 
  ["cherry", 8]
]);
// Right(Map<string, number>)

const result2 = StringNumberMap.decode([]);
// Right(Map<string, number>) - empty map is valid

const result3 = StringNumberMap.decode([
  ["apple", 5],
  ["apple", 10]  // Duplicate key - last value wins
]);
// Right(Map<string, number>) - Map with apple -> 10

// Invalid value type
const result4 = StringNumberMap.decode([
  ["apple", "not-a-number"]
]);
// Left([ValidationError])

// Encoding back to entries array
const map = new Map([["zebra", 1], ["apple", 2], ["banana", 3]]);
const encoded = StringNumberMap.encode(map);
// [["apple", 2], ["banana", 3], ["zebra", 1]] - sorted by key Ord

ReadonlyMap from Entries

Creates codecs for ReadonlyMap types with immutable guarantees.

/**
 * Type interface for ReadonlyMap codec
 */
interface ReadonlyMapFromEntriesC<K extends t.Mixed, V extends t.Mixed> 
  extends t.Type<ReadonlyMap<t.TypeOf<K>, t.TypeOf<V>>, ReadonlyArray<[t.OutputOf<K>, t.OutputOf<V>]>, unknown> {}

/**
 * Creates a codec for ReadonlyMap<K, V>
 * @param keyCodec - The codec for map keys
 * @param KO - Ord instance for key comparison and ordering
 * @param valueCodec - The codec for map values
 * @param name - Optional name for the codec
 * @returns ReadonlyMap codec
 */
function readonlyMapFromEntries<K extends t.Mixed, V extends t.Mixed>(
  keyCodec: K, 
  KO: Ord<t.TypeOf<K>>, 
  valueCodec: V, 
  name?: string
): ReadonlyMapFromEntriesC<K, V>;

Common Usage Patterns

API Response with Collections

import * as t from "io-ts";
import { nonEmptyArray, setFromArray, mapFromEntries } from "io-ts-types";
import * as S from "fp-ts/lib/string";
import * as N from "fp-ts/lib/number";

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

const Project = t.type({
  id: t.number,
  name: t.string,
  members: nonEmptyArray(User),           // At least one member required
  tags: setFromArray(t.string, S.Ord),   // Unique tags
  metadata: mapFromEntries(t.string, S.Ord, t.string) // Key-value metadata
});

const projectData = {
  id: 1,
  name: "Web Application",
  members: [
    { id: 1, name: "Alice", email: "alice@example.com" },
    { id: 2, name: "Bob", email: "bob@example.com" }
  ],
  tags: ["typescript", "react", "nodejs", "react"], // Duplicate removed
  metadata: [
    ["repository", "https://github.com/org/project"],
    ["status", "active"],
    ["priority", "high"]
  ]
};

const parsed = Project.decode(projectData);
// Right({ 
//   id: 1, 
//   name: "Web Application", 
//   members: NonEmptyArray<User>, 
//   tags: Set<string>, 
//   metadata: Map<string, string> 
// })

Configuration with Collections

import * as t from "io-ts";
import { setFromArray, mapFromEntries, nonEmptyArray } from "io-ts-types";
import * as S from "fp-ts/lib/string";
import * as N from "fp-ts/lib/number";

const ServerConfig = t.type({
  hosts: nonEmptyArray(t.string),                    // At least one host
  allowedOrigins: setFromArray(t.string, S.Ord),    // Unique origins
  ports: setFromArray(t.number, N.Ord),             // Unique ports
  environment: mapFromEntries(t.string, S.Ord, t.string) // Env variables
});

const config = {
  hosts: ["api.example.com", "api2.example.com"],
  allowedOrigins: ["https://app.example.com", "https://admin.example.com"],
  ports: [8080, 8443, 9000],
  environment: [
    ["NODE_ENV", "production"],
    ["LOG_LEVEL", "info"],
    ["DATABASE_URL", "postgresql://..."]
  ]
};

const validated = ServerConfig.decode(config);
// Right({ hosts: NonEmptyArray, allowedOrigins: Set, ports: Set, environment: Map })

Data Processing Pipeline

import * as t from "io-ts";
import { nonEmptyArray, setFromArray } from "io-ts-types";
import * as S from "fp-ts/lib/string";
import { pipe } from "fp-ts/lib/function";
import * as NEA from "fp-ts/lib/NonEmptyArray";

const ProcessingJob = t.type({
  id: t.string,
  inputFiles: nonEmptyArray(t.string),      // Must have input files
  outputFormats: setFromArray(t.string, S.Ord), // Unique output formats
  steps: nonEmptyArray(t.string)            // Must have processing steps
});

const jobData = {
  id: "job_001",
  inputFiles: ["data.csv", "metadata.json"],
  outputFormats: ["json", "csv", "xml", "json"], // Duplicate removed
  steps: ["validate", "transform", "aggregate", "export"]
};

const job = ProcessingJob.decode(jobData);

if (job._tag === "Right") {
  const { inputFiles, outputFormats, steps } = job.right;
  
  // Safe operations on non-empty arrays
  const firstFile = NEA.head(inputFiles);      // "data.csv"
  const totalSteps = steps.length;             // 4
  
  // Safe operations on sets
  const hasJsonOutput = outputFormats.has("json"); // true
  const formatCount = outputFormats.size;           // 3 (duplicates removed)
  
  console.log(`Processing ${totalSteps} steps for ${inputFiles.length} files`);
  console.log(`Generating ${formatCount} output formats`);
}

Database Entity with Collections

import * as t from "io-ts";
import { nonEmptyArray, setFromArray, mapFromEntries } from "io-ts-types";
import * as S from "fp-ts/lib/string";
import * as N from "fp-ts/lib/number";

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

const Article = t.type({
  id: t.number,
  title: t.string,
  content: t.string,
  categories: nonEmptyArray(Category),       // Must be in at least one category
  tags: setFromArray(t.string, S.Ord),      // Unique tags
  metadata: mapFromEntries(t.string, S.Ord, t.string), // Custom metadata
  relatedArticles: setFromArray(t.number, N.Ord)       // Unique article IDs
});

const articleData = {
  id: 1,
  title: "Getting Started with TypeScript",
  content: "TypeScript is a typed superset of JavaScript...",
  categories: [
    { id: 1, name: "Programming" },
    { id: 2, name: "TypeScript" }
  ],
  tags: ["typescript", "javascript", "tutorial", "beginners"],
  metadata: [
    ["author", "Alice Developer"],
    ["publishDate", "2023-12-25"],
    ["readTime", "10 minutes"]
  ],
  relatedArticles: [5, 12, 8, 15]
};

const parsed = Article.decode(articleData);
// Right({ 
//   id: 1, 
//   title: "...", 
//   content: "...", 
//   categories: NonEmptyArray<Category>, 
//   tags: Set<string>, 
//   metadata: Map<string, string>, 
//   relatedArticles: Set<number> 
// })

Form Processing with Collections

import * as t from "io-ts";
import { nonEmptyArray, setFromArray } from "io-ts-types";
import * as S from "fp-ts/lib/string";

const SurveyResponse = t.type({
  respondentId: t.string,
  answers: nonEmptyArray(t.string),          // Must answer at least one question
  selectedOptions: setFromArray(t.string, S.Ord), // Unique selected options
  feedback: t.union([t.string, t.null])
});

// Form data with repeated selections
const responseData = {
  respondentId: "user_123",
  answers: ["Satisfied", "Very Good", "Yes"],
  selectedOptions: ["option_a", "option_c", "option_b", "option_a"], // Duplicates
  feedback: "Great survey!"
};

const validated = SurveyResponse.decode(responseData);

if (validated._tag === "Right") {
  const response = validated.right;
  
  // selectedOptions is now a Set with duplicates removed
  const uniqueSelections = Array.from(response.selectedOptions);
  // ["option_a", "option_b", "option_c"] - sorted by string Ord
  
  console.log(`Respondent ${response.respondentId} provided ${response.answers.length} answers`);
  console.log(`Selected ${response.selectedOptions.size} unique options`);
}

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