Tiny, fast runtime validation library. Schemas compile to straight-line JavaScript.
npm install jitypeA 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.
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.
``bash`
bun add jitype
`ts
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
// { id: number; name: string; email: string; active: boolean; tags: string[] }
const user = parse(User, data); // throws on invalid input, returns typed value
`
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.
Schemas are plain descriptor objects — they don't do anything on their own.
When you call compile() (or parse(), which calls it internally), the librarynew
walks the schema tree and generates a JavaScript function body using
Function(). The result is a validator with zero interpretation overhead:
`ts
// 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.
| 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 |
`ts
literal("active") // Schema<"active">
literal(42) // Schema<42>
literal(true) // Schema
literal(null) // Schema
oneOf("a", "b", "c") // Schema<"a" | "b" | "c">
oneOf(1, 2, 3) // Schema<1 | 2 | 3>
`
`ts`
arr(str) // Schema
obj({ name: str, age: int }) // Schema<{ name: string; age: number }>
tuple(str, int, bool) // Schema<[string, number, boolean]>
record(num) // Schema
`ts`
maybe(str) // Schema
withDefault(str, "anon") // Schema
`ts
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
`
`ts
// 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
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",
);
`
`ts
// 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
`
`ts
// 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);
`
`ts
import type { Schema, Infer, InferObject } from "jitype";
type User = Infer
// Extracts the TypeScript type from any Schema
`
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
`ts
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
}
``
MIT