@victorylabs/params 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/chunk-43PUAYQP.js +573 -0
- package/dist/chunk-43PUAYQP.js.map +1 -0
- package/dist/chunk-4T4THPFW.js +100 -0
- package/dist/chunk-4T4THPFW.js.map +1 -0
- package/dist/chunk-5NSLHAHG.js +26 -0
- package/dist/chunk-5NSLHAHG.js.map +1 -0
- package/dist/chunk-NHCH2WKC.js +96 -0
- package/dist/chunk-NHCH2WKC.js.map +1 -0
- package/dist/chunk-NUO3GOXV.js +72 -0
- package/dist/chunk-NUO3GOXV.js.map +1 -0
- package/dist/devtools.cjs +41 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +45 -0
- package/dist/devtools.d.ts +45 -0
- package/dist/devtools.js +16 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.cjs +777 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/forms-reverse.cjs +777 -0
- package/dist/integrations/forms-reverse.cjs.map +1 -0
- package/dist/integrations/forms-reverse.d.cts +32 -0
- package/dist/integrations/forms-reverse.d.ts +32 -0
- package/dist/integrations/forms-reverse.js +73 -0
- package/dist/integrations/forms-reverse.js.map +1 -0
- package/dist/integrations/forms.cjs +771 -0
- package/dist/integrations/forms.cjs.map +1 -0
- package/dist/integrations/forms.d.cts +25 -0
- package/dist/integrations/forms.d.ts +25 -0
- package/dist/integrations/forms.js +65 -0
- package/dist/integrations/forms.js.map +1 -0
- package/dist/params-store-Cgbtn53j.d.cts +115 -0
- package/dist/params-store-CguA9-yr.d.ts +115 -0
- package/dist/react.cjs +910 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +75 -0
- package/dist/react.d.ts +75 -0
- package/dist/react.js +202 -0
- package/dist/react.js.map +1 -0
- package/dist/snapshot.cjs +75 -0
- package/dist/snapshot.cjs.map +1 -0
- package/dist/snapshot.d.cts +42 -0
- package/dist/snapshot.d.ts +42 -0
- package/dist/snapshot.js +42 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/storage/compose.cjs +196 -0
- package/dist/storage/compose.cjs.map +1 -0
- package/dist/storage/compose.d.cts +35 -0
- package/dist/storage/compose.d.ts +35 -0
- package/dist/storage/compose.js +123 -0
- package/dist/storage/compose.js.map +1 -0
- package/dist/storage/cookie.cjs +136 -0
- package/dist/storage/cookie.cjs.map +1 -0
- package/dist/storage/cookie.d.cts +57 -0
- package/dist/storage/cookie.d.ts +57 -0
- package/dist/storage/cookie.js +111 -0
- package/dist/storage/cookie.js.map +1 -0
- package/dist/storage/idb.cjs +144 -0
- package/dist/storage/idb.cjs.map +1 -0
- package/dist/storage/idb.d.cts +31 -0
- package/dist/storage/idb.d.ts +31 -0
- package/dist/storage/idb.js +119 -0
- package/dist/storage/idb.js.map +1 -0
- package/dist/storage/local.cjs +121 -0
- package/dist/storage/local.cjs.map +1 -0
- package/dist/storage/local.d.cts +23 -0
- package/dist/storage/local.d.ts +23 -0
- package/dist/storage/local.js +9 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/server.cjs +158 -0
- package/dist/storage/server.cjs.map +1 -0
- package/dist/storage/server.d.cts +57 -0
- package/dist/storage/server.d.ts +57 -0
- package/dist/storage/server.js +133 -0
- package/dist/storage/server.js.map +1 -0
- package/dist/storage/session.cjs +123 -0
- package/dist/storage/session.cjs.map +1 -0
- package/dist/storage/session.d.cts +14 -0
- package/dist/storage/session.d.ts +14 -0
- package/dist/storage/session.js +12 -0
- package/dist/storage/session.js.map +1 -0
- package/dist/storage/url.cjs +132 -0
- package/dist/storage/url.cjs.map +1 -0
- package/dist/storage/url.d.cts +37 -0
- package/dist/storage/url.d.ts +37 -0
- package/dist/storage/url.js +100 -0
- package/dist/storage/url.js.map +1 -0
- package/dist/storage-DBLIRR-4.d.cts +59 -0
- package/dist/storage-DBLIRR-4.d.ts +59 -0
- package/dist/types-BSWKH-jw.d.cts +68 -0
- package/dist/types-BUmNpSyP.d.ts +68 -0
- package/package.json +114 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defaultSerialize
|
|
3
|
+
} from "../chunk-NUO3GOXV.js";
|
|
4
|
+
|
|
5
|
+
// src/storage/url/index.ts
|
|
6
|
+
var isClient = typeof window !== "undefined";
|
|
7
|
+
function urlStorage(options = {}) {
|
|
8
|
+
const adapterStrategy = options.strategy ?? "replace";
|
|
9
|
+
const paramMap = options.paramMap ?? {};
|
|
10
|
+
const reverseMap = buildReverseMap(paramMap);
|
|
11
|
+
const serialize = options.serialize ?? {};
|
|
12
|
+
const deserialize = options.deserialize ?? {};
|
|
13
|
+
const readImpl = () => {
|
|
14
|
+
if (!isClient) return void 0;
|
|
15
|
+
try {
|
|
16
|
+
const params = new URLSearchParams(window.location.search);
|
|
17
|
+
const partial = {};
|
|
18
|
+
let hasAny = false;
|
|
19
|
+
for (const [paramKey, raw] of params.entries()) {
|
|
20
|
+
const formPath = reverseMap[paramKey] ?? paramKey;
|
|
21
|
+
const deserializer = deserialize[formPath];
|
|
22
|
+
try {
|
|
23
|
+
partial[formPath] = deserializer ? deserializer(raw) : raw;
|
|
24
|
+
hasAny = true;
|
|
25
|
+
} catch {
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return hasAny ? partial : void 0;
|
|
29
|
+
} catch {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const updateUrl = (params, opts) => {
|
|
34
|
+
if (!isClient) return;
|
|
35
|
+
const search = params.toString();
|
|
36
|
+
const url = search ? `${window.location.pathname}?${search}` : window.location.pathname;
|
|
37
|
+
const strategy = opts?.history ?? adapterStrategy;
|
|
38
|
+
try {
|
|
39
|
+
if (strategy === "push") {
|
|
40
|
+
window.history.pushState(null, "", url);
|
|
41
|
+
} else {
|
|
42
|
+
window.history.replaceState(null, "", url);
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
name: "urlStorage",
|
|
49
|
+
clientOnly: true,
|
|
50
|
+
read: readImpl,
|
|
51
|
+
write: (values, _changed, opts) => {
|
|
52
|
+
if (!isClient) return;
|
|
53
|
+
const params = new URLSearchParams(window.location.search);
|
|
54
|
+
for (const [path, value] of Object.entries(values)) {
|
|
55
|
+
const paramKey = paramMap[path] ?? path;
|
|
56
|
+
if (value === void 0 || value === null) {
|
|
57
|
+
params.delete(paramKey);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const serializer = serialize[path];
|
|
61
|
+
const str = serializer ? serializer(value) : defaultSerialize(value);
|
|
62
|
+
params.set(paramKey, str);
|
|
63
|
+
}
|
|
64
|
+
updateUrl(params, opts);
|
|
65
|
+
},
|
|
66
|
+
clear: (paths, opts) => {
|
|
67
|
+
if (!isClient) return;
|
|
68
|
+
if (paths.length === 0) {
|
|
69
|
+
updateUrl(new URLSearchParams(), opts);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const params = new URLSearchParams(window.location.search);
|
|
73
|
+
for (const path of paths) {
|
|
74
|
+
const paramKey = paramMap[path] ?? path;
|
|
75
|
+
params.delete(paramKey);
|
|
76
|
+
}
|
|
77
|
+
updateUrl(params, opts);
|
|
78
|
+
},
|
|
79
|
+
subscribe: (callback) => {
|
|
80
|
+
if (!isClient) return () => void 0;
|
|
81
|
+
const handler = () => {
|
|
82
|
+
const values = readImpl();
|
|
83
|
+
if (values) callback(values);
|
|
84
|
+
};
|
|
85
|
+
window.addEventListener("popstate", handler);
|
|
86
|
+
return () => window.removeEventListener("popstate", handler);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function buildReverseMap(map) {
|
|
91
|
+
const reverse = {};
|
|
92
|
+
for (const [formPath, paramKey] of Object.entries(map)) {
|
|
93
|
+
reverse[paramKey] = formPath;
|
|
94
|
+
}
|
|
95
|
+
return reverse;
|
|
96
|
+
}
|
|
97
|
+
export {
|
|
98
|
+
urlStorage
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/storage/url/index.ts"],"sourcesContent":["import { defaultSerialize } from '../../schema'\nimport type { ParamsStorage, WriteOptions } from '../../storage'\n\nexport interface UrlStorageOptions {\n /**\n * History API strategy. `'replace'` keeps the back-stack clean (recommended\n * for filters/search). `'push'` creates a new history entry for every write.\n * Default: `'replace'`.\n */\n readonly strategy?: 'replace' | 'push'\n\n /** form path → URL search-param key. Defaults to identity. */\n readonly paramMap?: Record<string, string>\n\n /** Per-path string serializer. Default: `defaultSerialize` (same as schema). */\n readonly serialize?: Record<string, (value: unknown) => string>\n\n /** Per-path string-to-value deserializer. Default: identity (raw string). */\n readonly deserialize?: Record<string, (str: string) => unknown>\n}\n\nconst isClient = typeof window !== 'undefined'\n\n/**\n * URL search-param storage backend (native History API).\n *\n * - Hydrates from `window.location.search` on store creation.\n * - Reflects writes back to the URL via `replaceState` (default) or `pushState`.\n * - Subscribes to `popstate` so back/forward navigation flows into the store.\n *\n * `omitWhenDefault` is honored at the engine level (the store sends `undefined`\n * for paths at default; the backend deletes them from the URL).\n *\n * Limitations (v0.1):\n * - History API only — `hashchange` not supported.\n * - Cross-tab URL sync not supported (History API is per-tab).\n *\n * v0.4: per-call strategy override via `WriteOptions.history` — store callers\n * can pass `params.set(path, value, { history: 'push' })` to override the\n * adapter-level default for that single write.\n */\nexport function urlStorage<T = Record<string, unknown>>(\n options: UrlStorageOptions = {},\n): ParamsStorage<T> {\n const adapterStrategy = options.strategy ?? 'replace'\n const paramMap = options.paramMap ?? {}\n const reverseMap = buildReverseMap(paramMap)\n const serialize = options.serialize ?? {}\n const deserialize = options.deserialize ?? {}\n\n const readImpl = (): Partial<T> | undefined => {\n if (!isClient) return undefined\n try {\n const params = new URLSearchParams(window.location.search)\n const partial: Record<string, unknown> = {}\n let hasAny = false\n for (const [paramKey, raw] of params.entries()) {\n const formPath = reverseMap[paramKey] ?? paramKey\n const deserializer = deserialize[formPath]\n try {\n partial[formPath] = deserializer ? deserializer(raw) : raw\n hasAny = true\n } catch {\n // Per-path deserialize failure → skip; engine falls back to default\n }\n }\n return hasAny ? (partial as Partial<T>) : undefined\n } catch {\n return undefined\n }\n }\n\n const updateUrl = (params: URLSearchParams, opts?: WriteOptions): void => {\n if (!isClient) return\n const search = params.toString()\n const url = search ? `${window.location.pathname}?${search}` : window.location.pathname\n const strategy = opts?.history ?? adapterStrategy\n try {\n if (strategy === 'push') {\n window.history.pushState(null, '', url)\n } else {\n window.history.replaceState(null, '', url)\n }\n } catch {\n // Some browsers cap History API writes (rare) — silent fallback.\n }\n }\n\n return {\n name: 'urlStorage',\n clientOnly: true,\n read: readImpl,\n write: (values, _changed, opts) => {\n if (!isClient) return\n const params = new URLSearchParams(window.location.search)\n for (const [path, value] of Object.entries(values as Record<string, unknown>)) {\n const paramKey = paramMap[path] ?? path\n if (value === undefined || value === null) {\n params.delete(paramKey)\n continue\n }\n const serializer = serialize[path]\n const str = serializer ? serializer(value) : defaultSerialize(value)\n params.set(paramKey, str)\n }\n updateUrl(params, opts)\n },\n clear: (paths, opts) => {\n if (!isClient) return\n if (paths.length === 0) {\n updateUrl(new URLSearchParams(), opts)\n return\n }\n const params = new URLSearchParams(window.location.search)\n for (const path of paths) {\n const paramKey = paramMap[path] ?? path\n params.delete(paramKey)\n }\n updateUrl(params, opts)\n },\n subscribe: (callback) => {\n if (!isClient) return () => undefined\n const handler = () => {\n const values = readImpl()\n if (values) callback(values)\n }\n window.addEventListener('popstate', handler)\n return () => window.removeEventListener('popstate', handler)\n },\n }\n}\n\nfunction buildReverseMap(map: Record<string, string>): Record<string, string> {\n const reverse: Record<string, string> = {}\n for (const [formPath, paramKey] of Object.entries(map)) {\n reverse[paramKey] = formPath\n }\n return reverse\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-call write/clear options. Backends opt-in to the fields that apply to
|
|
3
|
+
* them — `urlStorage` reads `history`; other backends ignore the field.
|
|
4
|
+
*/
|
|
5
|
+
interface WriteOptions {
|
|
6
|
+
/**
|
|
7
|
+
* History API strategy override for URL-like backends. When set, takes
|
|
8
|
+
* precedence over the adapter's construction-time `strategy`. Strongest-wins
|
|
9
|
+
* across batched writes (`'push'` beats `'replace'`).
|
|
10
|
+
*/
|
|
11
|
+
readonly history?: 'push' | 'replace';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* ParamsStorage — bidirectional bridge between the in-memory state container
|
|
15
|
+
* and an external source (URL, localStorage, sessionStorage, server, …).
|
|
16
|
+
*
|
|
17
|
+
* Storage interface is **public + stable**. Custom backends (IndexedDB,
|
|
18
|
+
* cookies, server) implement this directly. Built-in backends (memory, URL,
|
|
19
|
+
* local, session) are reference implementations.
|
|
20
|
+
*/
|
|
21
|
+
interface ParamsStorage<T = Record<string, unknown>> {
|
|
22
|
+
/** Identifier — used in dev warnings and devtools. */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/**
|
|
25
|
+
* Skip on server (no `window`). URL / localStorage / sessionStorage all set
|
|
26
|
+
* this to `true`. Memory storage leaves it unset.
|
|
27
|
+
*/
|
|
28
|
+
readonly clientOnly?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Synchronous read. Memory, URL, localStorage all return values immediately.
|
|
31
|
+
* Returning `undefined` means "no stored values" — engine uses defaults.
|
|
32
|
+
* Read failures (corrupted JSON, parse errors) should fall back to undefined
|
|
33
|
+
* silently; the engine surfaces them via `params.store.storageErrors`.
|
|
34
|
+
*/
|
|
35
|
+
read(): Partial<T> | undefined;
|
|
36
|
+
/** Async read — for backends that need it (server, IndexedDB). v0.2. */
|
|
37
|
+
readAsync?(): Promise<Partial<T> | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Persist a write. `changed` lists paths whose values updated since the
|
|
40
|
+
* last write — backends MAY use it for selective persistence. `options`
|
|
41
|
+
* carries per-call overrides (e.g. URL history strategy); backends that
|
|
42
|
+
* don't recognize a field SHOULD ignore it silently.
|
|
43
|
+
*
|
|
44
|
+
* Write failures (quota exceeded, History API rejected, storage unavailable)
|
|
45
|
+
* should be swallowed silently — params always have a usable in-memory
|
|
46
|
+
* value; consumer code never has to handle storage I/O exceptions.
|
|
47
|
+
*/
|
|
48
|
+
write(values: Partial<T>, changed: string[], options?: WriteOptions): void | Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to external changes (popstate, storage events, server pushes).
|
|
51
|
+
* The engine handles loop prevention via deep-equal-against-last-written;
|
|
52
|
+
* adapters should fire callbacks for genuinely external changes.
|
|
53
|
+
*/
|
|
54
|
+
subscribe?(callback: (values: Partial<T>) => void): () => void;
|
|
55
|
+
/** Invoked by `params.reset()`. Removes the storage representation. */
|
|
56
|
+
clear?(paths: string[], options?: WriteOptions): void | Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type { ParamsStorage as P, WriteOptions as W };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-call write/clear options. Backends opt-in to the fields that apply to
|
|
3
|
+
* them — `urlStorage` reads `history`; other backends ignore the field.
|
|
4
|
+
*/
|
|
5
|
+
interface WriteOptions {
|
|
6
|
+
/**
|
|
7
|
+
* History API strategy override for URL-like backends. When set, takes
|
|
8
|
+
* precedence over the adapter's construction-time `strategy`. Strongest-wins
|
|
9
|
+
* across batched writes (`'push'` beats `'replace'`).
|
|
10
|
+
*/
|
|
11
|
+
readonly history?: 'push' | 'replace';
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* ParamsStorage — bidirectional bridge between the in-memory state container
|
|
15
|
+
* and an external source (URL, localStorage, sessionStorage, server, …).
|
|
16
|
+
*
|
|
17
|
+
* Storage interface is **public + stable**. Custom backends (IndexedDB,
|
|
18
|
+
* cookies, server) implement this directly. Built-in backends (memory, URL,
|
|
19
|
+
* local, session) are reference implementations.
|
|
20
|
+
*/
|
|
21
|
+
interface ParamsStorage<T = Record<string, unknown>> {
|
|
22
|
+
/** Identifier — used in dev warnings and devtools. */
|
|
23
|
+
readonly name: string;
|
|
24
|
+
/**
|
|
25
|
+
* Skip on server (no `window`). URL / localStorage / sessionStorage all set
|
|
26
|
+
* this to `true`. Memory storage leaves it unset.
|
|
27
|
+
*/
|
|
28
|
+
readonly clientOnly?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Synchronous read. Memory, URL, localStorage all return values immediately.
|
|
31
|
+
* Returning `undefined` means "no stored values" — engine uses defaults.
|
|
32
|
+
* Read failures (corrupted JSON, parse errors) should fall back to undefined
|
|
33
|
+
* silently; the engine surfaces them via `params.store.storageErrors`.
|
|
34
|
+
*/
|
|
35
|
+
read(): Partial<T> | undefined;
|
|
36
|
+
/** Async read — for backends that need it (server, IndexedDB). v0.2. */
|
|
37
|
+
readAsync?(): Promise<Partial<T> | undefined>;
|
|
38
|
+
/**
|
|
39
|
+
* Persist a write. `changed` lists paths whose values updated since the
|
|
40
|
+
* last write — backends MAY use it for selective persistence. `options`
|
|
41
|
+
* carries per-call overrides (e.g. URL history strategy); backends that
|
|
42
|
+
* don't recognize a field SHOULD ignore it silently.
|
|
43
|
+
*
|
|
44
|
+
* Write failures (quota exceeded, History API rejected, storage unavailable)
|
|
45
|
+
* should be swallowed silently — params always have a usable in-memory
|
|
46
|
+
* value; consumer code never has to handle storage I/O exceptions.
|
|
47
|
+
*/
|
|
48
|
+
write(values: Partial<T>, changed: string[], options?: WriteOptions): void | Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Subscribe to external changes (popstate, storage events, server pushes).
|
|
51
|
+
* The engine handles loop prevention via deep-equal-against-last-written;
|
|
52
|
+
* adapters should fire callbacks for genuinely external changes.
|
|
53
|
+
*/
|
|
54
|
+
subscribe?(callback: (values: Partial<T>) => void): () => void;
|
|
55
|
+
/** Invoked by `params.reset()`. Removes the storage representation. */
|
|
56
|
+
clear?(paths: string[], options?: WriteOptions): void | Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type { ParamsStorage as P, WriteOptions as W };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { P as ParamsStorage } from './storage-DBLIRR-4.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plain field spec — for fields without a schema library. Provides a default
|
|
6
|
+
* value plus optional parse/serialize for storage round-tripping.
|
|
7
|
+
*/
|
|
8
|
+
interface PlainFieldSpec<T = unknown> {
|
|
9
|
+
default: T;
|
|
10
|
+
/** Storage string → typed value. Default: identity if T extends string. */
|
|
11
|
+
parse?: (raw: string) => T;
|
|
12
|
+
/** Typed value → storage string. Default: String(value). */
|
|
13
|
+
serialize?: (value: T) => string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Field spec — a Zod / Standard Schema-compatible schema OR a plain spec.
|
|
17
|
+
* Each field MUST be able to produce a default value (either via schema's
|
|
18
|
+
* `.default()` or plain spec's `default:` field).
|
|
19
|
+
*/
|
|
20
|
+
type FieldSpec<T = unknown> = StandardSchemaV1<unknown, T> | PlainFieldSpec<T>;
|
|
21
|
+
interface FieldConfig {
|
|
22
|
+
/** Trailing-edge debounce on store propagation (ms). 0 = no debounce. */
|
|
23
|
+
debounce?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Strip the value from external storage when it deep-equals the field's
|
|
26
|
+
* default. Useful for keeping URLs clean (`?page=1` becomes empty when
|
|
27
|
+
* page is at default).
|
|
28
|
+
*/
|
|
29
|
+
omitWhenDefault?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Paths whose changes trigger this field's `onDepChange`. `'*'` means
|
|
32
|
+
* "every other field"; `excludeDeps` carves out exceptions. Resolved at
|
|
33
|
+
* cascade time, so adding fields to a definition doesn't invalidate
|
|
34
|
+
* `'*'` consumers.
|
|
35
|
+
*/
|
|
36
|
+
dependsOn?: string[] | '*';
|
|
37
|
+
/**
|
|
38
|
+
* Paths to exclude from the `'*'` wildcard. Only meaningful when
|
|
39
|
+
* `dependsOn === '*'`. The field itself is always implicitly excluded.
|
|
40
|
+
*/
|
|
41
|
+
excludeDeps?: string[];
|
|
42
|
+
/**
|
|
43
|
+
* What to do when a dep changes. `'reset'` restores default; `'clear'`
|
|
44
|
+
* sets to `undefined`; a function receives ALL dep values (changed +
|
|
45
|
+
* unchanged) plus the current value, and returns the new value.
|
|
46
|
+
*/
|
|
47
|
+
onDepChange?: 'reset' | 'clear' | ((deps: Record<string, unknown>, current: unknown) => unknown);
|
|
48
|
+
}
|
|
49
|
+
interface ParamsConfig<T> {
|
|
50
|
+
/** Storage backend. Default: `memoryStorage()`. URL/local/session are opt-in. */
|
|
51
|
+
storage?: ParamsStorage<T>;
|
|
52
|
+
/** Per-field config keyed by field name. */
|
|
53
|
+
fields?: Record<string, FieldConfig>;
|
|
54
|
+
/**
|
|
55
|
+
* Optional definition name. Reserved for v0.2 SSR snapshot keys. Stored
|
|
56
|
+
* for forward-compat; in v0.1 it has no runtime behavior except a dev-mode
|
|
57
|
+
* warning when two definitions share a name.
|
|
58
|
+
*/
|
|
59
|
+
name?: string;
|
|
60
|
+
}
|
|
61
|
+
interface ParamsDefinition<T = Record<string, unknown>> {
|
|
62
|
+
readonly name: string | undefined;
|
|
63
|
+
readonly spec: Readonly<Record<string, FieldSpec>>;
|
|
64
|
+
readonly storage: ParamsStorage<T>;
|
|
65
|
+
readonly fields: Readonly<Record<string, FieldConfig>>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type { FieldSpec as F, ParamsConfig as P, ParamsDefinition as a, PlainFieldSpec as b, FieldConfig as c };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { StandardSchemaV1 } from '@standard-schema/spec';
|
|
2
|
+
import { P as ParamsStorage } from './storage-DBLIRR-4.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Plain field spec — for fields without a schema library. Provides a default
|
|
6
|
+
* value plus optional parse/serialize for storage round-tripping.
|
|
7
|
+
*/
|
|
8
|
+
interface PlainFieldSpec<T = unknown> {
|
|
9
|
+
default: T;
|
|
10
|
+
/** Storage string → typed value. Default: identity if T extends string. */
|
|
11
|
+
parse?: (raw: string) => T;
|
|
12
|
+
/** Typed value → storage string. Default: String(value). */
|
|
13
|
+
serialize?: (value: T) => string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Field spec — a Zod / Standard Schema-compatible schema OR a plain spec.
|
|
17
|
+
* Each field MUST be able to produce a default value (either via schema's
|
|
18
|
+
* `.default()` or plain spec's `default:` field).
|
|
19
|
+
*/
|
|
20
|
+
type FieldSpec<T = unknown> = StandardSchemaV1<unknown, T> | PlainFieldSpec<T>;
|
|
21
|
+
interface FieldConfig {
|
|
22
|
+
/** Trailing-edge debounce on store propagation (ms). 0 = no debounce. */
|
|
23
|
+
debounce?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Strip the value from external storage when it deep-equals the field's
|
|
26
|
+
* default. Useful for keeping URLs clean (`?page=1` becomes empty when
|
|
27
|
+
* page is at default).
|
|
28
|
+
*/
|
|
29
|
+
omitWhenDefault?: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Paths whose changes trigger this field's `onDepChange`. `'*'` means
|
|
32
|
+
* "every other field"; `excludeDeps` carves out exceptions. Resolved at
|
|
33
|
+
* cascade time, so adding fields to a definition doesn't invalidate
|
|
34
|
+
* `'*'` consumers.
|
|
35
|
+
*/
|
|
36
|
+
dependsOn?: string[] | '*';
|
|
37
|
+
/**
|
|
38
|
+
* Paths to exclude from the `'*'` wildcard. Only meaningful when
|
|
39
|
+
* `dependsOn === '*'`. The field itself is always implicitly excluded.
|
|
40
|
+
*/
|
|
41
|
+
excludeDeps?: string[];
|
|
42
|
+
/**
|
|
43
|
+
* What to do when a dep changes. `'reset'` restores default; `'clear'`
|
|
44
|
+
* sets to `undefined`; a function receives ALL dep values (changed +
|
|
45
|
+
* unchanged) plus the current value, and returns the new value.
|
|
46
|
+
*/
|
|
47
|
+
onDepChange?: 'reset' | 'clear' | ((deps: Record<string, unknown>, current: unknown) => unknown);
|
|
48
|
+
}
|
|
49
|
+
interface ParamsConfig<T> {
|
|
50
|
+
/** Storage backend. Default: `memoryStorage()`. URL/local/session are opt-in. */
|
|
51
|
+
storage?: ParamsStorage<T>;
|
|
52
|
+
/** Per-field config keyed by field name. */
|
|
53
|
+
fields?: Record<string, FieldConfig>;
|
|
54
|
+
/**
|
|
55
|
+
* Optional definition name. Reserved for v0.2 SSR snapshot keys. Stored
|
|
56
|
+
* for forward-compat; in v0.1 it has no runtime behavior except a dev-mode
|
|
57
|
+
* warning when two definitions share a name.
|
|
58
|
+
*/
|
|
59
|
+
name?: string;
|
|
60
|
+
}
|
|
61
|
+
interface ParamsDefinition<T = Record<string, unknown>> {
|
|
62
|
+
readonly name: string | undefined;
|
|
63
|
+
readonly spec: Readonly<Record<string, FieldSpec>>;
|
|
64
|
+
readonly storage: ParamsStorage<T>;
|
|
65
|
+
readonly fields: Readonly<Record<string, FieldConfig>>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type { FieldSpec as F, ParamsConfig as P, ParamsDefinition as a, PlainFieldSpec as b, FieldConfig as c };
|
package/package.json
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@victorylabs/params",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Centralized view-state config (filters, sort, pagination, app config) with pluggable storage. Memory by default; URL/localStorage/sessionStorage opt-in. Single React hook DX, schema-validated reads, cross-component sharing.",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"main": "./dist/index.cjs",
|
|
11
|
+
"module": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"exports": {
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"require": "./dist/index.cjs"
|
|
18
|
+
},
|
|
19
|
+
"./react": {
|
|
20
|
+
"types": "./dist/react.d.ts",
|
|
21
|
+
"import": "./dist/react.js",
|
|
22
|
+
"require": "./dist/react.cjs"
|
|
23
|
+
},
|
|
24
|
+
"./storage/url": {
|
|
25
|
+
"types": "./dist/storage/url.d.ts",
|
|
26
|
+
"import": "./dist/storage/url.js",
|
|
27
|
+
"require": "./dist/storage/url.cjs"
|
|
28
|
+
},
|
|
29
|
+
"./storage/local": {
|
|
30
|
+
"types": "./dist/storage/local.d.ts",
|
|
31
|
+
"import": "./dist/storage/local.js",
|
|
32
|
+
"require": "./dist/storage/local.cjs"
|
|
33
|
+
},
|
|
34
|
+
"./storage/session": {
|
|
35
|
+
"types": "./dist/storage/session.d.ts",
|
|
36
|
+
"import": "./dist/storage/session.js",
|
|
37
|
+
"require": "./dist/storage/session.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./storage/cookie": {
|
|
40
|
+
"types": "./dist/storage/cookie.d.ts",
|
|
41
|
+
"import": "./dist/storage/cookie.js",
|
|
42
|
+
"require": "./dist/storage/cookie.cjs"
|
|
43
|
+
},
|
|
44
|
+
"./storage/server": {
|
|
45
|
+
"types": "./dist/storage/server.d.ts",
|
|
46
|
+
"import": "./dist/storage/server.js",
|
|
47
|
+
"require": "./dist/storage/server.cjs"
|
|
48
|
+
},
|
|
49
|
+
"./storage/compose": {
|
|
50
|
+
"types": "./dist/storage/compose.d.ts",
|
|
51
|
+
"import": "./dist/storage/compose.js",
|
|
52
|
+
"require": "./dist/storage/compose.cjs"
|
|
53
|
+
},
|
|
54
|
+
"./storage/idb": {
|
|
55
|
+
"types": "./dist/storage/idb.d.ts",
|
|
56
|
+
"import": "./dist/storage/idb.js",
|
|
57
|
+
"require": "./dist/storage/idb.cjs"
|
|
58
|
+
},
|
|
59
|
+
"./snapshot": {
|
|
60
|
+
"types": "./dist/snapshot.d.ts",
|
|
61
|
+
"import": "./dist/snapshot.js",
|
|
62
|
+
"require": "./dist/snapshot.cjs"
|
|
63
|
+
},
|
|
64
|
+
"./integrations/forms": {
|
|
65
|
+
"types": "./dist/integrations/forms.d.ts",
|
|
66
|
+
"import": "./dist/integrations/forms.js",
|
|
67
|
+
"require": "./dist/integrations/forms.cjs"
|
|
68
|
+
},
|
|
69
|
+
"./integrations/forms-reverse": {
|
|
70
|
+
"types": "./dist/integrations/forms-reverse.d.ts",
|
|
71
|
+
"import": "./dist/integrations/forms-reverse.js",
|
|
72
|
+
"require": "./dist/integrations/forms-reverse.cjs"
|
|
73
|
+
},
|
|
74
|
+
"./devtools": {
|
|
75
|
+
"types": "./dist/devtools.d.ts",
|
|
76
|
+
"import": "./dist/devtools.js",
|
|
77
|
+
"require": "./dist/devtools.cjs"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"files": [
|
|
81
|
+
"dist"
|
|
82
|
+
],
|
|
83
|
+
"dependencies": {
|
|
84
|
+
"@standard-schema/spec": "^1.0.0"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"fake-indexeddb": "^6.0.0",
|
|
88
|
+
"zod": "^3.23.8",
|
|
89
|
+
"@victorylabs/forms": "0.1.0",
|
|
90
|
+
"@victorylabs/utils": "0.1.0"
|
|
91
|
+
},
|
|
92
|
+
"peerDependencies": {
|
|
93
|
+
"react": ">=18",
|
|
94
|
+
"zod": ">=3.23"
|
|
95
|
+
},
|
|
96
|
+
"peerDependenciesMeta": {
|
|
97
|
+
"react": {
|
|
98
|
+
"optional": true
|
|
99
|
+
},
|
|
100
|
+
"zod": {
|
|
101
|
+
"optional": true
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"engines": {
|
|
105
|
+
"node": ">=20"
|
|
106
|
+
},
|
|
107
|
+
"license": "MIT",
|
|
108
|
+
"scripts": {
|
|
109
|
+
"build": "tsup",
|
|
110
|
+
"test": "vitest run",
|
|
111
|
+
"typecheck": "tsc --noEmit",
|
|
112
|
+
"lint": "biome check src"
|
|
113
|
+
}
|
|
114
|
+
}
|