A collection of codecs and combinators for use with io-ts
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Utility functions for creating, modifying, and composing codecs including fallback values, custom validation, and lens generation. These utilities enable advanced codec composition and customization patterns.
Creates codecs that use a fallback value when the original codec validation fails, providing graceful degradation.
/**
* Creates a codec that uses a fallback value when validation fails
* @param codec - The original codec to try first
* @param a - The fallback value to use on validation failure
* @param name - Optional name for the codec
* @returns Codec that never fails, using fallback on validation errors
*/
function withFallback<C extends t.Any>(
codec: C,
a: t.TypeOf<C>,
name?: string
): C;Usage Examples:
import { withFallback } from "io-ts-types";
import * as t from "io-ts";
// Number codec with fallback to 0
const NumberWithFallback = withFallback(t.number, 0);
const result1 = NumberWithFallback.decode(42);
// Right(42)
const result2 = NumberWithFallback.decode("invalid");
// Right(0) - fallback value used instead of validation error
const result3 = NumberWithFallback.decode(null);
// Right(0) - fallback value used
// String codec with default value
const NameWithDefault = withFallback(t.string, "Anonymous");
const result4 = NameWithDefault.decode("Alice");
// Right("Alice")
const result5 = NameWithDefault.decode(123);
// Right("Anonymous") - fallback used for invalid inputCreates codecs with custom error messages using a message function, providing more meaningful validation feedback.
/**
* Creates a codec with custom error messages
* @param codec - The original codec
* @param message - Function that generates custom error messages
* @returns Codec with custom error reporting
*/
function withMessage<C extends t.Any>(
codec: C,
message: (i: t.InputOf<C>, c: t.Context) => string
): C;Usage Examples:
import { withMessage } from "io-ts-types";
import * as t from "io-ts";
// Custom error message for number validation
const PositiveNumber = withMessage(
t.refinement(t.number, (n) => n > 0, "PositiveNumber"),
(input, context) => `Expected a positive number, got: ${JSON.stringify(input)}`
);
const result1 = PositiveNumber.decode(5);
// Right(5)
const result2 = PositiveNumber.decode(-1);
// Left([{ message: "Expected a positive number, got: -1", ... }])
const result3 = PositiveNumber.decode("not-a-number");
// Left([{ message: "Expected a positive number, got: \"not-a-number\"", ... }])
// Custom message for email-like validation
const EmailLike = withMessage(
t.refinement(t.string, (s) => s.includes("@"), "EmailLike"),
(input) => `"${input}" doesn't look like an email address`
);
const result4 = EmailLike.decode("user@example.com");
// Right("user@example.com")
const result5 = EmailLike.decode("invalid-email");
// Left([{ message: "\"invalid-email\" doesn't look like an email address", ... }])Creates codecs with custom validate functions while preserving other codec properties.
/**
* Creates a codec with a custom validate function
* @param codec - The original codec to modify
* @param validate - Custom validation function
* @param name - Optional name for the codec
* @returns Codec with custom validation logic
*/
function withValidate<C extends t.Any>(
codec: C,
validate: C['validate'],
name?: string
): C;Usage Examples:
import { withValidate } from "io-ts-types";
import * as t from "io-ts";
import * as E from "fp-ts/lib/Either";
// Custom validation that normalizes input
const TrimmedString = withValidate(
t.string,
(input, context) => {
if (typeof input !== "string") {
return t.failure(input, context);
}
const trimmed = input.trim();
return trimmed.length > 0
? t.success(trimmed)
: t.failure(input, context, "String cannot be empty after trimming");
},
"TrimmedString"
);
const result1 = TrimmedString.decode(" hello ");
// Right("hello") - trimmed
const result2 = TrimmedString.decode(" ");
// Left([ValidationError]) - empty after trimming
const result3 = TrimmedString.decode(123);
// Left([ValidationError]) - not a stringCreates codecs that replace null/undefined inputs with a default value, otherwise uses the original codec.
/**
* Creates a codec that replaces null/undefined with a default value
* @param codec - The original codec for non-null values
* @param a - The default value to use for null/undefined inputs
* @param name - Optional name for the codec
* @returns Codec that handles nullable inputs with defaults
*/
function fromNullable<C extends t.Mixed>(
codec: C,
a: t.TypeOf<C>,
name?: string
): C;Usage Examples:
import { fromNullable } from "io-ts-types";
import * as t from "io-ts";
// String with default for null/undefined
const StringWithDefault = fromNullable(t.string, "default");
const result1 = StringWithDefault.decode("hello");
// Right("hello")
const result2 = StringWithDefault.decode(null);
// Right("default")
const result3 = StringWithDefault.decode(undefined);
// Right("default")
const result4 = StringWithDefault.decode(123);
// Left([ValidationError]) - number is not string or null/undefined
// Number array with empty array default
const NumberArrayWithDefault = fromNullable(t.array(t.number), []);
const result5 = NumberArrayWithDefault.decode([1, 2, 3]);
// Right([1, 2, 3])
const result6 = NumberArrayWithDefault.decode(null);
// Right([]) - empty array defaultCreates codecs from type guard functions (refinements), enabling custom type validation logic.
/**
* Creates a codec from a refinement (type guard) function
* @param name - Name for the codec
* @param is - Type guard function that determines if input matches type A
* @returns Codec that validates using the type guard
*/
function fromRefinement<A>(
name: string,
is: (u: unknown) => u is A
): t.Type<A, A, unknown>;Usage Examples:
import { fromRefinement } from "io-ts-types";
// Custom type guard for positive numbers
function isPositiveNumber(u: unknown): u is number {
return typeof u === "number" && u > 0;
}
const PositiveNumber = fromRefinement("PositiveNumber", isPositiveNumber);
const result1 = PositiveNumber.decode(5);
// Right(5)
const result2 = PositiveNumber.decode(-1);
// Left([ValidationError])
const result3 = PositiveNumber.decode("5");
// Left([ValidationError]) - string, not number
// Custom type guard for non-empty arrays
function isNonEmptyArray<T>(u: unknown): u is T[] {
return Array.isArray(u) && u.length > 0;
}
const NonEmptyArray = fromRefinement("NonEmptyArray", isNonEmptyArray);
const result4 = NonEmptyArray.decode([1, 2, 3]);
// Right([1, 2, 3])
const result5 = NonEmptyArray.decode([]);
// Left([ValidationError])Creates monocle-ts Lens objects for each property of an interface or exact type codec, enabling functional updates.
/**
* Interface for exact types that have lenses
*/
interface ExactHasLenses extends t.ExactType<HasLenses> {}
/**
* Union type for codecs that can have lenses generated
*/
type HasLenses = t.InterfaceType<any> | ExactHasLenses;
/**
* Creates monocle-ts Lens objects for each property of a codec
* @param codec - Interface or exact type codec
* @returns Object with lens for each property
*/
function getLenses<C extends HasLenses>(
codec: C
): { [K in keyof t.TypeOf<C>]: Lens<t.TypeOf<C>, t.TypeOf<C>[K]> };Usage Examples:
import { getLenses } from "io-ts-types";
import * as t from "io-ts";
const User = t.type({
id: t.number,
name: t.string,
email: t.string,
age: t.number
});
const userLenses = getLenses(User);
// Now you have lenses for each property
const user = { id: 1, name: "Alice", email: "alice@example.com", age: 25 };
// Update name using lens
const updatedUser = userLenses.name.set("Alice Smith")(user);
// { id: 1, name: "Alice Smith", email: "alice@example.com", age: 25 }
// Update age using lens
const olderUser = userLenses.age.modify((age) => age + 1)(user);
// { id: 1, name: "Alice", email: "alice@example.com", age: 26 }
// Get property using lens
const userName = userLenses.name.get(user);
// "Alice"
// Compose lenses for nested updates (if you had nested objects)
const Address = t.type({
street: t.string,
city: t.string,
zipCode: t.string
});
const UserWithAddress = t.type({
id: t.number,
name: t.string,
address: Address
});
const userWithAddressLenses = getLenses(UserWithAddress);
const addressLenses = getLenses(Address);
// Compose lenses for nested property access
const streetLens = userWithAddressLenses.address.compose(addressLenses.street);Transforms the output type of a codec by applying a function to the encoded value.
/**
* Transforms the output type of a codec
* @param codec - The original codec
* @param f - Function to transform the output
* @param name - Optional name for the codec
* @returns Codec with transformed output type
*/
function mapOutput<A, O, I, P>(
codec: t.Type<A, O, I>,
f: (p: O) => P,
name?: string
): t.Type<A, P, I>;Usage Examples:
import { mapOutput } from "io-ts-types";
import * as t from "io-ts";
// Transform number to string output
const NumberAsString = mapOutput(t.number, (n) => n.toString());
const result1 = NumberAsString.decode(42);
// Right(42) - still a number internally
const encoded1 = NumberAsString.encode(42);
// "42" - output transformed to string
// Transform date to ISO string output
const DateAsISO = mapOutput(t.type({ date: t.string }), (obj) => ({
...obj,
date: new Date(obj.date).toISOString()
}));
const dateObj = { date: "2023-12-25" };
const encoded2 = DateAsISO.encode(dateObj);
// { date: "2023-12-25T00:00:00.000Z" } - transformed to ISO stringCreates a codec clone with a custom encode function, allowing specialized serialization logic.
/**
* Creates a codec clone with a custom encode function
* @param codec - The original codec
* @param encode - Custom encoding function
* @param name - Optional name for the codec
* @returns Codec with custom encoding logic
*/
function withEncode<A, O, I, P>(
codec: t.Type<A, O, I>,
encode: (a: A) => P,
name?: string
): t.Type<A, P, I>;Usage Examples:
import { withEncode } from "io-ts-types";
import * as t from "io-ts";
// Custom encoding for user objects
const User = t.type({
id: t.number,
firstName: t.string,
lastName: t.string,
email: t.string
});
const UserWithFullName = withEncode(
User,
(user) => ({
...user,
fullName: `${user.firstName} ${user.lastName}`
})
);
const userData = {
id: 1,
firstName: "Alice",
lastName: "Smith",
email: "alice@example.com"
};
const result = UserWithFullName.decode(userData);
// Right({ id: 1, firstName: "Alice", lastName: "Smith", email: "alice@example.com" })
const encoded = UserWithFullName.encode(result.right);
// { id: 1, firstName: "Alice", lastName: "Smith", email: "alice@example.com", fullName: "Alice Smith" }Creates a shallow clone of a codec, preserving the original codec structure while allowing modifications.
/**
* Creates a shallow clone of a codec
* @param t - The codec to clone
* @returns Cloned codec with same prototype and properties
*/
function clone<C extends t.Any>(t: C): C;Usage Examples:
import { clone } from "io-ts-types";
import * as t from "io-ts";
const OriginalString = t.string;
const ClonedString = clone(OriginalString);
// Both codecs work identically
const result1 = OriginalString.decode("hello");
const result2 = ClonedString.decode("hello");
// Both: Right("hello")
// Useful when you need to modify codec properties
const ModifiedString = clone(t.string);
ModifiedString.name = "CustomString";Creates codecs from newtypes using newtype-ts iso functions, enabling safe wrapping and unwrapping of carrier types.
/**
* Creates a codec from a newtype using iso
* @param codec - The codec for the carrier type
* @param name - Optional name for the codec
* @returns Codec that wraps/unwraps the newtype
*/
function fromNewtype<N extends AnyNewtype = never>(
codec: t.Type<CarrierOf<N>, t.OutputOf<CarrierOf<N>>>,
name?: string
): t.Type<N, CarrierOf<N>, unknown>;Usage Examples:
import { fromNewtype } from "io-ts-types";
import * as t from "io-ts";
import { Newtype, iso } from "newtype-ts";
// Define a newtype for UserId
interface UserId extends Newtype<{ readonly UserId: unique symbol }, number> {}
// Create iso for the newtype
const userIdIso = iso<UserId>();
// Create codec using fromNewtype
const UserIdCodec = fromNewtype<UserId>(t.number);
const result1 = UserIdCodec.decode(123);
// Right(123 as UserId) - wrapped in newtype
const result2 = UserIdCodec.decode("invalid");
// Left([ValidationError]) - not a number
// Use with newtype operations
if (result1._tag === "Right") {
const userId = result1.right;
const rawNumber = userIdIso.unwrap(userId); // 123
const wrappedAgain = userIdIso.wrap(456); // 456 as UserId
}