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

jitype

v0.1.0

Published

Tiny, fast runtime validation library. Schemas compile to straight-line JavaScript.

Readme

jitype

A tiny, fast runtime validation library for TypeScript. Schemas compile to straight-line JavaScript at first use — no interpretation overhead, no intermediate objects, just typeof checks and property access.

Why

Sometimes you don't need a full ORM — a raw database client (like Bun's built-in SQLite) is enough. But going raw means losing type safety. TypeScript casts don't verify anything at runtime, and validation libraries like Zod have measurable interpreter overhead on every parse call.

jitype closes that gap. Define a schema once, compile it once, and get a validator that runs as fast as hand-written typeof checks. Zero dependencies, ~600 lines, and full TypeScript inference.

Install

bun add jitype

Quick start

import { obj, str, int, bool, arr, parse, type Infer } from "jitype";

const User = obj({
  id: int,
  name: str,
  email: str,
  active: bool,
  tags: arr(str),
});

type User = Infer<typeof User>;
// { id: number; name: string; email: string; active: boolean; tags: string[] }

const user = parse(User, data); // throws on invalid input, returns typed value

Benchmarks

Compared to Zod v4 on Apple M4 Pro (Bun 1.3.8):

| Benchmark | jitype | Zod | Speedup | |---|---|---|---| | Flat object (4 fields) | 0.06 ns | 40.03 ns | ~625x | | Nested object | 1.12 ns | 102.00 ns | ~91x | | Wide object (10 fields) | 0.13 ns | 94.75 ns | ~710x | | Array of 100 objects | 168 ns | 3,550 ns | ~21x | | Maybe fields | 4.50 ns | 44.87 ns | ~10x | | Tuple (3 elements) | 2.88 ns | 64.51 ns | ~22x | | Enum/oneOf | 1.75 ns | 43.60 ns | ~25x | | Union (string) | 1.73 ns | 21.80 ns | ~13x | | Union (number) | 1.80 ns | 31.52 ns | ~18x |

Run bun bench.ts to reproduce.

How it works

Schemas are plain descriptor objects — they don't do anything on their own. When you call compile() (or parse(), which calls it internally), the library walks the schema tree and generates a JavaScript function body using new Function(). The result is a validator with zero interpretation overhead:

// This schema:
const s = obj({ name: str, age: int });

// Compiles roughly to:
function(input) {
  if (typeof input !== 'object' || input === null || Array.isArray(input))
    throw 'expected object';
  if (typeof input["name"] !== 'string')
    throw 'name: expected string';
  if (typeof input["age"] !== 'number' || !Number.isInteger(input["age"]))
    throw 'age: expected integer';
  return input;
}

Compiled validators are cached (via WeakMap) so the codegen cost is paid only once per schema.

API

Primitives

| Schema | Validates | TypeScript type | |---|---|---| | str | typeof === 'string' | string | | num | typeof === 'number' and Number.isFinite | number | | int | typeof === 'number' and Number.isInteger | number | | bool | typeof === 'boolean' | boolean | | bigint | typeof === 'bigint' | bigint | | bytes | instanceof Uint8Array | Uint8Array | | nil | === null | null | | undef | === undefined | undefined | | any | passthrough (no validation) | unknown |

Literals and enums

literal("active")           // Schema<"active">
literal(42)                 // Schema<42>
literal(true)               // Schema<true>
literal(null)               // Schema<null>

oneOf("a", "b", "c")       // Schema<"a" | "b" | "c">
oneOf(1, 2, 3)              // Schema<1 | 2 | 3>

Containers

arr(str)                    // Schema<string[]>
obj({ name: str, age: int }) // Schema<{ name: string; age: number }>
tuple(str, int, bool)       // Schema<[string, number, boolean]>
record(num)                 // Schema<Record<string, number>>

Wrappers

maybe(str)                  // Schema<string | undefined>  — accepts null or undefined, normalizes to undefined
withDefault(str, "anon")    // Schema<string>  — replaces null/undefined with default

Object utilities

const User = obj({ id: int, name: str, email: str });

extend(User, { role: str })     // { id, name, email, role }
pick(User, "id", "name")       // { id, name }
omit(User, "id")               // { name, email }
partial(User)                   // { id?, name?, email? }

// Compose them:
partial(omit(User, "id"))       // { name?, email? } — useful for update payloads

Combinators

// Union — tries schemas in order. Optimizes to a typeof switch for primitives,
// a discriminant switch for tagged objects, or falls back to try/catch.
union(str, num, bool)

// Discriminated object unions are auto-detected:
union(
  obj({ type: literal("a"), val: str }),
  obj({ type: literal("b"), val: num }),
)
// Compiles to: switch(input.type) { case "a": ...; case "b": ... }

// Lazy — for recursive types
type Tree = { value: number; children: Tree[] };
const tree: Schema<Tree> = obj({
  value: num,
  children: arr(lazy(() => tree)),
});

// Refine — custom predicates
const positive = refine(num, (n) => n > 0, "expected positive number");
const email = refine(str, (s) => s.includes("@"), "expected email");

// Chained refinements
const evenPositive = refine(
  refine(int, (n) => n > 0, "expected positive"),
  (n) => n % 2 === 0,
  "expected even",
);

Transforms

// Preprocess — coerce input before validation
const coercedInt = preprocess(int, (v) => Number(v));
parse(coercedInt, "42"); // 42

const trimmed = preprocess(
  refine(str, (s) => s.length > 0, "required"),
  (v) => typeof v === "string" ? v.trim() : v,
);

// Transform — change the value after validation
const upper = transform(str, (s) => s.toUpperCase());
parse(upper, "hello"); // "HELLO"

// JSON — validate a JSON string against a schema
const payload = json(obj({ id: int }));
parse(payload, '{"id":1}'); // validates, returns original string

Compiler

// compile() returns a reusable validator function (cached per schema object)
const validate = compile(User);
validate(data); // throws on invalid, returns typed value

// parse() is shorthand for compile(schema)(input)
parse(User, data);

Type utilities

import type { Schema, Infer, InferObject } from "jitype";

type User = Infer<typeof User>;
// Extracts the TypeScript type from any Schema<T>

Error messages

Errors are thrown as strings with dot-path and bracket notation:

expected string
name: expected string
user.profile.age: expected integer
items[2]: expected string
items[1].id: expected integer
data[0].pair[1]: expected integer
[1][b]: expected finite number

Real-world example: Bun SQLite

import { Database } from "bun:sqlite";
import { obj, str, int, maybe, compile } from "jitype";

const db = new Database("app.db");

const UserRow = obj({
  id: int,
  name: str,
  email: str,
  bio: maybe(str),
});

const validateUser = compile(UserRow);

function getUser(id: number) {
  const row = db.query("SELECT id, name, email, bio FROM users WHERE id = ?").get(id);
  return validateUser(row); // runtime validated + fully typed
}

License

MIT