Skip to content
ChiChuRitaPublic

About

Compact binary serialization for Zod, Valibot, and ArkType

Topics

Resources

Stars

23 stars

Watchers

0 watching

Forks

Repository files navigation

{"name":"Grace","age":45,"role":"admin"} is 40 bytes as JSON, 20 as a positional array, and 8 bytes through shorn: 2d is age 45, 05 is the string length, 47 to 65 is Grace, 00 is the enum index of "admin"

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 version CI status bundle size, 6.48 kB gzip MIT license

Installation

npm install @chichurita/shorn zod

Works in Node 20 or newer, Bun, Deno, browsers, and workers.

Encode a value

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.

Where the bytes go

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.

Store and queue safely

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

Use another validator

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);

Scope

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.

Documentation

Getting started, API reference, byte layout, supported types, rejected shapes, fingerprinting, performance.

MIT licensed.

About

Compact binary serialization for Zod, Valibot, and ArkType

Topics

Resources

Stars

23 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages