Introduction
shorn turns the validation schema you already have into a binary format. It reads two things from that schema: Standard Schema for validation, and Standard JSON Schema for structure.
Both are small shared interfaces that Zod, Valibot, and ArkType already implement, so shorn needs no adapter for any of them, nor for the next validator that implements the two. Because your validator already describes every field and its type, there is no separate schema file to write, no code to generate, and no second copy of your types to keep in sync.
import { z } from "zod";import { decode, encode } from "@chichurita/shorn";
const Person = z.object({ name: z.string(), age: z.int().nonnegative(), sex: z.enum(["M", "F", "X"]),});
const bytes = encode(Person, { name: "Grace", age: 45, sex: "F" }); // 8 bytesconst decoded = decode(Person, bytes);As minified JSON, that value is 35 bytes. shorn writes 8, because the field names and type markers stay in the schema instead of being repeated in every payload. Where the bytes go walks from 35 down to 8 in three steps.
In every case:
- The same schema written in Zod, Valibot, or ArkType produces the same bytes.
- Your validator runs before encoding and again after decoding, so a payload never skips your rules.
- The runtime is small. Helpers you do not import cost nothing in your bundle.
- MIT licensed.
When it fits
Section titled “When it fits”shorn is a good fit when all of these are true: both ends of the wire are TypeScript or JavaScript, your application already validates its data, both ends can share one schema, and payload size or serialization cost matters to you. If any of those is false, Comparisons says what to use instead.
What shorn does not do
Section titled “What shorn does not do”Some of these are design decisions rather than gaps a later release will fill:
- No streaming: nor random access or zero-copy views.
- No cross-language decoder: TypeScript and JavaScript only.
- Some values have no wire form:
Date,bigint,Map,Setanddate-timestrings are supported natively.undefined, symbols,RegExp, class instances and one-way transforms are not, so convert those before encoding. - Not confidential: the bytes are compact, not secret. Encrypt them when secrecy matters.
- No universal speed guarantee: results depend on your schema, your data, the runtime, and compression. See Throughput and measure your own workload.
- No schema evolution: only the exact wire shape that wrote a payload can decode it.
fingerprinted()puts a short identifier for the wire shape in front of the payload, so it catches most mismatches instead of misreading them, but nothing migrates old payloads for you.
Where to go next
Section titled “Where to go next”If you are still deciding, read Comparisons first. Otherwise: Installation, then Quick start, then How it works for the model behind the bytes.
From there, Using payloads covers sending and storing them, and Wire fingerprints is worth reading before you store any.