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

@metreeca/type

v0.1.2

Published

A lightweight TypeScript library for composable runtime type validation.

Readme

@metreeca/type

npm

A lightweight TypeScript library for composable runtime type validation.

@metreeca/type provides an idiomatic, easy-to-use functional API for validating unknown data against type definitions using guards. Guards validate type structure (for instance, that radius is a number) while leaving semantic constraints (for instance, that radius >= 0) to application logic. Key features include:

  • Structural validation › Focus on type structure for clean separation of concerns
  • Type safety › Seamless type inference and automatic type narrowing
  • Composable guards › Build complex validators from simple primitives
  • Union/intersection support › Handle discriminated unions and type intersections
  • Recursive types › Define self-referencing and mutually recursive structures

Installation

npm install @metreeca/type

[!WARNING]

TypeScript consumers must use "moduleResolution": "nodenext"/"node16"/"bundler" in tsconfig.json. The legacy "node" resolver is not supported.

Usage

[!NOTE]

This section introduces essential concepts and common patterns: see the API reference for complete coverage.

A guard is a function that takes an unknown value and returns an empty string on success or an error message on failure. Guards perform structural validation only: they validate that a value has the expected type structure, not semantic constraints like radius >= 0.

@metreeca/type provides three main abstractions built around guards:

  • Guards: Functions that validate values against primitive types, arrays, and objects
  • Assertions: Functions that apply guards to produce type predicates and validating casts
  • Combinators: Functions that combine multiple guards

Defining Guards

Define guards as functions returning a Trace.

import { anObject, aString, aNumber, type Trace } from "@metreeca/type";

interface User {
    readonly name: string;
    readonly age: number;
}

function aUser(value: unknown): Trace<User> {
    return anObject(value, {
        name: aString,
        age: aNumber
    });
}

Using Assertions

Use is() for conditional narrowing, or as() for validating casts.

import { is, as } from "@metreeca/type";

function processUser(value: unknown): string {
    if (is(aUser, value)) {
        return value.name;  // type narrowed to User
    } else {
        return "unknown";
    }
}

function castUser(value: unknown): User {
    return as(aUser, value);  // throws TypeError on invalid input
}

[!CAUTION]

Circular references are not supported. Validating objects with cycles causes stack overflow.

Primitives

Guards for primitive types cover common JavaScript value types.

import { anObject, aNull, aBoolean, aNumber, aString, aFunction, type Trace } from "@metreeca/type";

interface Profile {
    readonly avatar: null;
    readonly verified: boolean;
    readonly age: number;
    readonly name: string;
    readonly onClick: Function;
}

function aProfile(value: unknown): Trace<Profile> {
    return anObject(value, {
        avatar: aNull,
        verified: aBoolean,
        age: aNumber,
        name: aString,
        onClick: aFunction
    });
}

Arrays

Use anArray() without an element guard to accept any array, or with an element guard to validate each item.

import { anObject, anArray, aNumber, type Trace } from "@metreeca/type";

interface Playlist {
    readonly tracks: readonly unknown[];
    readonly ratings: readonly number[];
}

function aPlaylist(value: unknown): Trace<Playlist> {
    return anObject(value, {
        tracks: anArray,                      // any array
        ratings: v => anArray(v, aNumber)     // array of numbers
    });
}

Objects

Use anObject() without a schema to accept any plain object.

import { anObject, type Trace } from "@metreeca/type";

interface Headers {
    readonly [key: string]: unknown;
}

function aHeaders(value: unknown): Trace<Headers> {
    return anObject(value);  // any plain object
}

Add key and value guards for uniform validation of all entries.

import { anObject, aString, type Trace } from "@metreeca/type";
import { isTag, type Tag } from "./tags";

interface Dictionary {
    readonly [tag: Tag]: string;
}

function aDictionary(value: unknown): Trace<Dictionary> {
    return anObject(value,
        (v: unknown) => isTag(v) ? "" : `malformed tag <${v}>`,
        aString
    );
}

[!NOTE] Validating Tag with a regex is structural type validation: the pattern defines what a Tag is, not a semantic constraint on strings.

Add a schema to create a closed object: only schema properties are allowed, extra properties are rejected.

import { anObject, aString, aNumber, type Trace } from "@metreeca/type";

interface Package {
    readonly name: string;
    readonly version: number;
}

function aPackage(value: unknown): Trace<Package> {
    return anObject(value, {
        name: aString,
        version: aNumber
    });  // closed: extra properties rejected
}

Add a rest guard to create an open object: extra properties are validated by the rest guard.

import { anObject, aString, aNumber, anUnknown, type Trace } from "@metreeca/type";

interface Options {
    readonly name: string;
    readonly version: number;
    readonly [key: string]: unknown;
}

function anOptions(value: unknown): Trace<Options> {
    return anObject(value, {
        name: aString,
        version: aNumber
    }, anUnknown);  // open: extra properties allowed
}

Unions

Use aUnion() to validate a value against guards disjunctively. Validation succeeds if any guard passes. Use literal values in schemas to create discriminated unions.

import { aUnion, anObject, aString, type Trace } from "@metreeca/type";

type Result = Success | Failure;

interface Success {
    readonly ok: true;
    readonly value: string;
}

interface Failure {
    readonly ok: false;
    readonly error: string;
}

function aResult(value: unknown): Trace<Result> {
    return aUnion(value, {
        aSuccess,
        aFailure
    });
}

function aSuccess(value: unknown): Trace<Success> {
    return anObject(value, {
        ok: true,       // literal value match
        value: aString
    });
}

function aFailure(value: unknown): Trace<Failure> {
    return anObject(value, {
        ok: false,
        error: aString
    });
}

Intersections

Use anIntersection() to validate a value against multiple guards conjunctively. All guards must pass for validation to succeed.

import { anIntersection, anObject, aString, aBoolean, type Trace } from "@metreeca/type";

interface Identifiable {
    readonly id: string;
}

interface Trackable {
    readonly active: boolean;
}

type Device = Identifiable & Trackable;

function aDevice(value: unknown): Trace<Device> {
    return anIntersection(value, [
        v => anObject(v, { id: aString }),
        v => anObject(v, { active: aBoolean })
    ]);
}

Recursion

Define self-referencing types by wrapping guard calls in lambdas.

import { anObject, anArray, aNumber, type Trace } from "@metreeca/type";

interface Node {
    readonly value: number;
    readonly children: readonly Node[];
}

function aNode(value: unknown): Trace<Node> {
    return anObject(value, {
        value: aNumber,
        children: v => anArray(v, aNode)  // self-reference via lambda
    });
}

Putting It All Together

A realistic example combining multiple patterns from previous sections.

import { aUnion, anObject, anArray, aNumber, type Trace } from "@metreeca/type";

type Shape =
    | Circle
    | Rectangle
    | Composite;

interface Circle {
    readonly type: "circle";
    readonly x: number;
    readonly y: number;
    readonly radius: number;
}

interface Rectangle {
    readonly type: "rectangle";
    readonly x: number;
    readonly y: number;
    readonly width: number;
    readonly height: number;
}

interface Composite {
    readonly type: "composite";
    readonly shapes: readonly Shape[];
}

function aShape(value: unknown): Trace<Shape> {
    return aUnion(value, {
        aCircle,
        aRectangle,
        aComposite
    });
}

function aCircle(value: unknown): Trace<Circle> {
    return anObject(value, {
        type: "circle",
        x: aNumber,
        y: aNumber,
        radius: aNumber
    });
}

function aRectangle(value: unknown): Trace<Rectangle> {
    return anObject(value, {
        type: "rectangle",
        x: aNumber,
        y: aNumber,
        width: aNumber,
        height: aNumber
    });
}

function aComposite(value: unknown): Trace<Composite> {
    return anObject(value, {
        type: "composite",
        shapes: v => anArray(v, aShape)  // recursive reference
    });
}

Support

  • Open an issue to report a problem or to suggest a new feature
  • Start a discussion to ask a how-to question or to share an idea

License

This project is licensed under the Apache 2.0 License – see LICENSE file for details.