npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

castage

v2.1.0

Published

A type-safe library for dynamic object casting and ensuring type consistency in JavaScript/TypeScript.

Readme

castage Coverage Status npm version npm downloads GitHub license

Castage is a small TypeScript runtime casting library. You describe the shape of unknown data with composable casters, run those casters at runtime, and get typed values back through resultage Result objects.

It is useful at boundaries where TypeScript cannot protect you by itself: JSON payloads, API responses, config files, request parameters, message queues, local storage, and other unknown inputs.

Installation

npm install castage

Main Features

  • Runtime type checks with static inference: a Caster<T> validates unknown input and narrows the successful result to T.
  • Composable schemas: build object, array, tuple, record, union, and intersection casters from smaller casters.
  • Refinements: use .validate(...) to add domain constraints such as positive numbers, non-blank strings, ranges, or custom predicates.
  • Transforms: use .map(...) and .chain(...) to convert successfully cast values into another representation.
  • Optional/default/null handling: derive .optional, .nullable, and .default(...) casters from any caster.
  • Structured errors: failed casts return CastingError values with an error code, path, expected type, and received value.
  • Single-error or multi-error parsing: call a caster directly for fail-fast casting, or call .parse(...) to collect nested errors where supported.

Quick Start

import { array, int, string, struct } from 'castage';
import { nonBlank, positive } from 'castage/validators';

const User = struct({
  id: int.validate(positive),
  name: string.validate(nonBlank),
  roles: array(string),
});

const result = User({
  id: 1,
  name: 'Ada',
  roles: ['admin', 'editor'],
});

if (result.isOk) {
  result.value.id; // number
  result.value.name; // string
  result.value.roles; // string[]
}

if (result.isErr) {
  console.error(result.error.path);
  console.error(result.error.extra);
}

A caster is just a function:

const result = int(42); // Result<number, CastingError>
const failed = string(42); // Result<string, CastingError>

Modeling Data

Primitives

import { boolean, int, nill, number, string, undef, unknown } from 'castage';

int(1); // ok(1)
number(1.5); // ok(1.5)
string('text'); // ok('text')
boolean(false); // ok(false)
nill(null); // ok(null)
undef(undefined); // ok(undefined)
unknown({ anything: true }); // ok(...)

Literal Values

import { value, values } from 'castage';

const Enabled = value('enabled');
const Status = values('draft', 'published', 'archived');

Objects

Use struct(...) for fixed object shapes. Missing required fields and invalid nested values include the failing path in the returned error.

import { int, string, struct } from 'castage';

const User = struct({
  id: int,
  name: string,
});

const result = User({ id: 1, name: 'Alice' });

Optional fields are expressed by deriving optional casters:

const UserPatch = struct({
  name: string.optional,
  age: int.optional,
});

Arrays, Tuples, and Records

import {
  array,
  boolean,
  int,
  number,
  record,
  string,
  text,
  tuple,
  unknown,
  values,
} from 'castage';

const IntList = array(int);
const Point = tuple(number, number);
const Scores = record(string, int);
const FeatureFlags = record(values('search', 'billing'), boolean);
const Metadata = record(string, unknown); // Record<string, unknown>
const UnknownResponses = record(text.int, unknown);

Use nonEmptyArray(caster) when the array must contain at least one item.

Unions and Intersections

Use oneOf(...) when several shapes are accepted, and allOf(...) when multiple object casters should all apply and merge.

import { allOf, int, oneOf, string, struct, values } from 'castage';

const Id = oneOf(int, string);

const Entity = allOf(
  struct({ id: Id }),
  struct({ kind: values('user', 'team') }),
);

JSON and Text Casters

Use json helpers to parse JSON strings before applying a caster, and text helpers to parse primitive values from strings.

import { json, string, struct, text } from 'castage';

const JsonUser = json.struct({
  name: string,
});

const parsedUser = JsonUser('{"name":"Alice"}');
const parsedInt = text.int('42');

Environment Variables

Use the dedicated castage/envs entrypoint for environment-specific constraints and explicit configuration loading. See src/envs/README.md for the full API and examples.

PKI Values

Use castage/pki for PEM-encoded RSA keys and X.509 certificates. See src/pki/README.md for the full API and examples.

Deriving TypeScript Types

Use CastedBy<typeof caster> to derive the TypeScript type produced by a caster. This keeps the runtime schema and static type in one place, so you do not have to maintain a separate interface that can drift from the actual validation rules.

import { array, int, string, struct, type CastedBy } from 'castage';
import { nonBlank, positive } from 'castage/validators';

const User = struct({
  id: int.validate(positive),
  name: string.validate(nonBlank),
  email: string.optional,
  roles: array(string),
});

type User = CastedBy<typeof User>;
// {
//   id: number;
//   name: string;
//   email?: string | undefined;
//   roles: string[];
// }

This also works with composed casters:

import { oneOf, string, value, type CastedBy } from 'castage';

const Command = oneOf(value('start'), value('stop'), string);

type Command = CastedBy<typeof Command>; // "start" | "stop" | string

OkType<typeof caster> is also exported for code that works directly with CasterFn values.

Refinements With Validators

.validate(predicate, name?) adds an extra rule after the base caster succeeds. The dedicated castage/validators entrypoint provides common reusable predicates; these helpers are intentionally not exported from the main castage entrypoint.

import { int, string } from 'castage';
import { and, between, max, nonBlank, positive } from 'castage/validators';

const PositiveInt = int.validate(positive);
const SizedInt = int.validate(between(1, 10));
const NonBlankString = string.validate(nonBlank);
const PositiveSmallInt = int.validate(and(positive, max(100)));

Predicate operators:

  • and(...predicates): Ensures every predicate returns true.
  • or(...predicates): Ensures at least one predicate returns true.
  • not(predicate): Ensures the predicate returns false.

Number validators:

  • finite, safeInteger
  • positive, negative, nonPositive, nonNegative
  • even, odd
  • min(value), greaterThan(value), max(value), lessThan(value)
  • between(min, max), multipleOf(divisor)

String validators:

  • nonBlank
  • matches(pattern)
  • startsWith(value), endsWith(value), includes(value)
  • email, url, uuid

Length validators for strings and arrays:

  • empty, nonEmpty
  • lengthOf(size), minLength(size), maxLength(size)
  • lengthBetween(min, max)

Array validators:

  • contains(value)
  • hasNoDuplicates
  • unique, an alias for hasNoDuplicates

Custom predicates work the same way:

type Email = string & { __brand: 'Email' };

const isEmail = (value: string): value is Email =>
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);

const Email = string.validate(isEmail, 'Email');

Transforming Values

Use .map(...) for simple transformations and .chain(...) when the next step can fail with a Result.

import { string } from 'castage';

const Trimmed = string.map((value) => value.trim(), 'TrimmedString');

For common text parsing, use the built-in text casters:

import { possibleText, text } from 'castage';

text.int('42'); // ok(42)
text.number('1.5'); // ok(1.5)
text.bool('true'); // ok(true)

possibleText.int(42); // ok(42)
possibleText.int('42'); // ok(42)

Error Handling

Calling a caster directly returns one CastingError on failure:

import { isCastingError, string } from 'castage';

const result = string(42, ['name']);

if (result.isErr && isCastingError(result.error)) {
  result.error.code; // "ERR_INVALID_VALUE_TYPE"
  result.error.path; // ["name"]
  result.error.extra.expected; // "string"
  result.error.extra.received; // 42
}

Use .parse(...) when you want a list of nested errors instead of the first failure:

const parsed = User.parse({ id: 'bad', name: 1 });

if (parsed.isErr) {
  parsed.error; // CastingError[]
}

Use .unpack when throwing on invalid data is preferable:

const id = int.unpack(42); // 42

API Reference

Caster<T>

The main runtime type abstraction. A caster is callable and has helper methods for deriving related casters.

interface Caster<T> {
  (value: unknown, path?: string[]): Result<T, CastingError>;

  nullable: Caster<T | null>;
  optional: Caster<T | undefined>;
  default(value: T, name?: string): Caster<T>;
  unpack: (value: unknown, path?: string[]) => T;

  validate<S extends T>(
    predicate: (value: T) => value is S,
    name?: string,
    error?: (value: T, path: string[]) => CastingError,
  ): Caster<S>;
  validate(
    predicate: (value: T) => boolean,
    name?: string,
    error?: (value: T, path: string[]) => CastingError,
  ): Caster<T>;

  match<S, E>(
    okMatcher: (data: T) => S,
    errMatcher: (err: CastingError) => E,
  ): (value: unknown, path?: string[]) => S | E;

  unpackOr<E>(
    handleError: (err: CastingError) => E,
  ): (value: unknown, path?: string[]) => T | E;

  map<S>(transform: (data: T) => S, name?: string): Caster<S>;

  chain<S>(
    casterFn: (data: T, path?: string[]) => Result<S, CastingError>,
    name?: string,
  ): Caster<S>;

  parse(value: unknown, path?: string[]): Result<T, CastingError[]>;

  assert(value: unknown, path?: string[]): asserts value is T;
}

Primitive Casters

  • int
  • number
  • string
  • boolean
  • object
  • nill
  • undef
  • any
  • unknown

Composition Helpers

  • array(caster, name?)
  • nonEmptyArray(caster, name?)
  • tuple(...casters)
  • struct(casters, name?)
  • record(keyCaster, valueCaster, name?)
  • oneOf(...casters)
  • allOf(...casters)
  • value(value)
  • values(...values)

Date Casters

  • date: Parses Date, string, or number into a JavaScript Date.
  • date.iso: Parses ISO-like date strings.
  • dateTimeStamp.unix: Parses a Unix timestamp in seconds.
  • dateTimeStamp.js: Parses a JavaScript timestamp in milliseconds.

JSON Casters

  • json: Parses any valid JSON string.
  • json.object: Parses JSON and validates that the result is an object.
  • json.struct(casters, name?): Parses JSON and validates a structured object.
  • json.array(caster, name?): Parses JSON and validates an array.

Text Casters

  • text.int
  • text.number
  • text.bool
  • possibleText.int
  • possibleText.number
  • possibleText.bool

Environment Casters

Exported from castage/envs. See src/envs/README.md.

PKI Casters

Exported from castage/pki. See src/pki/README.md.

CastingError

interface CastingError extends Error {
  code: CastingErrorCode;
  path: string[];
  extra: {
    expected: string;
    received?: unknown;
    causes?: CastingError[];
    reason?: string;
  };
}