Quick Start
1. The functional API
Section titled “1. The functional API”import { z } from "zod";import { decode, encode } from "shorn";
const Person = z.object({ name: z.string(), age: z.int().nonnegative(), sex: z.enum(["M", "F", "X"]),});
const person = { name: "Grace", age: 45, sex: "F" } as const;
const bytes = encode(Person, person);// Uint8Array(8) [45, 5, 71, 114, 97, 99, 101, 0]
const back = decode(Person, bytes);The complete record is eight bytes. 45 is the age. 5 is the byte length of "Grace", followed by its five ASCII bytes. 0 is the index of "F" in the sorted enum. Byte Layout explains why age comes first.
The wire plan is cached by schema identity: 3.53M encodes/s against 3.49M for compiled.encode — noise.
2. The compiled API
Section titled “2. The compiled API”import { compile } from "shorn";
const PersonWire = compile(Person);PersonWire.encode(person);PersonWire.decode(bytes);Use this form when you want a codec object to pass around. It is not faster than the functional API — both build and cache the same codec at runtime, with no build step and no generated file on disk.
3. Schema identity
Section titled “3. Schema identity”Bare payloads contain no schema identifier. If you decode them with the wrong schema, only 58% are rejected in the mismatch test. About 27% decode to the wrong value without an error.
import { fingerprinted } from "shorn";
const PersonWire = fingerprinted(compile(Person));
const bytes = PersonWire.encode(person); // 11 bytes: 3 fingerprint + 8 payloadPersonWire.decode(bytes); // throws if the schema changedUse fingerprinted for anything stored, queued, or sent across a version boundary. Use bare payloads only when both endpoints are pinned to one schema at deploy time. See Fingerprinting for the tradeoff.
Errors without exceptions
Section titled “Errors without exceptions”const result = safeDecode(Person, bytes);if (!result.success) return new Response("Bad request", { status: 400 });result.data; // typedValibot takes one extra argument
Section titled “Valibot takes one extra argument”import * as v from "valibot";import { toStandardJsonSchema } from "@valibot/to-json-schema";
const PersonWire = compile(Person, { structure: toStandardJsonSchema(Person) });Zod 4.2+ and ArkType 2.1.28+ need no second argument. See Valibot.