Skip to content

Validation

shorn runs your schema both ways: encode validates before writing bytes, decode validates the structurally-decoded value before returning it.

const bytes = encode(Person, person); // validates, then writes
const back = decode(Person, bytes); // reads, then validates

These checks are not redundant. The wire format knows that a field is a string; only your schema knows that it must be a non-empty email address under 64 characters.

ErrorThrown when
EncodeErrorvalidation failed on the way in, or the schema cannot be encoded
DecodeErrorthe bytes are malformed, or validation failed on the way out

DecodeError.offset is the byte position reached. See Errors.

const result = safeDecode(Person, bytes);
if (result.success) result.data; // typed
else result.error; // always an Error, non-Error throws are wrapped

Use the safe variants at boundaries where malformed input is expected rather than exceptional.

A schema with an async refinement cannot use encode/decode — they throw and say so.

const bytes = await encodeAsync(Person, person);
const back = await decodeAsync(Person, bytes);

Both take the Standard Schema, never a codec. There are no safe async variants — use try/catch.

fingerprinted() produces a codec, and no async entry point accepts one. A schema with an async refinement must choose.

The reason is bundle size: class methods do not tree-shake.

import setminifiedgzip
codec17,3895,383
+ m17,1475,314
+ safeEncode / safeDecode17,4355,382
+ encodeAsync / decodeAsync17,7485,456
+ fingerprinted18,9465,854
everything19,4466,023

Async support adds 313 minified and 74 gzip bytes, and only users who import it pay that cost. Moving async methods onto Schema would add those bytes for everyone, including users of only the low-level m API. See ADR 0001.

Workaround: carry codec.fingerprintHex separately, such as in a Kafka header or database column. Keep the payload bare and run async validation through the schema.

SituationUse
Ordinary codeencode / decode
Untrusted inputsafeEncode / safeDecode
Async refinementencodeAsync / decodeAsync
A codec to pass aroundcompile
Stored, queued, version-crossingfingerprinted(compile(schema))

All five use the same structural decode path through Schema.decode, so they report the same errors. test/fuzz.test.ts verifies this because a previous private path skipped the Uint8Array check and leaked a raw TypeError for invalid input.