Using payloads
Every example on this page uses one schema and one value:
import { z } from "zod";import { compile, encode, fingerprinted, safeDecode } from "@chichurita/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;Send over HTTP
Section titled “Send over HTTP”Send the bytes as the request body with a binary content type:
const body = encode(Person, person);
await fetch("/people", { method: "POST", headers: { "Content-Type": "application/octet-stream" }, body,});On the receiving side, read the body as an ArrayBuffer and wrap it in a Uint8Array. Use safeDecode when malformed input is something you expect to see, so a bad request becomes a result instead of an exception:
const bytes = new Uint8Array(await request.arrayBuffer());const result = safeDecode(Person, bytes);Store or queue payloads
Section titled “Store or queue payloads”Wrap the codec in fingerprinted() before anything is stored:
const StoredPerson = fingerprinted(compile(Person), { bytes: 4 });await queue.send(StoredPerson.encode(person));Keep every old codec for as long as payloads written by it still exist. The fingerprint covers the wire shape but not validation rules or conversion functions. If those need versioning too, store an application version in a header or a database column.
Frame multiple values
Section titled “Frame multiple values”A shorn payload has no overall length prefix, and any bytes left over after a value cause an error. So do not concatenate payloads and expect decode to find where one ends and the next begins. Send one value per transport message, or write your own length prefix in front of each value when several go into one stream or file.
Encryption and authentication
Section titled “Encryption and authentication”Compact is not encrypted, and a fingerprint is not an authentication tag. Use your transport’s normal encryption and authentication. See Hostile input for what the decoder checks on its own.