@vielzeug/codex 3.0.4 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,49 @@
1
+ {
2
+ "apiSource": "// Public root surface of @vielzeug/sigil. Every public export goes through this file.\nexport { drawToCanvas } from './canvas';\nexport { encodeQr, qrCapacity } from './encode';\nexport {\n SigilCapacityError,\n SigilDisposedError,\n SigilError,\n SigilOptionError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from './errors';\nexport { createQrScanner, detectQr, isQrScanSupported, qrScanSupport } from './scan';\nexport { toSvg } from './svg';\nexport type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';\n",
3
+ "docs": {
4
+ "index": "---\ntitle: Sigil — QR code generation and scanning\ndescription: \"QR code generation and scanning — pure matrix encoder, SVG and canvas renderers, native BarcodeDetector scanning\"\npackage: sigil\ncategory: utilities\nkeywords: [qr, qrcode, barcode, encoder, scanner, svg, canvas, barcodedetector, pairing]\nexports: [encodeQr, toSvg, createQrScanner, qrCapacity, detectQr]\nrelated: [mesh, refine]\nenvironments: [browser, node]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"sigil\" />\n\n## Why Sigil?\n\nQR workflows usually pull in a general-purpose barcode library that renders to canvas and guesses at colors, or a from-scratch decoder you do not need. Sigil is a pure, zero-dependency encoder (numeric, alphanumeric, and byte modes; versions 1–40; all four error-correction levels; automatic mask selection) with renderers that stay out of the way — an SVG string that themes with CSS, and a canvas painter for print/bitmap flows. Scanning is a thin lifecycle wrapper over the platform's native `BarcodeDetector`, so there is no decoder weight and no polyfill to ship.\n\n```ts\n// Before — a QR library per concern, canvas-only output, hardcoded colors\nimport QRCode from 'qrcode';\nconst dataUrl = await QRCode.toDataURL(text, { width: 256, color: { dark: '#000' } });\nimg.src = dataUrl; // raster, fixed colors, no theme support\n\n// After — encode once, render anywhere\nimport { encodeQr, toSvg } from '@vielzeug/sigil';\nconst matrix = encodeQr(text); // smallest version that fits, auto mask\nelement.innerHTML = toSvg(matrix); // currentColor modules — themes with CSS\n```\n\n| Feature | Sigil | `qrcode` (node-style) | `jsqr` |\n| --- | --- | --- | --- |\n| Bundle size | <PackageInfo package=\"sigil\" type=\"size\" /> | ~100 kB+ | decoder only |\n| Runtime dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> none | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> none |\n| Output | Matrix, SVG string, canvas | Canvas/DataURL/UTF-8 | — |\n| Themeable output | `currentColor` by default | Hardcoded color strings | — |\n| Scanning | Native `BarcodeDetector` wrapper | <ore-icon name=\"x\" size=\"16\"></ore-icon> | Pure-JS decoder |\n| SSR-safe import | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Sigil when** you need QR output that themes with your UI (SVG/`currentColor`), a minimal encoder with no runtime dependencies, or a scanner that uses the platform decoder instead of shipping one.\n\n**Consider `jsqr`-style decoding when** you must scan in browsers without `BarcodeDetector` — sigil reports `SigilUnsupportedError` rather than bundling a fallback decoder.\n\n</div>\n\n## Installation\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/sigil\n```\n\n```sh [npm]\nnpm install @vielzeug/sigil\n```\n\n```sh [yarn]\nyarn add @vielzeug/sigil\n```\n\n:::\n\n## Quick Start\n\nEncode to an SVG string — works in Node and the browser:\n\n```ts\nimport { encodeQr, qrCapacity, toSvg } from '@vielzeug/sigil';\n\nconst payload = 'https://vielzeug.dev';\nconst matrix = encodeQr(payload, { errorCorrection: 'M' });\ndocument.querySelector('#qr').innerHTML = toSvg(matrix, { label: 'Vielzeug link' });\n```\n\nScan with the camera (browser only — see support notes in the usage guide):\n\n```ts\nimport { createQrScanner } from '@vielzeug/sigil';\n\nconst video = document.querySelector('video');\nconst scanner = createQrScanner({ video });\nscanner.onResult((result) => {\n console.log('scanned:', result.value); // once: true stops after first decode\n});\nawait scanner.start(); // throws SigilUnsupportedError / SigilPermissionError\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- `encodeQr` — pure matrix encoder: numeric/alphanumeric/byte modes, versions 1–40, EC levels L/M/Q/H, automatic or forced mask\n- `qrCapacity` — byte capacity per version/mode/level, for pre-flight validation\n- `toSvg` — compact path-merged SVG string; `currentColor` modules, `<title>` + `role=\"img\"` built in\n- `drawToCanvas` — bitmap rendering with DPR-aware scaling for print and download flows\n- `detectQr` — one-shot detection on any `ImageBitmapSource`\n- `createQrScanner` — camera scan loop with `once` mode, status events, `tap()` observability, and strict disposal\n- `isQrScanSupported`/`qrScanSupport` — sync and async feature detection that never assumes\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md) — encoding modes, capacity, rendering, scanning, and testing\n- [API Reference](./api.md) — every export, option, type, and error\n- [Examples](./examples.md) — copy-paste recipes, including pairing two devices with `meshQrCodec`\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Mesh](../mesh/) — backendless P2P sessions; `meshQrCodec` produces compact QR-friendly pairing payloads\n- [Refine](../refine/) — `ore-qr-code` and `ore-qr-scanner` wrap sigil in ready-made, themeable components\n\n</div>\n\n<!-- markdownlint-enable -->\n",
5
+ "api": "---\ntitle: Sigil — API Reference\ndescription: Public API of @vielzeug/sigil — encoder, renderers, scanner, types, and errors.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution | Common gotcha |\n| --- | --- | --- | --- |\n| `encodeQr` | Encode a payload into a `QrMatrix` | Sync | Throws `SigilCapacityError` over capacity |\n| `qrCapacity` | Payload limit for a version/level/mode | Sync | Returns *characters* for numeric/alphanumeric, *bytes* for byte mode |\n| `toSvg` | Render a matrix as an SVG string | Sync | Escapes `dark`/`light` as raw CSS — pass values, not markup |\n| `drawToCanvas` | Paint a matrix onto a canvas | Sync | Browser only; resizes the canvas for DPR |\n| `detectQr` | One-shot detection on an image source | Async | Throws `SigilUnsupportedError` without `BarcodeDetector` |\n| `createQrScanner` | Camera scan loop | Async (start) | Must `dispose()`; camera tracks persist otherwise |\n| `isQrScanSupported` | Sync feature check | Sync | Constructor exists ≠ `qr_code` supported — prefer `qrScanSupport()` |\n| `qrScanSupport` | Async feature check incl. formats | Async | Never assume support per browser/version |\n\n## Package Entry Point\n\n| Import | Purpose |\n| --- | --- |\n| `@vielzeug/sigil` | Complete public Sigil API |\n\n## Encoding\n\n### `encodeQr(data, options?)`\n\n```ts\nfunction encodeQr(data: string | Uint8Array, options?: QrEncodeOptions): QrMatrix;\n```\n\nEncodes `data` into a QR symbol. Picks the most compact mode covering the whole input, the smallest fitting version, and the lowest-penalty mask.\n\n| Parameter | Type | Default | Description |\n| --- | --- | --- | --- |\n| `data` | `string \\| Uint8Array` | — | Payload; `Uint8Array` implies byte mode |\n| `options.errorCorrection` | `'L' \\| 'M' \\| 'Q' \\| 'H'` | `'M'` | Error-correction level |\n| `options.version` | `1–40` | auto | Pin the version; throws if payload can't fit |\n| `options.minVersion` | `1–40` | `1` | Smallest acceptable version |\n| `options.mode` | `QrMode` | auto | Force a segment mode |\n| `options.mask` | `0–7` | auto | Force the mask (test vectors only) |\n\n**Returns** a frozen `QrMatrix` with `size`, `version`, `mode`, `errorCorrection`, `mask`, `modules`, and `get(x, y)`.\n\n**Throws** `SigilCapacityError` (payload exceeds capacity), `SigilOptionError` (invalid mode/version/mask combination).\n\n```ts\nimport { encodeQr } from '@vielzeug/sigil';\n\nconst matrix = encodeQr('HELLO WORLD', { errorCorrection: 'Q' });\nmatrix.size; // 21 (v1)\nmatrix.get(0, 0); // finder pattern → true\n```\n\n---\n\n### `qrCapacity(version, errorCorrection, mode)`\n\n```ts\nfunction qrCapacity(version: number, errorCorrection: QrErrorCorrection, mode: QrMode): number;\n```\n\nReturns the payload limit: characters for `numeric`/`alphanumeric`, bytes for `byte`.\n\n```ts\nqrCapacity(1, 'M', 'byte'); // → 14\nqrCapacity(40, 'L', 'numeric'); // → 7089\n```\n\n## Rendering\n\n### `toSvg(matrix, options?)`\n\n```ts\nfunction toSvg(matrix: QrMatrix, options?: QrSvgOptions): string;\n```\n\nReturns a complete `<svg>` string. Runs in any environment — no DOM required.\n\n| Parameter | Type | Default | Description |\n| --- | --- | --- | --- |\n| `options.dark` | `string` | `'currentColor'` | Dark module color |\n| `options.light` | `string` | `'transparent'` | Background color |\n| `options.margin` | `number` | `4` | Quiet-zone modules |\n| `options.scale` | `number` | `1` | User units per module |\n| `options.optimizePath` | `boolean` | `true` | Merge dark runs into one `<path>` |\n| `options.label` | `string` | — | `<title>` + `aria-label`; enables `role=\"img\"` |\n\n```ts\nconst svg = toSvg(matrix, { label: 'Pairing code', dark: 'var(--qr-dark)' });\n```\n\n---\n\n### `drawToCanvas(matrix, canvas, options?)` — browser only\n\n```ts\nfunction drawToCanvas(matrix: QrMatrix, canvas: HTMLCanvasElement, options?: QrCanvasOptions): number;\n```\n\nPaints the matrix and returns the CSS-pixel edge length. Sizes the backing store for `devicePixelRatio` so output stays crisp on retina displays.\n\n| Parameter | Type | Default | Description |\n| --- | --- | --- | --- |\n| `options.scale` | `number` | `1` | CSS px per module |\n| `options.margin` | `number` | `4` | Quiet-zone modules |\n| `options.dark` / `options.light` | `string` | `'#000'` / `'#fff'` | Fill colors |\n\n## Scanning (browser only)\n\n### `createQrScanner(options)`\n\n```ts\nfunction createQrScanner(options: QrScannerOptions): QrScanner;\n```\n\nCreates a camera scan loop around the native `BarcodeDetector`.\n\n| Parameter | Type | Default | Description |\n| --- | --- | --- | --- |\n| `options.video` | `HTMLVideoElement` | — | Element the stream attaches to (needs `autoplay playsinline muted`) |\n| `options.constraints` | `MediaTrackConstraints` | `{ facingMode: 'environment' }` | `getUserMedia` video constraints |\n| `options.intervalMs` | `number` | `200` | Minimum ms between detect passes |\n| `options.once` | `boolean` | `true` | Stop after the first result |\n| `options.signal` | `AbortSignal` | — | Abort → `stop()` |\n| `options.detector` | `QrDetector` | native | Injected detector for tests |\n| `options.mediaDevices` | `Pick<MediaDevices, 'getUserMedia'>` | `navigator.mediaDevices` | Injected media for tests |\n\n**Returns** a `QrScanner`:\n\n| Member | Signature | Description |\n| --- | --- | --- |\n| `start()` | `() => Promise<void>` | Request camera, attach stream, run the detect loop. Rejects with `SigilUnsupportedError` / `SigilPermissionError` |\n| `stop()` | `() => void` | Stop the loop and tracks; instance stays usable |\n| `onResult(handler)` | `(handler) => () => void` | Decode results; returns unsubscribe |\n| `tap(handler, options?)` | `(handler, { signal? }) => () => void` | Observe `SigilEvent`s; handler errors are swallowed |\n| `status` | `QrScannerStatus` | `'idle' \\| 'starting' \\| 'scanning' \\| 'stopped' \\| 'disposed'` |\n| `dispose()` / `[Symbol.dispose]` | `() => void` | Terminal teardown; `disposalSignal` aborts |\n| `disposed` | `boolean` | Whether `dispose()` ran |\n\n```ts\nconst scanner = createQrScanner({ video });\nconst off = scanner.onResult(({ value }) => console.log(value));\nawait scanner.start();\n```\n\n---\n\n### `detectQr(source, options?)`\n\n```ts\nfunction detectQr(\n source: ImageBitmapSource,\n options?: { signal?: AbortSignal; detector?: QrDetector },\n): Promise<QrScanResult | null>;\n```\n\nSingle-pass detection on an image source. Returns the first result's `{ value, cornerPoints }`, or `null` when nothing decodes. Throws `SigilUnsupportedError` when no detector is available.\n\n---\n\n### `isQrScanSupported()` / `qrScanSupport()`\n\n```ts\nfunction isQrScanSupported(): boolean;\nfunction qrScanSupport(): Promise<boolean>;\n```\n\n`isQrScanSupported` checks the `BarcodeDetector` constructor synchronously. `qrScanSupport` additionally asks `getSupportedFormats()` for `'qr_code'` — use it before showing scan affordances.\n\n## Types\n\n```ts\ntype QrErrorCorrection = 'L' | 'M' | 'Q' | 'H';\ntype QrMode = 'numeric' | 'alphanumeric' | 'byte';\ntype QrScannerStatus = 'idle' | 'starting' | 'scanning' | 'stopped' | 'disposed';\n\ninterface QrMatrix {\n readonly errorCorrection: QrErrorCorrection;\n readonly mask: number;\n readonly mode: QrMode;\n readonly modules: ReadonlyArray<ReadonlyArray<boolean>>; // frozen, row-major\n readonly size: number; // 17 + 4 * version\n readonly version: number; // 1–40\n get(x: number, y: number): boolean; // false outside bounds\n}\n\ninterface QrScanResult {\n readonly cornerPoints?: ReadonlyArray<{ readonly x: number; readonly y: number }>;\n readonly value: string;\n}\n\ninterface QrDetector {\n detect(source: ImageBitmapSource): Promise<\n ReadonlyArray<{ rawValue: string; cornerPoints?: ReadonlyArray<{ x: number; y: number }> }>\n >;\n}\n\ntype SigilEvent =\n | { readonly type: 'status-change'; readonly status: QrScannerStatus }\n | { readonly type: 'detect'; readonly value: string; readonly elapsedMs: number }\n | { readonly type: 'frame-skipped'; readonly reason: 'not-ready' | 'busy' }\n | { readonly type: 'error'; readonly error: SigilError }\n | { readonly type: 'dispose' };\n```\n\n`QrEncodeOptions`, `QrSvgOptions`, `QrCanvasOptions`, `QrScannerOptions`, and `QrScanner` are documented under their factories above.\n\n## Errors\n\n| Class | Trigger | Notable members |\n| --- | --- | --- |\n| `SigilError` | Base class for all sigil errors | — |\n| `SigilCapacityError` | Payload exceeds the version/mode limit | `bytes`, `maxBytes`, `version` |\n| `SigilOptionError` | Invalid option combination (forced mode can't represent input, bad mask/version) | — |\n| `SigilUnsupportedError` | `BarcodeDetector`/`getUserMedia` missing | — |\n| `SigilPermissionError` | Camera `NotAllowedError` | — |\n| `SigilDisposedError` | `start()` on a disposed scanner | — |\n\nAll carry `cause` when wrapping a platform error.\n",
6
+ "usage": "---\ntitle: Sigil — Usage Guide\ndescription: Encoding modes, capacity checks, SVG/canvas rendering, camera scanning, and testing for @vielzeug/sigil.\n---\n\n[[toc]]\n\n## Basic Usage\n\n`encodeQr` produces a `QrMatrix` — a frozen square of dark/light modules. Render it with `toSvg` (string, works anywhere) or `drawToCanvas` (browser).\n\n```ts\nimport { encodeQr, qrCapacity, toSvg } from '@vielzeug/sigil';\n\nconst matrix = encodeQr('https://vielzeug.dev');\n// Pick the smallest version automatically, or pin one:\nconst pinned = encodeQr('https://vielzeug.dev', { version: 5, errorCorrection: 'Q' });\n\nconst svg = toSvg(matrix); // inline <svg> string — inject or serialize\n```\n\nThe encoder chooses the most compact mode for the whole input and the smallest version that fits. `mask` is chosen by the lowest ISO penalty score — override it only for test vectors.\n\n## Encoding Modes and Capacity\n\nThree segment modes cover every payload:\n\n| Mode | Alphabet | Max payload (v40-L) |\n| --- | --- | --- |\n| `numeric` | `0–9` | 7,089 digits |\n| `alphanumeric` | `0–9 A-Z space $%*+-./:` | 4,296 chars |\n| `byte` | UTF-8 (no ECI header) | 2,953 bytes |\n\nMode selection is automatic — `encodeQr('01234567')` uses `numeric`, `'HELLO WORLD'` uses `alphanumeric`, anything else uses `byte`. Force a mode with `mode`, but a forced mode that cannot represent the input throws `SigilOptionError`.\n\nCheck capacity before encoding with `qrCapacity(version, errorCorrection, mode)` — it returns the maximum *bytes* (for `byte` mode) or *characters* the symbol holds:\n\n```ts\nimport { qrCapacity, SigilCapacityError } from '@vielzeug/sigil';\n\nqrCapacity(10, 'M', 'byte'); // → 213 (data codewords available at v10-M)\n\ntry {\n encodeQr(hugePayload, { version: 1 });\n} catch (error) {\n if (error instanceof SigilCapacityError) {\n console.log(`${error.bytes} > ${error.maxBytes} at v${error.version}`);\n }\n}\n```\n\nUse `minVersion` to keep a stable symbol size while a payload changes — useful when a layout must not reflow between renders.\n\n## Error Correction\n\n| Level | Recovery | Trade-off |\n| --- | --- | --- |\n| `L` | ~7% | Smallest matrix |\n| `M` | ~15% | Default — good balance |\n| `Q` | ~25% | Print, wear |\n| `H` | ~30% | Logos/overlays, dense codes |\n\nHigher levels shrink capacity at the same version. For an overlaid logo, prefer `H` *and* keep the logo inside the quiet zone's margin of safety.\n\n## Rendering\n\n### SVG — `toSvg`\n\n```ts\nconst svg = toSvg(matrix, {\n dark: 'currentColor', // default — inherits text color\n light: 'transparent', // default\n margin: 4, // quiet zone in modules (spec minimum is 4)\n scale: 1, // user units per module — prefer scaling via CSS\n optimizePath: true, // merge adjacent modules into one path (default)\n label: 'Pairing code', // <title> + role=\"img\"\n});\n```\n\n`optimizePath` merges each row's dark runs into a single `<path>` — orders of magnitude smaller markup. Set `dark`/`light` to CSS variables to theme the code (`dark: 'var(--qr-dark)'`).\n\n### Canvas — `drawToCanvas` (browser only)\n\n```ts\nconst sizePx = drawToCanvas(matrix, canvas, { scale: 8, margin: 4 });\n// canvas is resized for devicePixelRatio; returns the CSS-pixel edge length\n```\n\nUse canvas for print, download (`canvas.toBlob`), or compositing. SVG is the better default for display.\n\n## Scanning (browser only)\n\nScanning wraps the native `BarcodeDetector`. It is **not** universally available — feature-detect, never assume:\n\n| Engine | `BarcodeDetector` | Notes |\n| --- | --- | --- |\n| Chrome / Edge / Android WebView | Yes | `qr_code` format supported |\n| Firefox | No (behind flag in some builds) | `isQrScanSupported()` → `false` |\n| Safari | Varies by version | Check at runtime; do not rely on version sniffing |\n\n```ts\nimport { createQrScanner, isQrScanSupported, qrScanSupport } from '@vielzeug/sigil';\n\nisQrScanSupported(); // sync — BarcodeDetector constructor exists\nawait qrScanSupport(); // async — also verifies 'qr_code' is a supported format\n\nconst scanner = createQrScanner({\n video, // <video autoplay playsinline muted>\n constraints: { facingMode: 'environment' },\n intervalMs: 200, // min ms between detect passes\n once: true, // stop after first result\n});\n\nscanner.onResult((result) => showPairing(result.value));\nscanner.tap((event) => console.debug(event.type)); // status-change, detect, frame-skipped…\n\nawait scanner.start(); // requests camera, attaches stream, starts the loop\nscanner.stop(); // pauses; start() resumes\nscanner.dispose(); // terminal — releases everything\n```\n\n`start()` rejects with `SigilUnsupportedError` (no detector/getUserMedia) or `SigilPermissionError` (`NotAllowedError`). Detection failures mid-scan surface through `tap` `error` events and stop the scanner.\n\n### One-shot detection\n\n`detectQr(source)` runs a single pass over any `ImageBitmapSource` — `ImageData`, `Blob`, `VideoFrame`, an `<img>`, or a `<canvas>`:\n\n```ts\nimport { detectQr } from '@vielzeug/sigil';\n\nconst result = await detectQr(imageBitmap); // → { value, cornerPoints } | null\n```\n\n## Testing\n\nBoth seams are injectable — never touch globals in tests:\n\n```ts\nimport { createQrScanner } from '@vielzeug/sigil';\n\nconst fakeScanner = createQrScanner({\n video: document.createElement('video'),\n detector: { detect: async () => [{ rawValue: 'payload' }] }, // fake BarcodeDetector\n mediaDevices: { getUserMedia: async () => fakeStream }, // fake camera\n});\n```\n\nFor the web components, `ore-qr-scanner` exposes a `scannerFactory` JS property so tests inject a fake `QrScanner` without stubbing `BarcodeDetector` or `getUserMedia`.\n\n## Framework Integration\n\nEncoding is synchronous and SSR-safe — render `toSvg` output as part of server markup:\n\n::: code-group\n\n```tsx [React]\nfunction Qr({ value }: { value: string }) {\n const svg = useMemo(() => toSvg(encodeQr(value)), [value]);\n return <span dangerouslySetInnerHTML={{ __html: svg }} />;\n}\n```\n\n```vue [Vue]\n<script setup>\nimport { computed } from 'vue';\nimport { encodeQr, toSvg } from '@vielzeug/sigil';\nconst props = defineProps({ value: String });\nconst svg = computed(() => toSvg(encodeQr(props.value ?? '')));\n</script>\n<template><span v-html=\"svg\" /></template>\n```\n\n```html [Svelte]\n<script>\n import { encodeQr, toSvg } from '@vielzeug/sigil';\n export let value;\n $: svg = toSvg(encodeQr(value));\n</script>\n<span>{@html svg}</span>\n```\n\n:::\n\nScanning needs lifecycle — start in `onMounted`/`onMount`, always `dispose()` in cleanup.\n\n## Working with Other Vielzeug Libraries\n\n- **Mesh** — `meshQrCodec` compresses WebRTC pairing payloads (offer/answer JSON) to QR-friendly strings. The [Pair Two Devices with QR](./examples/pair-two-devices.md) recipe shows the full flow.\n- **Refine** — prefer `ore-qr-code` / `ore-qr-scanner` over hand-rolled wrappers: they compose sigil with themeable markup, a11y labels, and state handling.\n\n## Best Practices\n\n- Pre-validate payloads with `qrCapacity` before surfacing an encode control.\n- Keep the quiet zone — `margin` below 4 risks unreadable codes; never overlay content on it.\n- Prefer `toSvg` + `currentColor` for display; reserve `drawToCanvas` for print/download.\n- Feature-detect scanning with `qrScanSupport()` before showing a scan affordance.\n- Always `dispose()` scanners — camera tracks outlive route changes otherwise.\n- Use `once: false` only when the UX genuinely expects repeated decodes.\n- Treat scanned strings as untrusted input — validate before navigation or pairing.\n- Pin `minVersion` in animated/live payloads so the symbol doesn't resize mid-flow.\n",
7
+ "examples": "---\ntitle: Sigil — Examples\ndescription: Practical examples and recipes for @vielzeug/sigil.\n---\n\n## Examples\n\n- [Getting Started](./examples/getting-started.md)\n- [Pair Two Devices with QR](./examples/pair-two-devices.md)\n"
8
+ },
9
+ "examples": [
10
+ {
11
+ "id": "capacity-check",
12
+ "code": "import { encodeQr, qrCapacity, SigilCapacityError } from '@vielzeug/sigil'\n\n// qrCapacity returns the payload limit per version/level/mode —\n// characters for numeric/alphanumeric, bytes for byte mode.\nfor (const [v, ec, mode] of [\n [1, 'L', 'byte'],\n [1, 'M', 'byte'],\n [10, 'M', 'byte'],\n [40, 'L', 'byte'],\n [40, 'L', 'numeric'],\n]) {\n console.log(`v${v}-${ec} ${mode}: ${qrCapacity(v, ec, mode)}`)\n}\n\n// Encoding beyond capacity throws SigilCapacityError with the numbers.\nconst payload = 'x'.repeat(qrCapacity(1, 'M', 'byte') + 1)\ntry {\n encodeQr(payload, { version: 1 })\n} catch (e) {\n if (e instanceof SigilCapacityError) {\n console.log('capacity exceeded:', e.bytes, '>', e.maxBytes, 'at v' + e.version)\n }\n}\n\n// Without a pinned version the encoder just grows to the next fitting one\nconst auto = encodeQr(payload)\nconsole.log('auto-picked version:', auto.version)",
13
+ "name": "Capacity Check"
14
+ },
15
+ {
16
+ "id": "encode-svg",
17
+ "code": "import { encodeQr, toSvg } from '@vielzeug/sigil'\n\n// The encoder picks the most compact mode, smallest version, and best mask.\nconst matrix = encodeQr('https://vielzeug.dev', { errorCorrection: 'M' })\nconsole.log('version:', matrix.version, '| size:', matrix.size, 'modules | mode:', matrix.mode)\n\n// toSvg returns a complete <svg> string — works in Node (no DOM needed).\n// dark defaults to currentColor so the code themes with CSS.\nconst svg = toSvg(matrix, { label: 'Vielzeug link' })\nconsole.log(svg.slice(0, 120) + '…')\nconsole.log('svg bytes:', svg.length)\n\n// Longer payloads bump the version automatically\nconst bigger = encodeQr('https://vielzeug.dev/sigil/examples/pair-two-devices')\nconsole.log('longer payload → version:', bigger.version, '| size:', bigger.size)",
18
+ "name": "Encode to SVG"
19
+ }
20
+ ],
21
+ "typeSignatures": {
22
+ "drawToCanvas": "export { drawToCanvas } from './canvas';",
23
+ "encodeQr": "export { encodeQr, qrCapacity } from './encode';",
24
+ "qrCapacity": "export { encodeQr, qrCapacity } from './encode';",
25
+ "SigilCapacityError": "export {\n SigilCapacityError,\n SigilDisposedError,\n SigilError,\n SigilOptionError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from './errors';",
26
+ "SigilDisposedError": "export {\n SigilCapacityError,\n SigilDisposedError,\n SigilError,\n SigilOptionError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from './errors';",
27
+ "SigilError": "export {\n SigilCapacityError,\n SigilDisposedError,\n SigilError,\n SigilOptionError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from './errors';",
28
+ "SigilOptionError": "export {\n SigilCapacityError,\n SigilDisposedError,\n SigilError,\n SigilOptionError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from './errors';",
29
+ "SigilPermissionError": "export {\n SigilCapacityError,\n SigilDisposedError,\n SigilError,\n SigilOptionError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from './errors';",
30
+ "SigilUnsupportedError": "export {\n SigilCapacityError,\n SigilDisposedError,\n SigilError,\n SigilOptionError,\n SigilPermissionError,\n SigilUnsupportedError,\n} from './errors';",
31
+ "createQrScanner": "export { createQrScanner, detectQr, isQrScanSupported, qrScanSupport } from './scan';",
32
+ "detectQr": "export { createQrScanner, detectQr, isQrScanSupported, qrScanSupport } from './scan';",
33
+ "isQrScanSupported": "export { createQrScanner, detectQr, isQrScanSupported, qrScanSupport } from './scan';",
34
+ "qrScanSupport": "export { createQrScanner, detectQr, isQrScanSupported, qrScanSupport } from './scan';",
35
+ "toSvg": "export { toSvg } from './svg';",
36
+ "QrCanvasOptions": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
37
+ "QrDetector": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
38
+ "QrEncodeOptions": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
39
+ "QrErrorCorrection": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
40
+ "QrMatrix": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
41
+ "QrMode": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
42
+ "QrScanner": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
43
+ "QrScannerOptions": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
44
+ "QrScannerStatus": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
45
+ "QrScanResult": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
46
+ "QrSvgOptions": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';",
47
+ "SigilEvent": "export type {\n QrCanvasOptions,\n QrDetector,\n QrEncodeOptions,\n QrErrorCorrection,\n QrMatrix,\n QrMode,\n QrScanner,\n QrScannerOptions,\n QrScannerStatus,\n QrScanResult,\n QrSvgOptions,\n SigilEvent,\n} from './types';"
48
+ }
49
+ }
@@ -1,9 +1,9 @@
1
1
  {
2
- "apiSource": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';\nexport { ErrorCode, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';\nexport { createParseContext, type DeepPartial } from './messages';\nexport { s } from './s';\n",
2
+ "apiSource": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';\nexport { ErrorCode, joinIssuePath, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';\nexport { createParseContext, type DeepPartial } from './messages';\nexport { s } from './s';\n",
3
3
  "docs": {
4
- "index": "---\ntitle: Spell — Schema validation for TypeScript\ndescription: Zero-dependency schema validation with explicit async checks, Standard Schema interoperability, and JSON Schema export.\npackage: spell\ncategory: validation\nkeywords: [schema, validation, parsing, standard-schema, json-schema, locale, typescript, descriptors]\nrelated: [forge, courier, vault]\nexports:\n [s, createParseContext, SpellValidationError, SpellDefinitionError, StandardSchemaV1, ErrorCode, './json', './predicates']\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"spell\" />\n\n## Why Spell?\n\nSpell keeps runtime validation, static inference, and portable definitions in one API. Use `s` for schema construction; import JSON conversion and predicates from dedicated subpaths.\n\nThis example shows the difference between manual branching and a single reusable schema.\n\n```ts\n// Before\nfunction parseUserBefore(value: unknown) {\n if (typeof value !== 'object' || value === null) throw new Error('Expected object');\n\n const candidate = value as Record<string, unknown>;\n\n if (typeof candidate.email !== 'string' || !candidate.email.includes('@')) {\n throw new Error('Expected valid email');\n }\n\n if (typeof candidate.role !== 'string' || !['admin', 'editor', 'viewer'].includes(candidate.role)) {\n throw new Error('Expected valid role');\n }\n\n return {\n email: candidate.email,\n role: candidate.role,\n };\n}\n\n// After\nimport { s } from '@vielzeug/spell';\n\nconst User = s.object({\n email: s.string().email(),\n role: s.enum(['admin', 'editor', 'viewer'] as const),\n});\n\nconst user = User.parse({ email: 'ada@example.com', role: 'admin' });\n```\n\n| Feature | Spell | Zod | Yup |\n| ----------------- | --------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------- |\n| Bundle size | <PackageInfo package=\"spell\" type=\"size\" /> | ~62 kB | ~14 kB |\n| Type inference | <ore-icon name=\"check\" size=\"16\"></ore-icon> `Infer<T>` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial |\n| Coercion API | <ore-icon name=\"check\" size=\"16\"></ore-icon> `s.coerce.*` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Async validation | <ore-icon name=\"check\" size=\"16\"></ore-icon> `.checkAsync()` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Error flattening | <ore-icon name=\"check\" size=\"16\"></ore-icon> `flatten()` + `flattenFirst()` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Spell when** you want a fluent schema API with strong TypeScript inference, structured errors, and no third-party runtime dependencies.\n\n**Consider alternatives when** you are already standardized on another validator ecosystem and migration cost outweighs the API benefits.\n\n</div>\n\n## Installation\n\nUse your workspace package manager to add Spell.\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/spell\n```\n\n```sh [npm]\nnpm install @vielzeug/spell\n```\n\n```sh [yarn]\nyarn add @vielzeug/spell\n```\n\n:::\n\n## Quick Start\n\nStart with a schema, then parse unknown input and use the inferred output type everywhere else.\n\n```ts\nimport { s, type Infer } from '@vielzeug/spell';\n\nconst User = s\n .object({\n email: s.string().email(),\n name: s.string().min(1),\n role: s.enum(['admin', 'editor', 'viewer'] as const),\n })\n .relaxed(); // allow extra keys — omit for strict-mode (default)\n\ntype User = Infer<typeof User>;\n\nconst payload: unknown = {\n email: 'ada@example.com',\n name: 'Ada',\n role: 'admin',\n team: 'platform',\n};\n\nconst result = User.safeParse(payload);\n\nif (!result.success) throw result.error;\nconst user = result.data;\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- Namespace and tree-shakeable schema builders.\n- Sync and async parsing with `parse()`, `safeParse()`, `parseAsync()`, and `safeParseAsync()`.\n- Dependency-free Standard Schema v1 interoperability through `schema['~standard']`.\n- Explicit `check()` and `checkAsync()` rules; sync parsing never skips an async check.\n- Wrapper modes for `optional`, `nullable`, `nullish`, `default`, `catch`, and `required`.\n- Frozen declarative definitions through `definition()` and JSON Schema export via `fromDefinition()` from `@vielzeug/spell/json`.\n- Request-local message customization through `createParseContext()`.\n- Ordered union parsing produces the same selected branch in sync and async modes.\n- Structured errors with direct path lookup and flattened views.\n- Object parsing is hardened against prototype-pollution-style keys.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Forge](/forge/) — typed form state that uses Spell schemas as its validation layer\n- [Courier](/courier/) — HTTP client for validating request and response payloads at service boundaries\n- [Vault](/vault/) — unified storage API that accepts Spell schemas to type-gate persisted data\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
- "api": "---\ntitle: Spell — API Reference\ndescription: Reference for Spell schema builders, parsing, errors, context, and tooling exports.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| ------------------------ | ------------------------------- | ---------------------------------- | ------------------------------------------ |\n| `s` | Creates schemas | Sync or async, depending on checks | `checkAsync()` requires async parsing |\n| `createParseContext()` | Creates request-local messages | Sync | Pass the context to each parse call |\n| `SpellValidationError` | Provides validation issues | Sync/async parse failures | Use `safeParse()` for expected invalid data |\n\n## Package Entry Point\n\n| Import | Purpose |\n| ---------------------------- | ----------------------------------------------- |\n| `@vielzeug/spell` | Schema builders, errors, types, and parse contexts |\n| `@vielzeug/spell/json` | Convert portable definitions to JSON Schema |\n| `@vielzeug/spell/predicates` | Standalone format and type predicates |\n\n```ts\nimport { createParseContext, s, type Infer } from '@vielzeug/spell';\nimport { fromDefinition } from '@vielzeug/spell/json';\nimport { isEmail } from '@vielzeug/spell/predicates';\n```\n\n## `s`\n\nAll builders live under `s`.\n\n| Builder | Purpose |\n| ----------------------------------------------------------------- | -------------------------- |\n| `string`, `number`, `boolean`, `bigint`, `date` | Primitive values |\n| `literal`, `enum`, `null`, `undefined`, `unknown`, `never` | Exact and universal values |\n| `array`, `tuple`, `set`, `map`, `record`, `object` | Collections |\n| `union`, `intersect`, `discriminatedUnion`, `lazy` | Composition |\n| `coerce.*` | Coercing primitive schemas |\n\n```ts\nconst User = s.object({\n email: s.string().email(),\n id: s.string().uuid(),\n role: s.enum(['admin', 'member'] as const),\n});\n\ntype User = Infer<typeof User>;\n```\n\nObject schemas reject unknown keys. Use `.relaxed()` to retain extras.\n\n## Standard Schema interoperability\n\nEvery schema implements the dependency-free Standard Schema v1 structural protocol through `schema['~standard']`. Consumers that accept Standard Schema validators can use Spell schemas directly without an adapter.\n\n```ts\nconst User = s.object({ name: s.string() });\nconst result = await User['~standard'].validate({ name: 'Ada' });\n```\n\nSuccessful results contain `value`; failures contain portable `{ message, path }` issues. Failed unions remain one stable union issue instead of selecting a branch heuristically.\n\nThe exported `StandardSchemaV1` interface mirrors the full v1 protocol, including `validate(value, options?)` and the `InferInput` and `InferOutput` namespace helpers.\n\n## Parsing\n\nEvery schema provides:\n\n```ts\nschema.parse(value, context?); // Output or SpellValidationError\nschema.safeParse(value, context?); // ParseResult<Output>\nschema.parseAsync(value, context?); // Promise<Output>\nschema.safeParseAsync(value, context?); // Promise<ParseResult<Output>>\nschema.is(value); // value is Output\nschema.assert(value, label?); // assertion\n```\n\n`parse()`, `safeParse()`, `is()`, and `assert()` are available on synchronous schemas. Calling `checkAsync()` returns an async-only schema, where TypeScript exposes only `parseAsync()` and `safeParseAsync()`. That async-only mode propagates through compositional schemas when a child is asynchronous.\n\n## Custom Checks\n\n`check()` is synchronous. `checkAsync()` is asynchronous. Do not return a Promise from `check()`.\n\n```ts\nconst Signup = s.object({ confirm: s.string(), password: s.string() }).check((value, context) => {\n if (value.password !== value.confirm) {\n context.addIssue({ code: 'custom', message: 'Passwords must match', path: ['confirm'] });\n }\n});\n\nconst AvailableEmail = s\n .string()\n .email()\n .checkAsync(async (value) => {\n return (await emailAvailable(value)) || 'Email is already registered';\n });\n```\n\n`CheckContext.addIssue()` takes `{ code, message, params?, path? }`. Paths are relative to current schema.\n\n## Modifiers and Transforms\n\n```ts\ns.string().optional();\ns.string().nullable();\ns.string().nullish();\ns.string().required();\ns.string().default('guest');\ns.string().catch('guest');\ns.string()\n .trim()\n .transform((value) => value.toLowerCase());\ns.string().pipe(s.string().slug());\ns.string().label('User name');\n```\n\n`default()`, `catch()`, preprocessors, transforms, and checks are runtime behavior. They cannot become portable definitions.\n\n## Definitions and JSON Schema\n\n`definition()` is only for schemas containing declarative structure. It returns frozen data and throws `SpellDefinitionError` when runtime behavior is present.\n\n```ts\nimport { s } from '@vielzeug/spell';\nimport { fromDefinition } from '@vielzeug/spell/json';\n\nconst Product = s.object({\n id: s.string().uuid(),\n name: s.string().min(1),\n});\n\nconst definition = Product.definition();\nconst jsonSchema = fromDefinition(definition);\n```\n\nNo implicit schema-to-JSON conversion exists. Make definition boundary explicit.\n\n## Parse Contexts\n\n`createParseContext()` creates immutable request-local validation messages.\n\n```ts\nfunction createParseContext(messages?: DeepPartial<Messages>): ParseContext;\n```\n\n**Returns:** A parse context with built-in messages merged with the supplied overrides.\n\n```ts\nimport { createParseContext, s } from '@vielzeug/spell';\n\nconst context = createParseContext({\n object: { invalidKeys: () => 'Unsupported field' },\n});\n\nconst result = s.object({ email: s.string().email() }).safeParse({ email: 'ada@example.com', extra: true }, context);\n\nif (!result.success) {\n const messages = result.error.messagesAt('email');\n console.log(messages);\n}\n```\n\n## Types\n\n### Inference types\n\n| Type | Resolves to |\n| --- | --- |\n| `Infer<TSchema>` | Parsed output; alias of `InferOutput<TSchema>` |\n| `InferInput<TSchema>` | Input accepted before parsing or transformation |\n| `InferOutput<TSchema>` | Value returned after parsing and transformation |\n\nComposite schemas preserve child input types. Object input types make fields optional when their field schema accepts `undefined`.\n\n### Standard Schema\n\n```ts\ninterface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly '~standard': StandardSchemaV1.Props<Input, Output>;\n}\n\nnamespace StandardSchemaV1 {\n interface Props<Input = unknown, Output = Input> {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (value: unknown, options?: Options) => Result<Output> | Promise<Result<Output>>;\n readonly types?: Types<Input, Output>;\n }\n\n type Result<Output> = { readonly value: Output; readonly issues?: undefined } | { readonly issues: readonly Issue[] };\n interface Options { readonly libraryOptions?: Record<string, unknown> }\n interface Issue { readonly message: string; readonly path?: readonly (PropertyKey | PathSegment)[] }\n interface PathSegment { readonly key: PropertyKey }\n interface Types<Input = unknown, Output = Input> { readonly input: Input; readonly output: Output }\n type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];\n type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];\n}\n```\n\n### Parse result and issues\n\n```ts\ntype ParseResult<T> = { data: T; success: true } | { error: SpellValidationError; success: false };\n\ntype Issue =\n | { code: 'custom'; message: string; params?: Record<string, unknown>; path: (string | number)[] }\n | { code: 'invalid_base64'; message: string; params: { format: string }; path: (string | number)[] }\n | { code: 'invalid_date'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_duration'; message: string; params: { format: string }; path: (string | number)[] }\n | { code: 'invalid_enum'; message: string; params: { values: readonly unknown[] }; path: (string | number)[] }\n | { code: 'invalid_finite'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_integer'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_keys'; message: string; params: { keys: string[] }; path: (string | number)[] }\n | { code: 'invalid_length'; message: string; params: { exact: number }; path: (string | number)[] }\n | { code: 'invalid_literal'; message: string; params: { expected: unknown }; path: (string | number)[] }\n | { code: 'invalid_multiple_of'; message: string; params: { step: number | bigint }; path: (string | number)[] }\n | { code: 'invalid_safe'; message: string; params?: undefined; path: (string | number)[] }\n | {\n code: 'invalid_string';\n message: string;\n params: { format?: string; includes?: string; pattern?: string; prefix?: string; suffix?: string };\n path: (string | number)[];\n }\n | { code: 'invalid_type'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_union'; message: string; params: { errors: Issue[][] }; path: (string | number)[] }\n | { code: 'invalid_unique'; message: string; params: { unique: true }; path: (string | number)[] }\n | { code: 'invalid_url'; message: string; params: { format: string }; path: (string | number)[] }\n | {\n code: 'invalid_variant';\n message: string;\n params: { discriminator: string; expected: string[] };\n path: (string | number)[];\n }\n | {\n code: 'too_big';\n message: string;\n params: { exclusive?: boolean; max: number | bigint | Date };\n path: (string | number)[];\n }\n | {\n code: 'too_small';\n message: string;\n params: { exclusive?: boolean; min: number | bigint | Date };\n path: (string | number)[];\n }\n | { code: string & {}; message: string; params?: Record<string, unknown>; path: (string | number)[] };\n```\n\n`ErrorCode` is a const object mapping each issue code to its string literal.\n\n### Validation contracts\n\n```ts\ntype ParseContext = { messages: Messages };\n\ntype CheckContext = {\n addIssue: (issue: {\n code: string;\n message: string;\n params?: Record<string, unknown>;\n path?: (string | number)[];\n }) => void;\n};\n\ntype ValidateResult = boolean | null | undefined | string;\n```\n\n### Messages\n\n```ts\ntype MessageFn<Ctx extends Record<string, unknown> = Record<string, unknown>> = string | ((ctx: Ctx) => string);\n\ntype Messages = {\n array: { length: (ctx: { exact: number; value: unknown[] }) => string; max: (ctx: { max: number; value: unknown[] }) => string; min: (ctx: { min: number; value: unknown[] }) => string; nonEmpty: () => string; type: () => string; unique: () => string };\n bigint: { max: (ctx: { max: bigint; value: bigint }) => string; min: (ctx: { min: bigint; value: bigint }) => string; multipleOf: (ctx: { step: bigint; value: bigint }) => string; negative: () => string; nonNegative: () => string; nonPositive: () => string; positive: () => string; type: () => string };\n boolean: { type: () => string };\n check: { default: () => string };\n date: { max: (ctx: { max: Date; value: Date }) => string; min: (ctx: { min: Date; value: Date }) => string; type: () => string };\n enum: { invalid: (ctx: { values: readonly unknown[] }) => string };\n instanceof: { type: (ctx: { className: string }) => string };\n literal: { expected: (ctx: { expected: unknown }) => string };\n map: { max: (ctx: { max: number; value: Map<unknown, unknown> }) => string; min: (ctx: { min: number; value: Map<unknown, unknown> }) => string; nonEmpty: () => string; size: (ctx: { exact: number; value: Map<unknown, unknown> }) => string; type: () => string };\n never: { invalid: () => string };\n number: { finite: () => string; int: () => string; max: (ctx: { max: number; value: number }) => string; min: (ctx: { min: number; value: number }) => string; multipleOf: (ctx: { step: number; value: number }) => string; negative: () => string; nonNegative: () => string; nonPositive: () => string; positive: () => string; safe: () => string; type: () => string };\n object: { invalidKeys: (ctx: { keys: string[] }) => string; type: () => string };\n set: { max: (ctx: { max: number; value: Set<unknown> }) => string; min: (ctx: { min: number; value: Set<unknown> }) => string; nonEmpty: () => string; size: (ctx: { exact: number; value: Set<unknown> }) => string; type: () => string };\n string: { base64: () => string; base64url: () => string; cuid: () => string; cuid2: () => string; date: () => string; dateTime: () => string; duration: () => string; email: () => string; emoji: () => string; endsWith: (ctx: { suffix: string; value: string }) => string; hex: () => string; hexColor: () => string; includes: (ctx: { substr: string; value: string }) => string; ip: () => string; jwt: () => string; length: (ctx: { exact: number; value: string }) => string; max: (ctx: { max: number; value: string }) => string; min: (ctx: { min: number; value: string }) => string; nanoid: () => string; nonEmpty: () => string; numeric: () => string; regex: (ctx: { value: string }) => string; semver: () => string; slug: () => string; startsWith: (ctx: { prefix: string; value: string }) => string; time: () => string; type: () => string; ulid: () => string; url: () => string; uuid: () => string };\n tuple: { length: (ctx: { exact: number }) => string; min: (ctx: { min: number }) => string; type: () => string };\n union: { invalid: () => string };\n variant: { invalidDiscriminator: (ctx: { discriminator: string; expected: string[] }) => string; type: () => string };\n};\n\ntype DeepPartial<T> = {\n [K in keyof T]?: T[K] extends Record<string, unknown> ? DeepPartial<T[K]> : T[K];\n};\n```\n\n### Descriptor and JSON Schema\n\n```ts\ntype SchemaDescriptor = BaseDescriptor &\n (\n | { kind: 'unknown' | 'never' | 'boolean' | 'bigint' | 'date' | 'lazy' }\n | { className: string; kind: 'instanceof' }\n | { contentEncoding?: string; format?: string; kind: 'string'; maxLength?: number; minLength?: number; pattern?: string | null }\n | { exclusiveMaximum?: number; exclusiveMinimum?: number; kind: 'number'; maximum?: number; minimum?: number; multipleOf?: number; typeHint?: 'integer' }\n | { kind: 'literal'; value: string | number | boolean | null | undefined }\n | { kind: 'enum'; values: readonly (string | number)[] }\n | { items: SchemaDescriptor; kind: 'array'; maxItems?: number; minItems?: number }\n | { items: SchemaDescriptor[]; kind: 'tuple'; rest: SchemaDescriptor | null }\n | { fields: Record<string, SchemaDescriptor>; kind: 'object'; strict: boolean }\n | { key: SchemaDescriptor; kind: 'record'; value: SchemaDescriptor }\n | { items: SchemaDescriptor; kind: 'set' }\n | { key: SchemaDescriptor; kind: 'map'; value: SchemaDescriptor }\n | { branches: SchemaDescriptor[]; kind: 'union' | 'intersect' }\n | { branches: Record<string, SchemaDescriptor>; discriminator: string; kind: 'variant' }\n | { from: SchemaDescriptor; kind: 'pipe'; to: SchemaDescriptor }\n );\n\ntype JsonSchema = Record<string, unknown>;\n```\n\n### Schema traversal\n\n`SchemaWalker<R>` is the visitor type accepted by `schema.walk()`. Handlers are optional and correspond to schema kinds; composite handlers also receive their walked children. Use `unknown` as the fallback handler.\n\n### Error helpers\n\n```ts\ntype FlatError = { messages: string[]; path: (string | number)[] };\ntype FlatErrorFirst = { message: string; path: (string | number)[] };\n```\n\n## Errors\n\n- `SpellError` — base class. Use `instanceof SpellError` for cross-boundary narrowing.\n- `SpellValidationError` — validation failure with `issues`, `messagesAt()`, `flatten()`, and `flattenFirst()`.\n- `SpellDefinitionError` — schema cannot create portable definition.\n\n```ts\nconst result = s.object({ email: s.string().email() }).safeParse({ email: 'invalid' });\n\nif (!result.success) {\n const { fieldErrors, formErrors } = result.error.flatten();\n console.log(fieldErrors, formErrors);\n}\n```\n",
6
- "usage": "---\ntitle: Spell — Usage Guide\ndescription: Learn how to build schemas, compose wrappers, customize locales, and integrate spell with other Vielzeug packages.\n---\n\n[[toc]]\n\n## Basic Usage\n\nStart with `safeParse()` when you want explicit success and failure branches.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Signup = s.object({\n email: s.string().email(),\n password: s.string().min(12),\n referralCode: s.string().optional(),\n});\n\nconst result = Signup.safeParse({\n email: 'ada@example.com',\n password: 'horse-battery-staple',\n});\n\nif (!result.success) {\n console.error(result.error.issues);\n} else {\n console.log(result.data.email);\n}\n```\n\nUse `parse()` when invalid input should throw immediately. Use `safeParse()` when invalid input is part of normal control flow.\n\n## Building Schemas\n\nUse the namespace form when readability matters more than bundle trimming.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Article = s.object({\n id: s.string().uuid(),\n title: s.string().trim().min(1).max(120),\n slug: s.string().slug(),\n tags: s.array(s.string().min(1)).default(() => []),\n meta: s\n .object({\n published: s.boolean(),\n publishedAt: s.date().nullable(),\n })\n .relaxed(),\n});\n```\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Todo = s.object({\n done: s.boolean(),\n tags: s.array(s.string().min(1)).default(() => []),\n title: s.string().min(1),\n});\n```\n\nObject schemas reject unknown keys by default. Call `.relaxed()` when you need to preserve extra properties.\n\nCall `.defaults()` to get a fully default-filled object without providing any input. Every required field must have a `.default()` set, or a `SpellValidationError` is thrown. Call `.partialDefaults()` when only some fields have defaults — fields without a default are silently omitted instead of throwing.\n\n```ts\nconst Config = s.object({\n host: s.string().default('localhost'),\n port: s.number().default(3000),\n});\n\nConfig.defaults(); // { host: 'localhost', port: 3000 }\n\nconst Form = s.object({ name: s.string(), role: s.string().default('viewer') });\nForm.partialDefaults(); // { role: 'viewer' }\n```\n\n## Wrapper Modes, Defaults, and Fallbacks\n\nChain wrappers to describe missing values and recovery rules without losing schema metadata.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst DisplayName = s.string().trim().min(2).label('Display name').optional().default('Guest').nullable();\n\nDisplayName.parse(undefined); // 'Guest'\nDisplayName.parse(null); // null\nDisplayName.description; // 'Display name'\n```\n\nCall `.required()` to remove `undefined` without removing `null`.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst NullableButRequired = s.string().optional().nullable().required();\n\nNullableButRequired.parse('Ada');\nNullableButRequired.parse(null);\n// NullableButRequired.parse(undefined); // throws\n```\n\nUse `.catch()` when you want a fallback output after validation fails.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Port = s.number().int().min(1).max(65535).catch(3000);\n\nPort.parse('not-a-number'); // 3000\n```\n\n## Custom Validation\n\nUse `check()` for synchronous domain rules and `checkAsync()` for asynchronous rules. Sync parsing rejects schemas with asynchronous checks.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\n// Boolean shorthand: return false to fail with default message\nconst EvenNumber = s.number().check((n) => n % 2 === 0);\n\n// String shorthand: return the message as a string\nconst Username = s\n .string()\n .min(3)\n .check((v) => !v.startsWith('_') || 'Cannot start with underscore');\n\n// Multiple issues via ctx.addIssue()\nconst Signup = s.object({ confirm: s.string(), password: s.string() }).check((v, ctx) => {\n if (v.password !== v.confirm) {\n ctx.addIssue({ code: 'custom', message: 'Passwords must match', path: ['confirm'] });\n }\n});\n```\n\n`checkAsync()` returns an async-only schema: TypeScript exposes `parseAsync()` and `safeParseAsync()` but not `parse()` or `safeParse()`. This mode survives fluent modifiers and propagates through nested arrays, objects, unions, intersections, tuples, maps, records, sets, lazy schemas, pipelines, and `s.discriminatedUnion(...)` branches. Sync parsing also fails at runtime instead of accepting an unchecked value.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst takenEmails = new Set(['ada@example.com']);\n\nconst AccountEmail = s\n .string()\n .email()\n .checkAsync(async (value, ctx) => {\n if (takenEmails.has(value)) {\n ctx.addIssue({ code: 'custom', message: 'Email is already taken', path: [] });\n }\n });\n\n// Async checks require parseAsync\nawait AccountEmail.parseAsync('grace@example.com');\n```\n\nUse `check()` for predicate-only rules too. Return `true` on success or message on failure.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst PositivePrice = s.number().check((value) => value > 0 || 'Must be positive');\nPositivePrice.parse(9.99);\n```\n\n## Strings, Numbers, and Safe Regex Usage\n\nUse schema helpers for common string and number constraints instead of hand-written predicates.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Password = s.string().min(12).regex(/[A-Z]/).regex(/[0-9]/);\nconst Price = s.number().nonNegative().multipleOf(0.01);\nconst LaunchWindow = s.date().min(new Date('2025-01-01T00:00:00.000Z'));\n```\n\nSpell strips stateful `/g` and `/y` flags from `regex()` patterns before validation. Repeated parses stay deterministic even when the original regular expression is reused.\n\n## Coercion and Transforms\n\nUse coercion when input arrives as strings, query parameters, or form values.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Query = s.object({\n draft: s.coerce.boolean().default(false),\n limit: s.coerce.number().int().positive().default(20),\n publishedAt: s.coerce.date().nullable(),\n search: s.coerce.string().trim().min(1).optional(),\n});\n\nconst parsed = Query.parse({\n draft: 'true',\n limit: '50',\n publishedAt: '2025-04-01T12:00:00.000Z',\n search: ' vielzeug ',\n});\n```\n\nUse `transform()` or `pipe()` after validation when downstream code needs a different output shape.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst TrimmedTags = s.array(s.string().trim().min(1)).transform((tags) => tags.map((tag) => tag.toLowerCase()));\nconst Slug = s.string().trim().min(1).pipe(s.string().slug());\n```\n\n## Introspection, Round-Trips, and JSON Schema\n\nUse declarative definitions when schemas need to cross process boundaries or feed tooling.\n\n```ts\nimport { s } from '@vielzeug/spell';\nimport { fromDefinition } from '@vielzeug/spell/json';\n\nconst Product = s\n .object({\n id: s.string().uuid(),\n name: s.string().min(1),\n price: s.number().positive().multipleOf(0.01),\n })\n .label('Product');\n\nconst definition = Product.definition();\nconst jsonSchema = fromDefinition(definition);\n\nProduct.parse({ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Keyboard', price: 129.99 });\nconsole.log(jsonSchema.title);\n```\n\nDefinitions are frozen serializable snapshots of declarative schema structure. Use `definition()` and `fromDefinition()` for external tooling. Schemas with runtime checks, transforms, defaults, catches, or preprocessors intentionally have no definition.\n\n## Messages\n\nSpell has no mutable process-wide configuration. Build one parse context per request, locale, or form, then pass it explicitly.\n\n```ts\nimport { createParseContext, s } from '@vielzeug/spell';\n\nconst User = s.object({ email: s.string().email() });\nconst german = createParseContext({\n object: { invalidKeys: () => 'Keine unbekannten Felder erlaubt' },\n});\n\nUser.safeParse({ email: 'ada@example.com', extra: true }, german);\n```\n\nInternal development warnings always use `console.warn` in development builds. Route application diagnostics in application code instead of mutating library-wide logger state.\n\n## Working with Validation Errors\n\nUse `SpellValidationError` helpers when you need UI-ready error structures.\n\n```ts\nimport { s, SpellValidationError } from '@vielzeug/spell';\n\nconst User = s.object({\n email: s.string().email(),\n profile: s.object({\n name: s.string().min(2),\n }),\n});\n\nconst result = User.safeParse({ email: 'nope', profile: { name: '' } });\n\nif (!result.success && result.error instanceof SpellValidationError) {\n const profileErrors = result.error.messagesAt('profile', 'name');\n console.log(profileErrors);\n}\n```\n\nUnion failures produce one stable `invalid_union` issue. Use `s.discriminatedUnion()` when a discriminator can identify the intended object branch and provide field-specific errors.\n\n## Schema Traversal with walk()\n\nUse `walk()` to inspect or transform a schema tree without importing internal implementation classes.\n\n```ts\nimport { s, type SchemaWalker } from '@vielzeug/spell';\n\nconst fields: string[] = [];\n\nconst collectFields: SchemaWalker<void> = {\n object(schema) {\n for (const [key, child] of Object.entries(schema.shape)) {\n fields.push(key);\n child.walk(collectFields);\n }\n },\n unknown() {},\n};\n\nconst User = s.object({\n email: s.string().email(),\n profile: s.object({ name: s.string() }),\n});\n\nUser.walk(collectFields);\nconsole.log(fields); // ['email', 'profile', 'name']\n```\n\n`walk()` dispatches by `schema.kind`. If no handler matches and no `unknown` fallback is provided, `walk()` returns `null`. Add an `unknown` handler to capture any kind not explicitly listed in your visitor.\n\n## Framework Integration\n\nSpell works anywhere you can call a function before state enters your app.\n\n::: code-group\n\n```tsx [React]\nimport { s } from '@vielzeug/spell';\n\nconst SearchParams = s\n .object({\n page: s.coerce.number().int().positive().default(1),\n q: s.string().trim().optional(),\n })\n .relaxed();\n\nexport function SearchPage({ rawParams }: { rawParams: unknown }) {\n const params = SearchParams.parse(rawParams);\n\n return (\n <div>\n {params.q ?? 'All results'} — page {params.page}\n </div>\n );\n}\n```\n\n```ts [Vue]\nimport { computed, ref } from 'vue';\nimport { s } from '@vielzeug/spell';\n\nconst Settings = s.object({\n locale: s.string().min(2),\n compact: s.coerce.boolean().default(false),\n});\n\nconst raw = ref<unknown>({ locale: 'en', compact: 'true' });\nconst settings = computed(() => Settings.parse(raw.value));\n```\n\n:::\n\nUse `safeParse()` at event boundaries and `parse()` inside trusted data flows.\n\n## Working with Other Vielzeug Libraries\n\nUse Spell as the validation layer and let other packages focus on transport, forms, or storage.\n\n```ts\nimport { createForm } from '@vielzeug/forge';\nimport { schemaValidator } from '@vielzeug/forge/schema';\nimport { createCourier } from '@vielzeug/courier';\nimport { s } from '@vielzeug/spell';\n\nconst Profile = s.object({\n displayName: s.string().min(2),\n newsletter: s.boolean(),\n});\n\nconst form = createForm({\n initialValues: {\n displayName: '',\n newsletter: false,\n },\n validate: schemaValidator(Profile),\n});\n\nconst courier = createCourier({ baseUrl: '/api' });\nconst profile = Profile.parse(await courier.get('/profile'));\n```\n\nUse Spell definitions with `@vielzeug/codex` or other tooling when you need generated docs or external schema consumers.\n\n## Best Practices\n\n- Keep schemas close to the boundary where unknown data enters your app.\n- Use `s` consistently for construction; use explicit `/json` and `/predicates` subpaths for tooling.\n- Use `.default(() => value)` for mutable defaults such as arrays, objects, `Map`, and `Set`.\n- Call `.required()` when you want to remove `undefined` but keep `null` semantics intact.\n- Use `check()` with a `ctx` argument when you need `ctx.addIssue()`; return a message for simple predicate failures.\n- Use `checkAsync()` and `parseAsync()` for every asynchronous domain rule.\n- Build a parse context per request or test; never rely on mutable process-wide configuration.\n- Use `definition()` with `fromDefinition()` from `@vielzeug/spell/json` for external tooling.\n",
4
+ "index": "---\ntitle: Spell — Schema validation for TypeScript\ndescription: Zero-dependency schema validation with explicit async checks, Standard Schema interoperability, and JSON Schema export.\npackage: spell\ncategory: validation\nkeywords: [schema, validation, parsing, standard-schema, json-schema, locale, typescript, descriptors]\nrelated: [forge, courier, vault]\nexports:\n [s, createParseContext, joinIssuePath, SpellValidationError, SpellDefinitionError, StandardSchemaV1, ErrorCode, './json', './predicates']\nenvironments: [browser, node, ssr, deno]\n---\n\n<!-- markdownlint-disable MD025 MD033 MD060 -->\n\n<PackageHero package=\"spell\" />\n\n## Why Spell?\n\nSpell keeps runtime validation, static inference, and portable definitions in one API. Use `s` for schema construction; import JSON conversion and predicates from dedicated subpaths.\n\nThis example shows the difference between manual branching and a single reusable schema.\n\n```ts\n// Before\nfunction parseUserBefore(value: unknown) {\n if (typeof value !== 'object' || value === null) throw new Error('Expected object');\n\n const candidate = value as Record<string, unknown>;\n\n if (typeof candidate.email !== 'string' || !candidate.email.includes('@')) {\n throw new Error('Expected valid email');\n }\n\n if (typeof candidate.role !== 'string' || !['admin', 'editor', 'viewer'].includes(candidate.role)) {\n throw new Error('Expected valid role');\n }\n\n return {\n email: candidate.email,\n role: candidate.role,\n };\n}\n\n// After\nimport { s } from '@vielzeug/spell';\n\nconst User = s.object({\n email: s.string().email(),\n role: s.enum(['admin', 'editor', 'viewer'] as const),\n});\n\nconst user = User.parse({ email: 'ada@example.com', role: 'admin' });\n```\n\n| Feature | Spell | Zod | Yup |\n| ----------------- | --------------------------------------------------------------------------- | -------------------------------------------- | -------------------------------------------- |\n| Bundle size | <PackageInfo package=\"spell\" type=\"size\" /> | ~62 kB | ~14 kB |\n| Type inference | <ore-icon name=\"check\" size=\"16\"></ore-icon> `Infer<T>` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial |\n| Coercion API | <ore-icon name=\"check\" size=\"16\"></ore-icon> `s.coerce.*` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Async validation | <ore-icon name=\"check\" size=\"16\"></ore-icon> `.checkAsync()` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> |\n| Error flattening | <ore-icon name=\"check\" size=\"16\"></ore-icon> `flatten()` + `flattenFirst()` | <ore-icon name=\"check\" size=\"16\"></ore-icon> | Partial |\n| Zero dependencies | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"check\" size=\"16\"></ore-icon> | <ore-icon name=\"x\" size=\"16\"></ore-icon> |\n\n<div class=\"decision-callout\">\n\n**Use Spell when** you want a fluent schema API with strong TypeScript inference, structured errors, and no third-party runtime dependencies.\n\n**Consider alternatives when** you are already standardized on another validator ecosystem and migration cost outweighs the API benefits.\n\n</div>\n\n## Installation\n\nUse your workspace package manager to add Spell.\n\n::: code-group\n\n```sh [pnpm]\npnpm add @vielzeug/spell\n```\n\n```sh [npm]\nnpm install @vielzeug/spell\n```\n\n```sh [yarn]\nyarn add @vielzeug/spell\n```\n\n:::\n\n## Quick Start\n\nStart with a schema, then parse unknown input and use the inferred output type everywhere else.\n\n```ts\nimport { s, type Infer } from '@vielzeug/spell';\n\nconst User = s\n .object({\n email: s.string().email(),\n name: s.string().min(1),\n role: s.enum(['admin', 'editor', 'viewer'] as const),\n })\n .relaxed(); // allow extra keys — omit for strict-mode (default)\n\ntype User = Infer<typeof User>;\n\nconst payload: unknown = {\n email: 'ada@example.com',\n name: 'Ada',\n role: 'admin',\n team: 'platform',\n};\n\nconst result = User.safeParse(payload);\n\nif (!result.success) throw result.error;\nconst user = result.data;\n```\n\n## Features\n\n<div class=\"features-grid\">\n\n- Namespace and tree-shakeable schema builders.\n- Sync and async parsing with `parse()`, `safeParse()`, `parseAsync()`, and `safeParseAsync()`.\n- Dependency-free Standard Schema v1 interoperability through `schema['~standard']`.\n- Explicit `check()` and `checkAsync()` rules; sync parsing never skips an async check.\n- Wrapper modes for `optional`, `nullable`, `nullish`, `default`, `catch`, and `required`.\n- Frozen declarative definitions through `definition()` and JSON Schema export via `fromDefinition()` from `@vielzeug/spell/json`.\n- Request-local message customization through `createParseContext()`.\n- Ordered union parsing produces the same selected branch in sync and async modes.\n- Structured errors with direct path lookup and flattened views.\n- Object parsing is hardened against prototype-pollution-style keys.\n\n</div>\n\n## Documentation\n\n<div class=\"doc-links\">\n\n- [Usage Guide](./usage.md)\n- [API Reference](./api.md)\n- [Examples](./examples.md)\n- [Migration Guide](./migration.md)\n\n</div>\n\n## See Also\n\n<div class=\"see-also\">\n\n- [Forge](/forge/) — typed form state that uses Spell schemas as its validation layer\n- [Courier](/courier/) — HTTP client for validating request and response payloads at service boundaries\n- [Vault](/vault/) — unified storage API that accepts Spell schemas to type-gate persisted data\n\n</div>\n\n<!-- markdownlint-enable MD025 MD033 MD060 -->\n",
5
+ "api": "---\ntitle: Spell — API Reference\ndescription: Reference for Spell schema builders, parsing, errors, context, and tooling exports.\n---\n\n[[toc]]\n\n## API Overview\n\n| Symbol | Purpose | Execution mode | Common gotcha |\n| ------------------------ | ------------------------------- | ---------------------------------- | ------------------------------------------ |\n| `s` | Creates schemas | Sync or async, depending on checks | `checkAsync()` requires async parsing |\n| `createParseContext()` | Creates request-local messages | Sync | Pass the context to each parse call |\n| `joinIssuePath()` | Flattens issue paths to UI keys | Sync | Empty or undefined paths join to `''` |\n| `SpellValidationError` | Provides validation issues | Sync/async parse failures | Use `safeParse()` for expected invalid data |\n\n## Package Entry Point\n\n| Import | Purpose |\n| ---------------------------- | ----------------------------------------------- |\n| `@vielzeug/spell` | Schema builders, errors, types, and parse contexts |\n| `@vielzeug/spell/json` | Convert portable definitions to JSON Schema |\n| `@vielzeug/spell/predicates` | Standalone format and type predicates |\n\n```ts\nimport { createParseContext, s, type Infer } from '@vielzeug/spell';\nimport { fromDefinition } from '@vielzeug/spell/json';\nimport { isEmail } from '@vielzeug/spell/predicates';\n```\n\n## `s`\n\nAll builders live under `s`.\n\n| Builder | Purpose |\n| ----------------------------------------------------------------- | -------------------------- |\n| `string`, `number`, `boolean`, `bigint`, `date` | Primitive values |\n| `literal`, `enum`, `null`, `undefined`, `unknown`, `never` | Exact and universal values |\n| `array`, `tuple`, `set`, `map`, `record`, `object` | Collections |\n| `union`, `intersect`, `discriminatedUnion`, `lazy` | Composition |\n| `coerce.*` | Coercing primitive schemas |\n\n```ts\nconst User = s.object({\n email: s.string().email(),\n id: s.string().uuid(),\n role: s.enum(['admin', 'member'] as const),\n});\n\ntype User = Infer<typeof User>;\n```\n\nObject schemas reject unknown keys. Use `.relaxed()` to retain extras.\n\n## Standard Schema interoperability\n\nEvery schema implements the dependency-free Standard Schema v1 structural protocol through `schema['~standard']`. Consumers that accept Standard Schema validators can use Spell schemas directly without an adapter.\n\n```ts\nconst User = s.object({ name: s.string() });\nconst result = await User['~standard'].validate({ name: 'Ada' });\n```\n\nSuccessful results contain `value`; failures contain portable `{ message, path }` issues. Failed unions remain one stable union issue instead of selecting a branch heuristically.\n\nThe exported `StandardSchemaV1` interface mirrors the full v1 protocol, including `validate(value, options?)` and the `InferInput` and `InferOutput` namespace helpers.\n\n## Parsing\n\nEvery schema provides:\n\n```ts\nschema.parse(value, context?); // Output or SpellValidationError\nschema.safeParse(value, context?); // ParseResult<Output>\nschema.parseAsync(value, context?); // Promise<Output>\nschema.safeParseAsync(value, context?); // Promise<ParseResult<Output>>\nschema.is(value); // value is Output\nschema.assert(value, label?); // assertion\n```\n\n`parse()`, `safeParse()`, `is()`, and `assert()` are available on synchronous schemas. Calling `checkAsync()` returns an async-only schema, where TypeScript exposes only `parseAsync()` and `safeParseAsync()`. That async-only mode propagates through compositional schemas when a child is asynchronous.\n\nUI adapters that accept `AnySchema` cannot call `safeParse()` on it — the structural surface type omits it. Type adapter parameters against `SyncParsable` instead:\n\n```ts\nimport type { SyncParsable } from '@vielzeug/spell';\n\nfunction spellValidator(schema: SyncParsable<unknown>) {\n const result = schema.safeParse(value); // types cleanly, no shim or cast\n}\n```\n\nThe shape is structural, so an async-only schema is assignable to `SyncParsable`; calling `safeParse()` on one returns a failed result (\"parse() cannot evaluate async checks\"). When the mode is not statically known, narrow with `InferSchemaMode` and use `safeParseAsync()` for async members.\n\n## Custom Checks\n\n`check()` is synchronous. `checkAsync()` is asynchronous. Do not return a Promise from `check()`.\n\n```ts\nconst Signup = s.object({ confirm: s.string(), password: s.string() }).check((value, context) => {\n if (value.password !== value.confirm) {\n context.addIssue({ code: 'custom', message: 'Passwords must match', path: ['confirm'] });\n }\n});\n\nconst AvailableEmail = s\n .string()\n .email()\n .checkAsync(async (value) => {\n return (await emailAvailable(value)) || 'Email is already registered';\n });\n```\n\n`CheckContext.addIssue()` takes `{ code, message, params?, path? }`. Paths are relative to current schema.\n\n## Modifiers and Transforms\n\n```ts\ns.string().optional();\ns.string().nullable();\ns.string().nullish();\ns.string().required();\ns.string().default('guest');\ns.string().catch('guest');\ns.string()\n .trim()\n .transform((value) => value.toLowerCase());\ns.string().pipe(s.string().slug());\ns.string().label('User name');\n```\n\n`default()`, `catch()`, preprocessors, transforms, and checks are runtime behavior. They cannot become portable definitions.\n\n## Definitions and JSON Schema\n\n`definition()` is only for schemas containing declarative structure. It returns frozen data and throws `SpellDefinitionError` when runtime behavior is present.\n\n```ts\nimport { s } from '@vielzeug/spell';\nimport { fromDefinition } from '@vielzeug/spell/json';\n\nconst Product = s.object({\n id: s.string().uuid(),\n name: s.string().min(1),\n});\n\nconst definition = Product.definition();\nconst jsonSchema = fromDefinition(definition);\n```\n\nNo implicit schema-to-JSON conversion exists. Make definition boundary explicit.\n\n## Parse Contexts\n\n`createParseContext()` creates immutable request-local validation messages.\n\n```ts\nfunction createParseContext(messages?: DeepPartial<Messages>): ParseContext;\n```\n\n**Returns:** A parse context with built-in messages merged with the supplied overrides.\n\n```ts\nimport { createParseContext, s } from '@vielzeug/spell';\n\nconst context = createParseContext({\n object: { invalidKeys: () => 'Unsupported field' },\n});\n\nconst result = s.object({ email: s.string().email() }).safeParse({ email: 'ada@example.com', extra: true }, context);\n\nif (!result.success) {\n const messages = result.error.messagesAt('email');\n console.log(messages);\n}\n```\n\n## Types\n\n### Inference types\n\n| Type | Resolves to |\n| --- | --- |\n| `Infer<TSchema>` | Parsed output; alias of `InferOutput<TSchema>` |\n| `InferInput<TSchema>` | Input accepted before parsing or transformation |\n| `InferOutput<TSchema>` | Value returned after parsing and transformation |\n\nComposite schemas preserve child input types. Object input types make fields optional when their field schema accepts `undefined`.\n\n### Standard Schema\n\n```ts\ninterface StandardSchemaV1<Input = unknown, Output = Input> {\n readonly '~standard': StandardSchemaV1.Props<Input, Output>;\n}\n\nnamespace StandardSchemaV1 {\n interface Props<Input = unknown, Output = Input> {\n readonly version: 1;\n readonly vendor: string;\n readonly validate: (value: unknown, options?: Options) => Result<Output> | Promise<Result<Output>>;\n readonly types?: Types<Input, Output>;\n }\n\n type Result<Output> = { readonly value: Output; readonly issues?: undefined } | { readonly issues: readonly Issue[] };\n interface Options { readonly libraryOptions?: Record<string, unknown> }\n interface Issue { readonly message: string; readonly path?: readonly (PropertyKey | PathSegment)[] }\n interface PathSegment { readonly key: PropertyKey }\n interface Types<Input = unknown, Output = Input> { readonly input: Input; readonly output: Output }\n type InferInput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['input'];\n type InferOutput<Schema extends StandardSchemaV1> = NonNullable<Schema['~standard']['types']>['output'];\n}\n```\n\n### Parse result and issues\n\n```ts\ntype ParseResult<T> = { data: T; success: true } | { error: SpellValidationError; success: false };\n\ntype Issue =\n | { code: 'custom'; message: string; params?: Record<string, unknown>; path: (string | number)[] }\n | { code: 'invalid_base64'; message: string; params: { format: string }; path: (string | number)[] }\n | { code: 'invalid_date'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_duration'; message: string; params: { format: string }; path: (string | number)[] }\n | { code: 'invalid_enum'; message: string; params: { values: readonly unknown[] }; path: (string | number)[] }\n | { code: 'invalid_finite'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_integer'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_keys'; message: string; params: { keys: string[] }; path: (string | number)[] }\n | { code: 'invalid_length'; message: string; params: { exact: number }; path: (string | number)[] }\n | { code: 'invalid_literal'; message: string; params: { expected: unknown }; path: (string | number)[] }\n | { code: 'invalid_multiple_of'; message: string; params: { step: number | bigint }; path: (string | number)[] }\n | { code: 'invalid_safe'; message: string; params?: undefined; path: (string | number)[] }\n | {\n code: 'invalid_string';\n message: string;\n params: { format?: string; includes?: string; pattern?: string; prefix?: string; suffix?: string };\n path: (string | number)[];\n }\n | { code: 'invalid_type'; message: string; params?: undefined; path: (string | number)[] }\n | { code: 'invalid_union'; message: string; params: { errors: Issue[][] }; path: (string | number)[] }\n | { code: 'invalid_unique'; message: string; params: { unique: true }; path: (string | number)[] }\n | { code: 'invalid_url'; message: string; params: { format: string }; path: (string | number)[] }\n | {\n code: 'invalid_variant';\n message: string;\n params: { discriminator: string; expected: string[] };\n path: (string | number)[];\n }\n | {\n code: 'too_big';\n message: string;\n params: { exclusive?: boolean; max: number | bigint | Date };\n path: (string | number)[];\n }\n | {\n code: 'too_small';\n message: string;\n params: { exclusive?: boolean; min: number | bigint | Date };\n path: (string | number)[];\n }\n | { code: string & {}; message: string; params?: Record<string, unknown>; path: (string | number)[] };\n```\n\n`ErrorCode` is a const object mapping each issue code to its string literal.\n\n### Validation contracts\n\n```ts\ntype ParseContext = { messages: Messages };\n\ntype CheckContext = {\n addIssue: (issue: {\n code: string;\n message: string;\n params?: Record<string, unknown>;\n path?: (string | number)[];\n }) => void;\n};\n\ntype ValidateResult = boolean | null | undefined | string;\n```\n\n### Messages\n\n```ts\ntype MessageFn<Ctx extends Record<string, unknown> = Record<string, unknown>> = string | ((ctx: Ctx) => string);\n\ntype Messages = {\n array: { length: (ctx: { exact: number; value: unknown[] }) => string; max: (ctx: { max: number; value: unknown[] }) => string; min: (ctx: { min: number; value: unknown[] }) => string; nonEmpty: () => string; type: () => string; unique: () => string };\n bigint: { max: (ctx: { max: bigint; value: bigint }) => string; min: (ctx: { min: bigint; value: bigint }) => string; multipleOf: (ctx: { step: bigint; value: bigint }) => string; negative: () => string; nonNegative: () => string; nonPositive: () => string; positive: () => string; type: () => string };\n boolean: { type: () => string };\n check: { default: () => string };\n date: { max: (ctx: { max: Date; value: Date }) => string; min: (ctx: { min: Date; value: Date }) => string; type: () => string };\n enum: { invalid: (ctx: { values: readonly unknown[] }) => string };\n instanceof: { type: (ctx: { className: string }) => string };\n literal: { expected: (ctx: { expected: unknown }) => string };\n map: { max: (ctx: { max: number; value: Map<unknown, unknown> }) => string; min: (ctx: { min: number; value: Map<unknown, unknown> }) => string; nonEmpty: () => string; size: (ctx: { exact: number; value: Map<unknown, unknown> }) => string; type: () => string };\n never: { invalid: () => string };\n number: { finite: () => string; int: () => string; max: (ctx: { max: number; value: number }) => string; min: (ctx: { min: number; value: number }) => string; multipleOf: (ctx: { step: number; value: number }) => string; negative: () => string; nonNegative: () => string; nonPositive: () => string; positive: () => string; safe: () => string; type: () => string };\n object: { invalidKeys: (ctx: { keys: string[] }) => string; type: () => string };\n set: { max: (ctx: { max: number; value: Set<unknown> }) => string; min: (ctx: { min: number; value: Set<unknown> }) => string; nonEmpty: () => string; size: (ctx: { exact: number; value: Set<unknown> }) => string; type: () => string };\n string: { base64: () => string; base64url: () => string; cuid: () => string; cuid2: () => string; date: () => string; dateTime: () => string; duration: () => string; email: () => string; emoji: () => string; endsWith: (ctx: { suffix: string; value: string }) => string; hex: () => string; hexColor: () => string; includes: (ctx: { substr: string; value: string }) => string; ip: () => string; jwt: () => string; length: (ctx: { exact: number; value: string }) => string; max: (ctx: { max: number; value: string }) => string; min: (ctx: { min: number; value: string }) => string; nanoid: () => string; nonEmpty: () => string; numeric: () => string; regex: (ctx: { value: string }) => string; semver: () => string; slug: () => string; startsWith: (ctx: { prefix: string; value: string }) => string; time: () => string; type: () => string; ulid: () => string; url: () => string; uuid: () => string };\n tuple: { length: (ctx: { exact: number }) => string; min: (ctx: { min: number }) => string; type: () => string };\n union: { invalid: () => string };\n variant: { invalidDiscriminator: (ctx: { discriminator: string; expected: string[] }) => string; type: () => string };\n};\n\ntype DeepPartial<T> = {\n [K in keyof T]?: T[K] extends Record<string, unknown> ? DeepPartial<T[K]> : T[K];\n};\n```\n\n### Descriptor and JSON Schema\n\n```ts\ntype SchemaDescriptor = BaseDescriptor &\n (\n | { kind: 'unknown' | 'never' | 'boolean' | 'bigint' | 'date' | 'lazy' }\n | { className: string; kind: 'instanceof' }\n | { contentEncoding?: string; format?: string; kind: 'string'; maxLength?: number; minLength?: number; pattern?: string | null }\n | { exclusiveMaximum?: number; exclusiveMinimum?: number; kind: 'number'; maximum?: number; minimum?: number; multipleOf?: number; typeHint?: 'integer' }\n | { kind: 'literal'; value: string | number | boolean | null | undefined }\n | { kind: 'enum'; values: readonly (string | number)[] }\n | { items: SchemaDescriptor; kind: 'array'; maxItems?: number; minItems?: number }\n | { items: SchemaDescriptor[]; kind: 'tuple'; rest: SchemaDescriptor | null }\n | { fields: Record<string, SchemaDescriptor>; kind: 'object'; strict: boolean }\n | { key: SchemaDescriptor; kind: 'record'; value: SchemaDescriptor }\n | { items: SchemaDescriptor; kind: 'set' }\n | { key: SchemaDescriptor; kind: 'map'; value: SchemaDescriptor }\n | { branches: SchemaDescriptor[]; kind: 'union' | 'intersect' }\n | { branches: Record<string, SchemaDescriptor>; discriminator: string; kind: 'variant' }\n | { from: SchemaDescriptor; kind: 'pipe'; to: SchemaDescriptor }\n );\n\ntype JsonSchema = Record<string, unknown>;\n```\n\n### Schema traversal\n\n`SchemaWalker<R>` is the visitor type accepted by `schema.walk()`. Handlers are optional and correspond to schema kinds; composite handlers also receive their walked children. Use `unknown` as the fallback handler.\n\n### Error helpers\n\n```ts\ntype FlatError = { messages: string[]; path: (string | number)[] };\ntype FlatErrorFirst = { message: string; path: (string | number)[] };\n\n/** An issue path in either shape: spell-native segments or Standard Schema `{ key }` segments. */\ntype IssuePathSegment = PropertyKey | { readonly key: PropertyKey };\ntype IssuePath = readonly IssuePathSegment[];\n\n/** The synchronous parse surface; `AnySchema` omits `safeParse`, adapter parameters use this. */\ntype SyncParsable<T = unknown> = { safeParse(value: unknown): ParseResult<T> };\n\nfunction joinIssuePath(path: IssuePath | undefined, separator?: string): string;\n```\n\n`joinIssuePath()` flattens an issue path into a UI key such as `'permissions.0.slug'`. It accepts spell-native `(string | number)[]` paths and Standard Schema paths with `{ key }` segments, and returns `''` for empty or undefined paths.\n\n## Errors\n\n- `SpellError` — base class. Use `instanceof SpellError` for cross-boundary narrowing.\n- `SpellValidationError` — validation failure with `issues`, `messagesAt()`, `flatten()`, and `flattenFirst()`.\n- `SpellDefinitionError` — schema cannot create portable definition.\n\n```ts\nconst result = s.object({ email: s.string().email() }).safeParse({ email: 'invalid' });\n\nif (!result.success) {\n const { fieldErrors, formErrors } = result.error.flatten();\n console.log(fieldErrors, formErrors);\n}\n```\n",
6
+ "usage": "---\ntitle: Spell — Usage Guide\ndescription: Learn how to build schemas, compose wrappers, customize locales, and integrate spell with other Vielzeug packages.\n---\n\n[[toc]]\n\n## Basic Usage\n\nStart with `safeParse()` when you want explicit success and failure branches.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Signup = s.object({\n email: s.string().email(),\n password: s.string().min(12),\n referralCode: s.string().optional(),\n});\n\nconst result = Signup.safeParse({\n email: 'ada@example.com',\n password: 'horse-battery-staple',\n});\n\nif (!result.success) {\n console.error(result.error.issues);\n} else {\n console.log(result.data.email);\n}\n```\n\nUse `parse()` when invalid input should throw immediately. Use `safeParse()` when invalid input is part of normal control flow.\n\n## Building Schemas\n\nUse the namespace form when readability matters more than bundle trimming.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Article = s.object({\n id: s.string().uuid(),\n title: s.string().trim().min(1).max(120),\n slug: s.string().slug(),\n tags: s.array(s.string().min(1)).default(() => []),\n meta: s\n .object({\n published: s.boolean(),\n publishedAt: s.date().nullable(),\n })\n .relaxed(),\n});\n```\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Todo = s.object({\n done: s.boolean(),\n tags: s.array(s.string().min(1)).default(() => []),\n title: s.string().min(1),\n});\n```\n\nObject schemas reject unknown keys by default. Call `.relaxed()` when you need to preserve extra properties.\n\nCall `.defaults()` to get a fully default-filled object without providing any input. Every required field must have a `.default()` set, or a `SpellValidationError` is thrown. Call `.partialDefaults()` when only some fields have defaults — fields without a default are silently omitted instead of throwing.\n\n```ts\nconst Config = s.object({\n host: s.string().default('localhost'),\n port: s.number().default(3000),\n});\n\nConfig.defaults(); // { host: 'localhost', port: 3000 }\n\nconst Form = s.object({ name: s.string(), role: s.string().default('viewer') });\nForm.partialDefaults(); // { role: 'viewer' }\n```\n\n## Wrapper Modes, Defaults, and Fallbacks\n\nChain wrappers to describe missing values and recovery rules without losing schema metadata.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst DisplayName = s.string().trim().min(2).label('Display name').optional().default('Guest').nullable();\n\nDisplayName.parse(undefined); // 'Guest'\nDisplayName.parse(null); // null\nDisplayName.description; // 'Display name'\n```\n\nCall `.required()` to remove `undefined` without removing `null`.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst NullableButRequired = s.string().optional().nullable().required();\n\nNullableButRequired.parse('Ada');\nNullableButRequired.parse(null);\n// NullableButRequired.parse(undefined); // throws\n```\n\nUse `.catch()` when you want a fallback output after validation fails.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Port = s.number().int().min(1).max(65535).catch(3000);\n\nPort.parse('not-a-number'); // 3000\n```\n\n## Custom Validation\n\nUse `check()` for synchronous domain rules and `checkAsync()` for asynchronous rules. Sync parsing rejects schemas with asynchronous checks.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\n// Boolean shorthand: return false to fail with default message\nconst EvenNumber = s.number().check((n) => n % 2 === 0);\n\n// String shorthand: return the message as a string\nconst Username = s\n .string()\n .min(3)\n .check((v) => !v.startsWith('_') || 'Cannot start with underscore');\n\n// Multiple issues via ctx.addIssue()\nconst Signup = s.object({ confirm: s.string(), password: s.string() }).check((v, ctx) => {\n if (v.password !== v.confirm) {\n ctx.addIssue({ code: 'custom', message: 'Passwords must match', path: ['confirm'] });\n }\n});\n```\n\n`checkAsync()` returns an async-only schema: TypeScript exposes `parseAsync()` and `safeParseAsync()` but not `parse()` or `safeParse()`. This mode survives fluent modifiers and propagates through nested arrays, objects, unions, intersections, tuples, maps, records, sets, lazy schemas, pipelines, and `s.discriminatedUnion(...)` branches. Sync parsing also fails at runtime instead of accepting an unchecked value.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst takenEmails = new Set(['ada@example.com']);\n\nconst AccountEmail = s\n .string()\n .email()\n .checkAsync(async (value, ctx) => {\n if (takenEmails.has(value)) {\n ctx.addIssue({ code: 'custom', message: 'Email is already taken', path: [] });\n }\n });\n\n// Async checks require parseAsync\nawait AccountEmail.parseAsync('grace@example.com');\n```\n\nUse `check()` for predicate-only rules too. Return `true` on success or message on failure.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst PositivePrice = s.number().check((value) => value > 0 || 'Must be positive');\nPositivePrice.parse(9.99);\n```\n\n## Strings, Numbers, and Safe Regex Usage\n\nUse schema helpers for common string and number constraints instead of hand-written predicates.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Password = s.string().min(12).regex(/[A-Z]/).regex(/[0-9]/);\nconst Price = s.number().nonNegative().multipleOf(0.01);\nconst LaunchWindow = s.date().min(new Date('2025-01-01T00:00:00.000Z'));\n```\n\nSpell strips stateful `/g` and `/y` flags from `regex()` patterns before validation. Repeated parses stay deterministic even when the original regular expression is reused.\n\n## Coercion and Transforms\n\nUse coercion when input arrives as strings, query parameters, or form values.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst Query = s.object({\n draft: s.coerce.boolean().default(false),\n limit: s.coerce.number().int().positive().default(20),\n publishedAt: s.coerce.date().nullable(),\n search: s.coerce.string().trim().min(1).optional(),\n});\n\nconst parsed = Query.parse({\n draft: 'true',\n limit: '50',\n publishedAt: '2025-04-01T12:00:00.000Z',\n search: ' vielzeug ',\n});\n```\n\nUse `transform()` or `pipe()` after validation when downstream code needs a different output shape.\n\n```ts\nimport { s } from '@vielzeug/spell';\n\nconst TrimmedTags = s.array(s.string().trim().min(1)).transform((tags) => tags.map((tag) => tag.toLowerCase()));\nconst Slug = s.string().trim().min(1).pipe(s.string().slug());\n```\n\n## Introspection, Round-Trips, and JSON Schema\n\nUse declarative definitions when schemas need to cross process boundaries or feed tooling.\n\n```ts\nimport { s } from '@vielzeug/spell';\nimport { fromDefinition } from '@vielzeug/spell/json';\n\nconst Product = s\n .object({\n id: s.string().uuid(),\n name: s.string().min(1),\n price: s.number().positive().multipleOf(0.01),\n })\n .label('Product');\n\nconst definition = Product.definition();\nconst jsonSchema = fromDefinition(definition);\n\nProduct.parse({ id: '550e8400-e29b-41d4-a716-446655440000', name: 'Keyboard', price: 129.99 });\nconsole.log(jsonSchema.title);\n```\n\nDefinitions are frozen serializable snapshots of declarative schema structure. Use `definition()` and `fromDefinition()` for external tooling. Schemas with runtime checks, transforms, defaults, catches, or preprocessors intentionally have no definition.\n\n## Messages\n\nSpell has no mutable process-wide configuration. Build one parse context per request, locale, or form, then pass it explicitly.\n\n```ts\nimport { createParseContext, s } from '@vielzeug/spell';\n\nconst User = s.object({ email: s.string().email() });\nconst german = createParseContext({\n object: { invalidKeys: () => 'Keine unbekannten Felder erlaubt' },\n});\n\nUser.safeParse({ email: 'ada@example.com', extra: true }, german);\n```\n\nInternal development warnings always use `console.warn` in development builds. Route application diagnostics in application code instead of mutating library-wide logger state.\n\n## Working with Validation Errors\n\nUse `SpellValidationError` helpers when you need UI-ready error structures.\n\n```ts\nimport { s, SpellValidationError } from '@vielzeug/spell';\n\nconst User = s.object({\n email: s.string().email(),\n profile: s.object({\n name: s.string().min(2),\n }),\n});\n\nconst result = User.safeParse({ email: 'nope', profile: { name: '' } });\n\nif (!result.success && result.error instanceof SpellValidationError) {\n const profileErrors = result.error.messagesAt('profile', 'name');\n console.log(profileErrors);\n}\n```\n\nKey inline errors by dot-path with `joinIssuePath()`, which also unwraps Standard Schema `{ key }` segments:\n\n```ts\nimport { joinIssuePath } from '@vielzeug/spell';\n\nfor (const issue of result.error.issues) {\n console.log(joinIssuePath(issue.path), issue.message); // 'profile.name: ...'\n}\n```\n\nUnion failures produce one stable `invalid_union` issue. Use `s.discriminatedUnion()` when a discriminator can identify the intended object branch and provide field-specific errors.\n\n## Validating synchronously in React\n\nAdapters that validate inside a render or `validate` callback need the sync parse surface. `AnySchema` does not expose `safeParse`; type the adapter parameter against `SyncParsable` instead:\n\n```ts\nimport { type SyncParsable } from '@vielzeug/spell';\n\nfunction spellValidator(schema: SyncParsable<unknown>) {\n return (values: unknown) => {\n const result = schema.safeParse(values);\n if (result.success) return undefined;\n\n return result.error.issues.map((issue) => ({\n message: issue.message,\n path: issue.path,\n }));\n };\n}\n\nspellValidator(s.object({ email: s.string() })); // no shim or cast\n```\n\n## Schema Traversal with walk()\n\nUse `walk()` to inspect or transform a schema tree without importing internal implementation classes.\n\n```ts\nimport { s, type SchemaWalker } from '@vielzeug/spell';\n\nconst fields: string[] = [];\n\nconst collectFields: SchemaWalker<void> = {\n object(schema) {\n for (const [key, child] of Object.entries(schema.shape)) {\n fields.push(key);\n child.walk(collectFields);\n }\n },\n unknown() {},\n};\n\nconst User = s.object({\n email: s.string().email(),\n profile: s.object({ name: s.string() }),\n});\n\nUser.walk(collectFields);\nconsole.log(fields); // ['email', 'profile', 'name']\n```\n\n`walk()` dispatches by `schema.kind`. If no handler matches and no `unknown` fallback is provided, `walk()` returns `null`. Add an `unknown` handler to capture any kind not explicitly listed in your visitor.\n\n## Framework Integration\n\nSpell works anywhere you can call a function before state enters your app.\n\n::: code-group\n\n```tsx [React]\nimport { s } from '@vielzeug/spell';\n\nconst SearchParams = s\n .object({\n page: s.coerce.number().int().positive().default(1),\n q: s.string().trim().optional(),\n })\n .relaxed();\n\nexport function SearchPage({ rawParams }: { rawParams: unknown }) {\n const params = SearchParams.parse(rawParams);\n\n return (\n <div>\n {params.q ?? 'All results'} — page {params.page}\n </div>\n );\n}\n```\n\n```ts [Vue]\nimport { computed, ref } from 'vue';\nimport { s } from '@vielzeug/spell';\n\nconst Settings = s.object({\n locale: s.string().min(2),\n compact: s.coerce.boolean().default(false),\n});\n\nconst raw = ref<unknown>({ locale: 'en', compact: 'true' });\nconst settings = computed(() => Settings.parse(raw.value));\n```\n\n:::\n\nUse `safeParse()` at event boundaries and `parse()` inside trusted data flows.\n\n## Working with Other Vielzeug Libraries\n\nUse Spell as the validation layer and let other packages focus on transport, forms, or storage.\n\n```ts\nimport { createForm } from '@vielzeug/forge';\nimport { schemaValidator } from '@vielzeug/forge/schema';\nimport { createCourier } from '@vielzeug/courier';\nimport { s } from '@vielzeug/spell';\n\nconst Profile = s.object({\n displayName: s.string().min(2),\n newsletter: s.boolean(),\n});\n\nconst form = createForm({\n initialValues: {\n displayName: '',\n newsletter: false,\n },\n validate: schemaValidator(Profile),\n});\n\nconst courier = createCourier({ baseUrl: '/api' });\nconst profile = Profile.parse(await courier.get('/profile'));\n```\n\nUse Spell definitions with `@vielzeug/codex` or other tooling when you need generated docs or external schema consumers.\n\n## Best Practices\n\n- Keep schemas close to the boundary where unknown data enters your app.\n- Use `s` consistently for construction; use explicit `/json` and `/predicates` subpaths for tooling.\n- Use `.default(() => value)` for mutable defaults such as arrays, objects, `Map`, and `Set`.\n- Call `.required()` when you want to remove `undefined` but keep `null` semantics intact.\n- Use `check()` with a `ctx` argument when you need `ctx.addIssue()`; return a message for simple predicate failures.\n- Use `checkAsync()` and `parseAsync()` for every asynchronous domain rule.\n- Build a parse context per request or test; never rely on mutable process-wide configuration.\n- Use `definition()` with `fromDefinition()` from `@vielzeug/spell/json` for external tooling.\n",
7
7
  "examples": "---\ntitle: Spell — Examples\ndescription: Practical examples and recipes for spell.\n---\n\n## Examples\n\n- [Validating API Payloads](./examples/api.md)\n- [Form-Safe Parsing](./examples/forms.md)\n- [Async Business Rules](./examples/async.md)\n- [Schema Introspection and Round-Trips](./examples/introspection.md)\n- [Unions, Intersections, and Variants](./examples/unions.md)\n- [Schema Traversal with walk()](./examples/walk.md)\n"
8
8
  },
9
9
  "examples": [
@@ -99,28 +99,32 @@
99
99
  }
100
100
  ],
101
101
  "typeSignatures": {
102
- "AnySchema": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
103
- "CheckContext": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
104
- "FlatError": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
105
- "FlatErrorFirst": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
106
- "Infer": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
107
- "InferInput": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
108
- "InferOutput": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
109
- "Issue": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
110
- "JsonSchema": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
111
- "MessageFn": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
112
- "Messages": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
113
- "ParseContext": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
114
- "ParseResult": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
115
- "SchemaDescriptor": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
116
- "SchemaWalker": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
117
- "StandardSchemaV1": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
118
- "ValidateResult": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n ValidateResult,\n} from './core';",
119
- "ErrorCode": "export { ErrorCode, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
120
- "Schema": "export { ErrorCode, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
121
- "SpellDefinitionError": "export { ErrorCode, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
122
- "SpellError": "export { ErrorCode, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
123
- "SpellValidationError": "export { ErrorCode, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
102
+ "AnySchema": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
103
+ "CheckContext": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
104
+ "FlatError": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
105
+ "FlatErrorFirst": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
106
+ "Infer": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
107
+ "InferInput": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
108
+ "InferOutput": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
109
+ "Issue": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
110
+ "IssuePath": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
111
+ "IssuePathSegment": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
112
+ "JsonSchema": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
113
+ "MessageFn": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
114
+ "Messages": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
115
+ "ParseContext": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
116
+ "ParseResult": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
117
+ "SchemaDescriptor": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
118
+ "SchemaWalker": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
119
+ "StandardSchemaV1": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
120
+ "SyncParsable": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
121
+ "ValidateResult": "export type {\n AnySchema,\n CheckContext,\n FlatError,\n FlatErrorFirst,\n Infer,\n InferInput,\n InferOutput,\n Issue,\n IssuePath,\n IssuePathSegment,\n JsonSchema,\n MessageFn,\n Messages,\n ParseContext,\n ParseResult,\n SchemaDescriptor,\n SchemaWalker,\n StandardSchemaV1,\n SyncParsable,\n ValidateResult,\n} from './core';",
122
+ "ErrorCode": "export { ErrorCode, joinIssuePath, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
123
+ "joinIssuePath": "export { ErrorCode, joinIssuePath, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
124
+ "Schema": "export { ErrorCode, joinIssuePath, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
125
+ "SpellDefinitionError": "export { ErrorCode, joinIssuePath, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
126
+ "SpellError": "export { ErrorCode, joinIssuePath, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
127
+ "SpellValidationError": "export { ErrorCode, joinIssuePath, Schema, SpellDefinitionError, SpellError, SpellValidationError } from './core';",
124
128
  "createParseContext": "export { createParseContext, type DeepPartial } from './messages';",
125
129
  "DeepPartial": "export { createParseContext, type DeepPartial } from './messages';",
126
130
  "s": "export { s } from './s';"