-
-
Notifications
You must be signed in to change notification settings - Fork 0
feat(codecs): Standard Schema wraps; remove zod codec #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| "@stainless-code/persist": minor | ||
| --- | ||
|
|
||
| Add `./codecs/standard-schema` — sync `~standard` codec plus `PersistStorage` wraps (`withStandardSchema` / `withStandardSchemaAsync`) and JSON factories (`createStandardSchemaStorage` / `createStandardSchemaStorageAsync`). Types vendored; no runtime peer. | ||
|
|
||
| Remove `./codecs/zod`. Migrate to `createStandardSchemaStorage(getStorage, schema)` (Zod ≥3.24 / v4 via `~standard`). Encode now writes defaults/transforms (replaces side-effect `parse`). Yup / Promise-returning `~standard.validate` → async wrap or factory (always-async hydrate — gate UI). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,18 @@ | ||
| --- | ||
| title: Choosing a codec | ||
| description: Pick jsonCodec, serovalCodec, zodCodec, or identityCodec for your Persist wire format. | ||
| description: Pick a Persist wire format — Set/Map/Date, schema validation, or structured-clone backends. | ||
| search: | ||
| tags: ["concept", "codec"] | ||
| --- | ||
|
|
||
| Pick by whether you need Set/Map/Date round-trips, schema-gated persistence, or a structured-clone backend. `identityCodec` only with structured-clone backends (IDB) — never string-wire. | ||
| localStorage stays JSON by default. Reach for seroval when Set/Map/Date must round-trip; Standard Schema when invalid prefs must never write; `identityCodec` only with structured-clone backends (IDB) — never string-wire. | ||
|
|
||
| | Codec | Set/Map/Date | Wire type | Schema validation | For backend | Subpath | | ||
| | --------------------- | ------------------------------------------ | -------------------------- | ----------------- | --------------------------- | ------------------------ | | ||
| | `jsonCodec` | ❌ | string | ❌ | string-wire | core | | ||
| | `serovalCodec` | ✅ | string (JSON-shaped) | ❌ | string-wire | `./codecs/seroval` | | ||
| | `zodCodec` | ❌ (JSON wire; only via schema transforms) | string | ✅ | string-wire | `./codecs/zod` | | ||
| | `identityCodec` | ✅ (structured clone) | `StorageValue<S>` (object) | ❌ | structured-clone only (IDB) | core | | ||
| | custom `StorageCodec` | yours | yours | yours | any | custom `encode`/`decode` | | ||
| | Codec | Set/Map/Date | Wire type | Schema validation | For backend | Subpath | | ||
| | --------------------- | ------------------------------------------ | -------------------------- | ----------------- | --------------------------- | -------------------------- | | ||
| | `jsonCodec` | ❌ | string | ❌ | string-wire | core | | ||
| | `serovalCodec` | ✅ | string (JSON-shaped) | ❌ | string-wire | `./codecs/seroval` | | ||
| | `standardSchemaCodec` | ❌ (JSON wire; only via schema transforms) | string | ✅ | string-wire | `./codecs/standard-schema` | | ||
| | `identityCodec` | ✅ (structured clone) | `StorageValue<S>` (object) | ❌ | structured-clone only (IDB) | core | | ||
| | custom `StorageCodec` | yours | yours | yours | any | custom `encode`/`decode` | | ||
|
|
||
| See [The three seams](/concepts/three-seams) for composition examples. | ||
| `standardSchemaCodec` accepts any sync [`~standard`](https://standardschema.dev/) schema and persists its `value`. Schema gating also composes via `withStandardSchema` / `withStandardSchemaAsync` over any `PersistStorage` (sync vs async lanes) — library matrix and Yup notes in the [Standard Schema recipe](/recipes/standard-schema). Composition: [The three seams](/concepts/three-seams). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,20 @@ | ||
| --- | ||
| title: Recipes | ||
| description: Copy-paste Persist patterns — encryption, options, wrapping stores, cross-tab, zod, React Native, and Node fs. | ||
| description: Copy-paste Persist patterns — composition, options, stores, cross-tab, Standard Schema, React Native, Node fs. | ||
| search: | ||
| tags: ["recipe"] | ||
| --- | ||
|
|
||
| Composable patterns over the three seams. Start with [Composition](/recipes/composition) for encrypt/compress stacks; use the pages below for options and store adapters. | ||
|
|
||
| | Recipe | Topic | | ||
| | ------------------------------------------- | ------------------------------------------------------------------------------------------ | | ||
| | [Composition](/recipes/composition) | Encrypt, compress, legacy IDB string payloads, namespaced stores | | ||
| | [Options](/recipes/options) | Clear-all, partialize, merge, retryWrite, throttleMs, maxAge, buster, migrate, skipPersist | | ||
| | [Wrapping stores](/recipes/wrapping-stores) | zustand, jotai, valtio, mobx, pinia, redux, custom `PersistableSource` | | ||
| | [Cross-tab](/recipes/crosstab) | localStorage sync and BroadcastChannel over IndexedDB | | ||
| | [Zod](/recipes/zod) | Schema-gated persist with `createZodStorage` | | ||
| | [React Native](/recipes/react-native) | AsyncStorage, MMKV, Expo Secure Store | | ||
| | [Node fs](/recipes/node-fs) | Server / SSR / CLI file-backed storage | | ||
| | Recipe | Topic | | ||
| | ------------------------------------------------- | ------------------------------------------------------------------------------------------ | | ||
| | [Composition](/recipes/composition) | Encrypt, compress, legacy IDB string payloads, namespaced stores | | ||
| | [Options](/recipes/options) | Clear-all, partialize, merge, retryWrite, throttleMs, maxAge, buster, migrate, skipPersist | | ||
| | [Wrapping stores](/recipes/wrapping-stores) | zustand, jotai, valtio, mobx, pinia, redux, custom `PersistableSource` | | ||
| | [Cross-tab](/recipes/crosstab) | localStorage sync and BroadcastChannel over IndexedDB | | ||
| | [Standard Schema](/recipes/standard-schema) | Schema-gated persist with any `~standard` library | | ||
| | [React Native](/recipes/react-native) | AsyncStorage, MMKV, Expo Secure Store | | ||
| | [Node fs](/recipes/node-fs) | Server / SSR / CLI file-backed storage | | ||
|
|
||
| Caveats that matter per backend: async backends (IDB, AsyncStorage, Secure Store) can't settle hydration before first paint → gate UI on a framework adapter; `sessionStorage` is per-tab (crossTab is meaningless); `identityCodec` never with string-only backends. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| --- | ||
| title: Standard Schema–validated storage | ||
| description: Schema-gate PersistStorage with any ~standard library — sync/async wraps or JSON factories. | ||
| search: | ||
| tags: ["recipe", "standard-schema", "codec"] | ||
| --- | ||
|
|
||
| Invalid prefs must not survive a refresh. Pass any sync `~standard` schema — Persist validates on write and on read; `clearCorruptOnFailure` deletes the key. | ||
|
|
||
| ```ts | ||
| import { z } from "zod"; | ||
| // or: import * as v from "valibot"; // any ~standard schema works | ||
| import { createStandardSchemaStorage } from "@stainless-code/persist/codecs/standard-schema"; | ||
| import { persistStore } from "@stainless-code/persist/sources/zustand"; | ||
|
|
||
| const prefs = z.object({ | ||
| theme: z.enum(["light", "dark"]), | ||
| density: z.enum(["comfortable", "compact"]).default("comfortable"), | ||
| }); | ||
| type Prefs = z.infer<typeof prefs>; | ||
|
|
||
| const storage = createStandardSchemaStorage<Prefs>(() => localStorage, prefs, { | ||
| clearCorruptOnFailure: true, | ||
| })!; | ||
|
|
||
| persistStore(usePrefs, { | ||
| name: "app:prefs:v1", | ||
| storage, | ||
| onError: (error, { phase }) => { | ||
| // phase "write" = schema rejected outbound state | ||
| report({ error, phase }); | ||
| }, | ||
| }); | ||
| ``` | ||
|
|
||
| `createStandardSchemaStorage` is JSON sugar over `withStandardSchema`. Wraps are the primitive — compose after any `PersistStorage` (don't stack wrap + `standardSchemaCodec`; that double-validates): | ||
|
|
||
| ```ts | ||
| import { createIdbStorage } from "@stainless-code/persist/backends/idb"; | ||
| import { withStandardSchema } from "@stainless-code/persist/codecs/standard-schema"; | ||
|
|
||
| const storage = withStandardSchema(createIdbStorage<Prefs>()!, prefs, { | ||
| clearCorruptOnFailure: true, | ||
| }); | ||
| ``` | ||
|
|
||
| ### Async lane (Yup / Promise-returning validate) | ||
|
|
||
| `~standard.validate` may return a `Promise` (Yup, async refine). Codecs stay sync — use the async wrap / factory: | ||
|
|
||
| ```ts | ||
| import { createStandardSchemaStorageAsync } from "@stainless-code/persist/codecs/standard-schema"; | ||
|
|
||
| const storage = createStandardSchemaStorageAsync(() => localStorage, yupSchema)!; | ||
| // or: withStandardSchemaAsync(anyPersistStorage, yupSchema) | ||
| ``` | ||
|
|
||
| That lane forces async hydrate even over localStorage — gate UI like IDB (`useHydrated`). Prefer the sync wrap when validate is sync. | ||
|
|
||
| Anything with sync `~standard.validate` works on the sync lane. Fact-checked implementers: | ||
|
|
||
| - [ArkType](https://github.com/arktypeio/arktype), [Valibot](https://github.com/fabian-hiller/valibot), [Zod](https://github.com/colinhacks/zod) (v3.24+), [Raptor Validator](https://raptorjs.com/docs/validation) (v0.9.0+) — on the [official list](https://standardschema.dev/) | ||
| - [Typia](https://typia.io/docs/validators/validate/) — `createValidate` / `createValidateEquals` (v9.2+) | ||
| - [Effect Schema](https://effect.website/docs/schema/standard-schema/) — wrap with `Schema.standardSchemaV1` | ||
| - [TypeMap](https://github.com/sinclairzx81/typemap) — `Compile(...)` for TypeBox (and others); TypeBox schematics alone do **not** implement `~standard` | ||
|
|
||
| [Yup](https://github.com/jquense/yup) implements the spec, but its `~standard.validate` returns a `Promise` — use the async lane above (or a sync Standard Schema wrapper around `validateSync`). | ||
|
|
||
| Encode writes the schema `value` (defaults/transforms). Date/Map/Set without a schema → [`./codecs/seroval`](/concepts/choosing-codec). |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.