Validation (Standard Schema & JSON Schema)
Every codec — Timestamp, Reverse Timestamp, Signed Timestamp, Opaque Timestamp, Wrapped key, and Digest — exposes two shared validation surfaces:
~standard— Standard Schema v1 support, letting any codec drop directly into ArkType, Valibot, tRPC, and other Standard Schema-compatible libraries without hand-rolling arefinecallback.toJsonSchema()— synchronous JSON Schema export for OpenAPI document generation, JSON Schema validators, and documentation tooling.
Both surfaces work on the wire form only — they validate prefix and base32 shape, require no key material, and are fully synchronous on every codec, including keyed ones (Opaque Timestamp, Wrapped key, Signed Timestamp, Digest) where key-dependent operations are async.
Standard Schema (~standard)
Section titled “Standard Schema (~standard)”Each codec’s ~standard property implements
Standard Schema v1:
codec["~standard"].version; // 1codec["~standard"].vendor; // "@smonn/ids"codec["~standard"].validate(value);// → { value: Id<Brand> } on success// → { issues: Array<{ message: string }> } on failure| Property | Value | Notes |
|---|---|---|
version |
1 |
Fixed — Standard Schema protocol version |
vendor |
"@smonn/ids" |
Fixed — identifies this library |
validate |
(unknown) => ... |
Sync; wraps safeParse; returns canonical ID |
validate return types
Section titled “validate return types”| Outcome | Return shape |
|---|---|
| Valid | { value: Id<Brand> } (issues is undefined) |
| Invalid | { issues: [{ message: string }, ...] } |
validate accepts the same mixed-case and Crockford visual aliases
(o → 0, i → 1, l → 1) that safeParse does, and returns the
canonical Id<Brand> on success. Each failure maps to a distinct message:
| ParseError | issues[0].message |
|---|---|
not_string |
"expected string" |
invalid_prefix |
"expected prefix 'usr_'" |
invalid_base32 |
"invalid base32 payload" |
Worked example — ArkType
Section titled “Worked example — ArkType”Any Standard Schema v1-compatible library accepts a codec directly as a schema member. For example, with ArkType:
import { type } from "arktype";import { createTimestampId } from "@smonn/ids";
const users = createTimestampId("usr");
const Body = type({ userId: users });
const r = Body({ userId: "USR_06F80Z92D2DBSQQG28T5CY4TQG" });// → { userId: "usr_06f80z92d2dbsqqg28t5cy4tqg" } typed as { userId: Id<"usr"> }The same pattern works with any library that consumes a Standard Schema validator — tRPC, oRPC, TanStack, and others. Libraries that only produce Standard Schema (Zod, Valibot) need a small wrapper instead. See Framework integrations for a per-library guide.
Direct use
Section titled “Direct use”If a library does not yet support Standard Schema, call validate directly:
const result = users["~standard"].validate(req.body.userId);
if ("issues" in result) { return res.status(400).json({ error: result.issues[0].message });}
const userId = result.value; // Id<"usr">, canonicalJSON Schema (toJsonSchema())
Section titled “JSON Schema (toJsonSchema())”toJsonSchema() returns a plain object describing the canonical wire form:
users.toJsonSchema();// {// type: "string",// pattern: "^usr_[0-9a-hjkmnp-tv-z]{25}[048cgmrw]$",// description: "Branded ID for 'usr'",// example: "usr_00000000000000000000000000",// }Output shape
Section titled “Output shape”| Field | Type | Notes |
|---|---|---|
type |
string |
Always "string" |
pattern |
string |
Anchored regex matching the canonical form only — see note below |
description |
string |
"Branded ID for '<brand>'" — includes the brand literal |
example |
string |
A deterministic structural placeholder — stable across calls |
pattern is canonical-form-only
Section titled “pattern is canonical-form-only”The regex matches generate() output and is() — not the lenient forms
accepted by safeParse(). Uppercase letters and Crockford visual aliases
(O, I, L) do not match. The final character is constrained to one of
[048cgmrw] because 16 bytes encoded in 26 Crockford base32 characters
(130 bits) leave 2 surplus padding bits; canonical encoding sets them to zero.
See ADR-0003.
example is a deterministic placeholder
Section titled “example is a deterministic placeholder”example is a deterministic structural placeholder (prefix + "0".repeat(26))
computed once at codec construction — stable across repeated toJsonSchema()
calls. It always satisfies the returned pattern.
OpenAPI usage
Section titled “OpenAPI usage”Drop the result straight into an OpenAPI components.schemas entry:
const schema = users.toJsonSchema();
// in your OpenAPI document builder:components.schemas.UserId = schema;// → { type: "string", pattern: "^usr_...$", description: "...", example: "..." }Availability on keyed codecs
Section titled “Availability on keyed codecs”Both surfaces are available — and synchronous — on every codec variant:
| Codec | ~standard |
toJsonSchema() |
Notes |
|---|---|---|---|
| Timestamp | sync | sync | |
| Reverse Timestamp | sync | sync | |
| Signed Timestamp | sync | sync | generate / verify are async; these two are not |
| Opaque Timestamp | sync | sync | generate / extractTimestamp are async |
| Wrapped key | sync | sync | wrap / unwrap are async |
| Digest | sync | sync | digest is async |
Neither surface reads the payload — they validate prefix and base32 structure only — so no key is needed and there is nothing to await.