ChiChuRita/shorn(新しいタブで開きます)
主要言語: TypeScript
Compact binary serialization for Zod, Valibot, and ArkType
- star 数
- 23
- watcher 数
- 1
- fork 数
- 0
- issue 数
- 0
- 最終更新
- 2026/11/01
主要言語: TypeScript
Compact binary serialization for Zod, Valibot, and ArkType
Your Zod, Valibot, or ArkType schema is already a binary codec.
No schema file, no code generation, no second copy of your types to keep in sync.
npm install @chichurita/shorn zod
Works in Node 20 or newer, Bun, Deno, browsers, and workers.
import { z } from "zod"; import { decode, encode } from "@chichurita/shorn"; const Person = z.object({ name: z.string(), age: z.int().nonnegative(), role: z.enum(["viewer", "editor", "admin"]), }); const person = { name: "Grace", age: 45, role: "admin" } as const; const bytes = encode(Person, person); // Uint8Array(8) const back = decode(Person, bytes); // typed and validated
shorn runs your validator before it writes the bytes, and again after it reads them back. The same schema written in Zod, Valibot, or ArkType produces the same bytes.
JSON spends most of its bytes on things both sides already know: field names, quotes, brackets, and commas. Your schema holds all of that, so shorn leaves it out and writes only the values. An enum member becomes a small index instead of a string. The picture above shows the result, and how it works walks through the eight bytes one at a time.
A bare payload does not say which schema wrote it. If the bytes will sit in a database, a queue, or a file, or cross a deployment boundary, add a fingerprint. shorn then rejects a mismatch instead of decoding it into a wrong value:
import { compile, fingerprinted } from "@chichurita/shorn"; const PersonWire = fingerprinted(compile(Person), { bytes: 4 }); const bytes = PersonWire.encode(person); // 4-byte fingerprint + payload PersonWire.decode(bytes); // rejects a different wire shape
Zod 4.2 or newer and ArkType 2.1.28 or newer work as they are: pass the schema. Valibot 1.5 or newer keeps its JSON Schema conversion in a separate package, @valibot/to-json-schema 1.8 or newer, so create the structure once with toStandardJsonSchema(schema), next to the schema, and pass it as the last argument. shorn reads validation through Standard Schema and structure through Standard JSON Schema.
Valibot's wrapper takes no options, so for Date, bigint, Map and Set use the raw converter together with valibotOverride:
import { toJsonSchema } from "@valibot/to-json-schema"; import { compile, valibotOverride } from "@chichurita/shorn"; const structure = toJsonSchema(schema, { overrideSchema: valibotOverride(toJsonSchema) }); const codec = compile(schema, structure);
shorn encodes strings, booleans, integers, floats, literals, enums, nullable values, arrays, tuples, records, recursive schemas, z.any(), and objects, closed or open, with optional fields. Unions need a literal tag in every branch, or branches that share no JSON type. A union of literals is an enum. Date, bigint, Map, and Set are supported natively.
Not supported: overlapping unions, one-way transforms, streaming, and schema migration. undefined, symbols, RegExp, and class instances have no wire form, so convert those first.
Against JSON bytes, encoding is up to 6.7× faster and decoding up to 13.0×, with no compressor involved. The m API is 6.48 KB gzip; compile with validation is 12.64 KB.
Getting started, API reference, byte layout, supported types, rejected shapes, fingerprinting, performance.
MIT licensed.