@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.
Files changed (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/chunk-43PUAYQP.js +573 -0
  4. package/dist/chunk-43PUAYQP.js.map +1 -0
  5. package/dist/chunk-4T4THPFW.js +100 -0
  6. package/dist/chunk-4T4THPFW.js.map +1 -0
  7. package/dist/chunk-5NSLHAHG.js +26 -0
  8. package/dist/chunk-5NSLHAHG.js.map +1 -0
  9. package/dist/chunk-NHCH2WKC.js +96 -0
  10. package/dist/chunk-NHCH2WKC.js.map +1 -0
  11. package/dist/chunk-NUO3GOXV.js +72 -0
  12. package/dist/chunk-NUO3GOXV.js.map +1 -0
  13. package/dist/devtools.cjs +41 -0
  14. package/dist/devtools.cjs.map +1 -0
  15. package/dist/devtools.d.cts +45 -0
  16. package/dist/devtools.d.ts +45 -0
  17. package/dist/devtools.js +16 -0
  18. package/dist/devtools.js.map +1 -0
  19. package/dist/index.cjs +777 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +133 -0
  22. package/dist/index.d.ts +133 -0
  23. package/dist/index.js +83 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/integrations/forms-reverse.cjs +777 -0
  26. package/dist/integrations/forms-reverse.cjs.map +1 -0
  27. package/dist/integrations/forms-reverse.d.cts +32 -0
  28. package/dist/integrations/forms-reverse.d.ts +32 -0
  29. package/dist/integrations/forms-reverse.js +73 -0
  30. package/dist/integrations/forms-reverse.js.map +1 -0
  31. package/dist/integrations/forms.cjs +771 -0
  32. package/dist/integrations/forms.cjs.map +1 -0
  33. package/dist/integrations/forms.d.cts +25 -0
  34. package/dist/integrations/forms.d.ts +25 -0
  35. package/dist/integrations/forms.js +65 -0
  36. package/dist/integrations/forms.js.map +1 -0
  37. package/dist/params-store-Cgbtn53j.d.cts +115 -0
  38. package/dist/params-store-CguA9-yr.d.ts +115 -0
  39. package/dist/react.cjs +910 -0
  40. package/dist/react.cjs.map +1 -0
  41. package/dist/react.d.cts +75 -0
  42. package/dist/react.d.ts +75 -0
  43. package/dist/react.js +202 -0
  44. package/dist/react.js.map +1 -0
  45. package/dist/snapshot.cjs +75 -0
  46. package/dist/snapshot.cjs.map +1 -0
  47. package/dist/snapshot.d.cts +42 -0
  48. package/dist/snapshot.d.ts +42 -0
  49. package/dist/snapshot.js +42 -0
  50. package/dist/snapshot.js.map +1 -0
  51. package/dist/storage/compose.cjs +196 -0
  52. package/dist/storage/compose.cjs.map +1 -0
  53. package/dist/storage/compose.d.cts +35 -0
  54. package/dist/storage/compose.d.ts +35 -0
  55. package/dist/storage/compose.js +123 -0
  56. package/dist/storage/compose.js.map +1 -0
  57. package/dist/storage/cookie.cjs +136 -0
  58. package/dist/storage/cookie.cjs.map +1 -0
  59. package/dist/storage/cookie.d.cts +57 -0
  60. package/dist/storage/cookie.d.ts +57 -0
  61. package/dist/storage/cookie.js +111 -0
  62. package/dist/storage/cookie.js.map +1 -0
  63. package/dist/storage/idb.cjs +144 -0
  64. package/dist/storage/idb.cjs.map +1 -0
  65. package/dist/storage/idb.d.cts +31 -0
  66. package/dist/storage/idb.d.ts +31 -0
  67. package/dist/storage/idb.js +119 -0
  68. package/dist/storage/idb.js.map +1 -0
  69. package/dist/storage/local.cjs +121 -0
  70. package/dist/storage/local.cjs.map +1 -0
  71. package/dist/storage/local.d.cts +23 -0
  72. package/dist/storage/local.d.ts +23 -0
  73. package/dist/storage/local.js +9 -0
  74. package/dist/storage/local.js.map +1 -0
  75. package/dist/storage/server.cjs +158 -0
  76. package/dist/storage/server.cjs.map +1 -0
  77. package/dist/storage/server.d.cts +57 -0
  78. package/dist/storage/server.d.ts +57 -0
  79. package/dist/storage/server.js +133 -0
  80. package/dist/storage/server.js.map +1 -0
  81. package/dist/storage/session.cjs +123 -0
  82. package/dist/storage/session.cjs.map +1 -0
  83. package/dist/storage/session.d.cts +14 -0
  84. package/dist/storage/session.d.ts +14 -0
  85. package/dist/storage/session.js +12 -0
  86. package/dist/storage/session.js.map +1 -0
  87. package/dist/storage/url.cjs +132 -0
  88. package/dist/storage/url.cjs.map +1 -0
  89. package/dist/storage/url.d.cts +37 -0
  90. package/dist/storage/url.d.ts +37 -0
  91. package/dist/storage/url.js +100 -0
  92. package/dist/storage/url.js.map +1 -0
  93. package/dist/storage-DBLIRR-4.d.cts +59 -0
  94. package/dist/storage-DBLIRR-4.d.ts +59 -0
  95. package/dist/types-BSWKH-jw.d.cts +68 -0
  96. package/dist/types-BUmNpSyP.d.ts +68 -0
  97. package/package.json +114 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/snapshot.ts","../src/name-registry.ts"],"sourcesContent":["import { getDefByName, setPreHydrationValues } from './name-registry'\nimport type { ParamsDefinition } from './types'\n\n/**\n * Snapshot keyed by definition name. Returned from `paramsSnapshot()` and\n * accepted by `hydrateParams()` — round-trip transport for SSR.\n */\nexport type ParamsSnapshot = Record<string, unknown>\n\n/**\n * Server-side: produce a snapshot of the current values for the given\n * definitions, keyed by `def.name`. The returned object is JSON-serializable\n * (call `JSON.stringify` to embed in HTML, or pass through your framework's\n * data-loader contract).\n *\n * Each definition MUST have a `name` set (added in v0.1 for forward-compat,\n * load-bearing in v0.2). Throws otherwise.\n *\n * Throws on duplicate names — the snapshot keys would collide.\n *\n * Reads via the synchronous `def.storage.read()` path. Definitions backed by\n * an async-only backend (sync `read()` returns undefined) snapshot as\n * defaults; pair them with a server-side prefetch step before calling\n * `paramsSnapshot()` if you want their fetched values in the snapshot.\n */\nexport function paramsSnapshot(defs: readonly ParamsDefinition[]): ParamsSnapshot {\n const out: ParamsSnapshot = {}\n const seenNames = new Set<string>()\n\n for (const def of defs) {\n if (def.name === undefined) {\n throw new Error(\n 'paramsSnapshot: every definition must have a `name` set. Add `{ name: \"<unique>\" }` to defineParams.',\n )\n }\n if (seenNames.has(def.name)) {\n throw new Error(\n `paramsSnapshot: duplicate definition name '${def.name}'. Snapshot keys would collide.`,\n )\n }\n seenNames.add(def.name)\n\n let values: unknown = undefined\n try {\n values = def.storage.read() ?? {}\n } catch {\n values = {}\n }\n out[def.name] = values\n }\n\n return out\n}\n\n/**\n * Client-side: pre-seed the params store cache from a server-rendered\n * snapshot. Must run BEFORE the first `useParams(def)` / `getParamsStore(def)`\n * call to avoid a flicker.\n *\n * For each entry in the snapshot, looks up the definition by name (must have\n * been imported / `defineParams` already evaluated on the client) and seeds\n * the pre-hydration cache. The first `ParamsStore` constructed for that def\n * uses the seeded values instead of calling its storage backend's `read()`.\n *\n * Idempotent — re-calling overwrites. Definitions whose names aren't in the\n * snapshot use their normal storage hydration path.\n */\nexport function hydrateParams(snapshot: ParamsSnapshot): void {\n for (const [name, values] of Object.entries(snapshot)) {\n setPreHydrationValues(name, values)\n // Tolerate names with no matching def — the def may load lazily via a\n // dynamic import. The pre-hydration cache holds the values until the\n // matching ParamsStore is constructed.\n void getDefByName(name)\n }\n}\n","import type { ParamsDefinition } from './types'\n\n/**\n * Side-effect registry of all definitions that carry a `name`. Populated by\n * `defineParams()` at construction time so `paramsSnapshot()` and\n * `hydrateParams()` can look up defs by name.\n *\n * Last-write-wins: re-defining the same name overwrites silently (the\n * duplicate-name DEV warning is enforced separately at acquire time in\n * `store-cache.ts`).\n */\nconst nameRegistry = new Map<string, ParamsDefinition<unknown>>()\n\n/**\n * Pre-hydration cache for SSR snapshots. `hydrateParams(snapshot)` populates\n * this; `ParamsStore` checks it during initial hydration and uses the seeded\n * values instead of calling the storage backend's `read()`.\n *\n * Once consumed by a store on first hydration the entry is left in place\n * (idempotent re-hydrate) — the consumer can call `hydrateParams()` again\n * with overrides if needed before any store is constructed.\n */\nconst preHydrationCache = new Map<string, unknown>()\n\nexport function registerNamedDef<T>(def: ParamsDefinition<T>): void {\n if (def.name === undefined) return\n nameRegistry.set(def.name, def as ParamsDefinition<unknown>)\n}\n\nexport function getDefByName(name: string): ParamsDefinition<unknown> | undefined {\n return nameRegistry.get(name)\n}\n\nexport function getRegisteredDefs(): ReadonlyArray<ParamsDefinition<unknown>> {\n return Array.from(nameRegistry.values())\n}\n\nexport function setPreHydrationValues(name: string, values: unknown): void {\n preHydrationCache.set(name, values)\n}\n\nexport function takePreHydrationValues<T>(name: string | undefined): Partial<T> | undefined {\n if (name === undefined) return undefined\n if (!preHydrationCache.has(name)) return undefined\n return preHydrationCache.get(name) as Partial<T>\n}\n\n/** Test-only: clear both registries. Not exported from the package. */\nexport function _resetNameRegistry(): void {\n nameRegistry.clear()\n preHydrationCache.clear()\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWA,IAAM,eAAe,oBAAI,IAAuC;AAWhE,IAAM,oBAAoB,oBAAI,IAAqB;AAO5C,SAAS,aAAa,MAAqD;AAChF,SAAO,aAAa,IAAI,IAAI;AAC9B;AAMO,SAAS,sBAAsB,MAAc,QAAuB;AACzE,oBAAkB,IAAI,MAAM,MAAM;AACpC;;;ADdO,SAAS,eAAe,MAAmD;AAChF,QAAM,MAAsB,CAAC;AAC7B,QAAM,YAAY,oBAAI,IAAY;AAElC,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,QAAW;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,UAAU,IAAI,IAAI,IAAI,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,8CAA8C,IAAI,IAAI;AAAA,MACxD;AAAA,IACF;AACA,cAAU,IAAI,IAAI,IAAI;AAEtB,QAAI,SAAkB;AACtB,QAAI;AACF,eAAS,IAAI,QAAQ,KAAK,KAAK,CAAC;AAAA,IAClC,QAAQ;AACN,eAAS,CAAC;AAAA,IACZ;AACA,QAAI,IAAI,IAAI,IAAI;AAAA,EAClB;AAEA,SAAO;AACT;AAeO,SAAS,cAAc,UAAgC;AAC5D,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,0BAAsB,MAAM,MAAM;AAIlC,SAAK,aAAa,IAAI;AAAA,EACxB;AACF;","names":[]}
@@ -0,0 +1,42 @@
1
+ import { a as ParamsDefinition } from './types-BSWKH-jw.cjs';
2
+ import '@standard-schema/spec';
3
+ import './storage-DBLIRR-4.cjs';
4
+
5
+ /**
6
+ * Snapshot keyed by definition name. Returned from `paramsSnapshot()` and
7
+ * accepted by `hydrateParams()` — round-trip transport for SSR.
8
+ */
9
+ type ParamsSnapshot = Record<string, unknown>;
10
+ /**
11
+ * Server-side: produce a snapshot of the current values for the given
12
+ * definitions, keyed by `def.name`. The returned object is JSON-serializable
13
+ * (call `JSON.stringify` to embed in HTML, or pass through your framework's
14
+ * data-loader contract).
15
+ *
16
+ * Each definition MUST have a `name` set (added in v0.1 for forward-compat,
17
+ * load-bearing in v0.2). Throws otherwise.
18
+ *
19
+ * Throws on duplicate names — the snapshot keys would collide.
20
+ *
21
+ * Reads via the synchronous `def.storage.read()` path. Definitions backed by
22
+ * an async-only backend (sync `read()` returns undefined) snapshot as
23
+ * defaults; pair them with a server-side prefetch step before calling
24
+ * `paramsSnapshot()` if you want their fetched values in the snapshot.
25
+ */
26
+ declare function paramsSnapshot(defs: readonly ParamsDefinition[]): ParamsSnapshot;
27
+ /**
28
+ * Client-side: pre-seed the params store cache from a server-rendered
29
+ * snapshot. Must run BEFORE the first `useParams(def)` / `getParamsStore(def)`
30
+ * call to avoid a flicker.
31
+ *
32
+ * For each entry in the snapshot, looks up the definition by name (must have
33
+ * been imported / `defineParams` already evaluated on the client) and seeds
34
+ * the pre-hydration cache. The first `ParamsStore` constructed for that def
35
+ * uses the seeded values instead of calling its storage backend's `read()`.
36
+ *
37
+ * Idempotent — re-calling overwrites. Definitions whose names aren't in the
38
+ * snapshot use their normal storage hydration path.
39
+ */
40
+ declare function hydrateParams(snapshot: ParamsSnapshot): void;
41
+
42
+ export { type ParamsSnapshot, hydrateParams, paramsSnapshot };
@@ -0,0 +1,42 @@
1
+ import { a as ParamsDefinition } from './types-BUmNpSyP.js';
2
+ import '@standard-schema/spec';
3
+ import './storage-DBLIRR-4.js';
4
+
5
+ /**
6
+ * Snapshot keyed by definition name. Returned from `paramsSnapshot()` and
7
+ * accepted by `hydrateParams()` — round-trip transport for SSR.
8
+ */
9
+ type ParamsSnapshot = Record<string, unknown>;
10
+ /**
11
+ * Server-side: produce a snapshot of the current values for the given
12
+ * definitions, keyed by `def.name`. The returned object is JSON-serializable
13
+ * (call `JSON.stringify` to embed in HTML, or pass through your framework's
14
+ * data-loader contract).
15
+ *
16
+ * Each definition MUST have a `name` set (added in v0.1 for forward-compat,
17
+ * load-bearing in v0.2). Throws otherwise.
18
+ *
19
+ * Throws on duplicate names — the snapshot keys would collide.
20
+ *
21
+ * Reads via the synchronous `def.storage.read()` path. Definitions backed by
22
+ * an async-only backend (sync `read()` returns undefined) snapshot as
23
+ * defaults; pair them with a server-side prefetch step before calling
24
+ * `paramsSnapshot()` if you want their fetched values in the snapshot.
25
+ */
26
+ declare function paramsSnapshot(defs: readonly ParamsDefinition[]): ParamsSnapshot;
27
+ /**
28
+ * Client-side: pre-seed the params store cache from a server-rendered
29
+ * snapshot. Must run BEFORE the first `useParams(def)` / `getParamsStore(def)`
30
+ * call to avoid a flicker.
31
+ *
32
+ * For each entry in the snapshot, looks up the definition by name (must have
33
+ * been imported / `defineParams` already evaluated on the client) and seeds
34
+ * the pre-hydration cache. The first `ParamsStore` constructed for that def
35
+ * uses the seeded values instead of calling its storage backend's `read()`.
36
+ *
37
+ * Idempotent — re-calling overwrites. Definitions whose names aren't in the
38
+ * snapshot use their normal storage hydration path.
39
+ */
40
+ declare function hydrateParams(snapshot: ParamsSnapshot): void;
41
+
42
+ export { type ParamsSnapshot, hydrateParams, paramsSnapshot };
@@ -0,0 +1,42 @@
1
+ import {
2
+ getDefByName,
3
+ setPreHydrationValues
4
+ } from "./chunk-5NSLHAHG.js";
5
+
6
+ // src/snapshot.ts
7
+ function paramsSnapshot(defs) {
8
+ const out = {};
9
+ const seenNames = /* @__PURE__ */ new Set();
10
+ for (const def of defs) {
11
+ if (def.name === void 0) {
12
+ throw new Error(
13
+ 'paramsSnapshot: every definition must have a `name` set. Add `{ name: "<unique>" }` to defineParams.'
14
+ );
15
+ }
16
+ if (seenNames.has(def.name)) {
17
+ throw new Error(
18
+ `paramsSnapshot: duplicate definition name '${def.name}'. Snapshot keys would collide.`
19
+ );
20
+ }
21
+ seenNames.add(def.name);
22
+ let values = void 0;
23
+ try {
24
+ values = def.storage.read() ?? {};
25
+ } catch {
26
+ values = {};
27
+ }
28
+ out[def.name] = values;
29
+ }
30
+ return out;
31
+ }
32
+ function hydrateParams(snapshot) {
33
+ for (const [name, values] of Object.entries(snapshot)) {
34
+ setPreHydrationValues(name, values);
35
+ void getDefByName(name);
36
+ }
37
+ }
38
+ export {
39
+ hydrateParams,
40
+ paramsSnapshot
41
+ };
42
+ //# sourceMappingURL=snapshot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/snapshot.ts"],"sourcesContent":["import { getDefByName, setPreHydrationValues } from './name-registry'\nimport type { ParamsDefinition } from './types'\n\n/**\n * Snapshot keyed by definition name. Returned from `paramsSnapshot()` and\n * accepted by `hydrateParams()` — round-trip transport for SSR.\n */\nexport type ParamsSnapshot = Record<string, unknown>\n\n/**\n * Server-side: produce a snapshot of the current values for the given\n * definitions, keyed by `def.name`. The returned object is JSON-serializable\n * (call `JSON.stringify` to embed in HTML, or pass through your framework's\n * data-loader contract).\n *\n * Each definition MUST have a `name` set (added in v0.1 for forward-compat,\n * load-bearing in v0.2). Throws otherwise.\n *\n * Throws on duplicate names — the snapshot keys would collide.\n *\n * Reads via the synchronous `def.storage.read()` path. Definitions backed by\n * an async-only backend (sync `read()` returns undefined) snapshot as\n * defaults; pair them with a server-side prefetch step before calling\n * `paramsSnapshot()` if you want their fetched values in the snapshot.\n */\nexport function paramsSnapshot(defs: readonly ParamsDefinition[]): ParamsSnapshot {\n const out: ParamsSnapshot = {}\n const seenNames = new Set<string>()\n\n for (const def of defs) {\n if (def.name === undefined) {\n throw new Error(\n 'paramsSnapshot: every definition must have a `name` set. Add `{ name: \"<unique>\" }` to defineParams.',\n )\n }\n if (seenNames.has(def.name)) {\n throw new Error(\n `paramsSnapshot: duplicate definition name '${def.name}'. Snapshot keys would collide.`,\n )\n }\n seenNames.add(def.name)\n\n let values: unknown = undefined\n try {\n values = def.storage.read() ?? {}\n } catch {\n values = {}\n }\n out[def.name] = values\n }\n\n return out\n}\n\n/**\n * Client-side: pre-seed the params store cache from a server-rendered\n * snapshot. Must run BEFORE the first `useParams(def)` / `getParamsStore(def)`\n * call to avoid a flicker.\n *\n * For each entry in the snapshot, looks up the definition by name (must have\n * been imported / `defineParams` already evaluated on the client) and seeds\n * the pre-hydration cache. The first `ParamsStore` constructed for that def\n * uses the seeded values instead of calling its storage backend's `read()`.\n *\n * Idempotent — re-calling overwrites. Definitions whose names aren't in the\n * snapshot use their normal storage hydration path.\n */\nexport function hydrateParams(snapshot: ParamsSnapshot): void {\n for (const [name, values] of Object.entries(snapshot)) {\n setPreHydrationValues(name, values)\n // Tolerate names with no matching def — the def may load lazily via a\n // dynamic import. The pre-hydration cache holds the values until the\n // matching ParamsStore is constructed.\n void getDefByName(name)\n }\n}\n"],"mappings":";;;;;;AAyBO,SAAS,eAAe,MAAmD;AAChF,QAAM,MAAsB,CAAC;AAC7B,QAAM,YAAY,oBAAI,IAAY;AAElC,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,SAAS,QAAW;AAC1B,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,UAAU,IAAI,IAAI,IAAI,GAAG;AAC3B,YAAM,IAAI;AAAA,QACR,8CAA8C,IAAI,IAAI;AAAA,MACxD;AAAA,IACF;AACA,cAAU,IAAI,IAAI,IAAI;AAEtB,QAAI,SAAkB;AACtB,QAAI;AACF,eAAS,IAAI,QAAQ,KAAK,KAAK,CAAC;AAAA,IAClC,QAAQ;AACN,eAAS,CAAC;AAAA,IACZ;AACA,QAAI,IAAI,IAAI,IAAI;AAAA,EAClB;AAEA,SAAO;AACT;AAeO,SAAS,cAAc,UAAgC;AAC5D,aAAW,CAAC,MAAM,MAAM,KAAK,OAAO,QAAQ,QAAQ,GAAG;AACrD,0BAAsB,MAAM,MAAM;AAIlC,SAAK,aAAa,IAAI;AAAA,EACxB;AACF;","names":[]}
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/storage/compose/index.ts
21
+ var compose_exports = {};
22
+ __export(compose_exports, {
23
+ composeStorage: () => composeStorage
24
+ });
25
+ module.exports = __toCommonJS(compose_exports);
26
+
27
+ // ../utils/src/deep.ts
28
+ function deepEqual(a, b) {
29
+ if (Object.is(a, b)) return true;
30
+ if (typeof a !== typeof b) return false;
31
+ if (a === null || b === null) return false;
32
+ if (typeof a !== "object") return false;
33
+ if (Array.isArray(a)) {
34
+ if (!Array.isArray(b)) return false;
35
+ if (a.length !== b.length) return false;
36
+ for (let i = 0; i < a.length; i++) {
37
+ if (!deepEqual(a[i], b[i])) return false;
38
+ }
39
+ return true;
40
+ }
41
+ if (Array.isArray(b)) return false;
42
+ if (a instanceof Date) {
43
+ return b instanceof Date && a.getTime() === b.getTime();
44
+ }
45
+ if (b instanceof Date) return false;
46
+ if (a instanceof RegExp) {
47
+ return b instanceof RegExp && a.source === b.source && a.flags === b.flags;
48
+ }
49
+ if (b instanceof RegExp) return false;
50
+ const aKeys = Object.keys(a);
51
+ const bKeys = Object.keys(b);
52
+ if (aKeys.length !== bKeys.length) return false;
53
+ for (const key of aKeys) {
54
+ if (!Object.hasOwn(b, key)) return false;
55
+ if (!deepEqual(a[key], b[key])) {
56
+ return false;
57
+ }
58
+ }
59
+ return true;
60
+ }
61
+
62
+ // src/dev.ts
63
+ var isDev = (() => {
64
+ try {
65
+ return typeof process !== "undefined" && process?.env?.NODE_ENV !== "production";
66
+ } catch {
67
+ return false;
68
+ }
69
+ })();
70
+ var warned = /* @__PURE__ */ new Set();
71
+ function warn(message) {
72
+ if (!isDev) return;
73
+ if (warned.has(message)) return;
74
+ warned.add(message);
75
+ console.warn(`[@victorylabs/params] ${message}`);
76
+ }
77
+
78
+ // src/storage/compose/index.ts
79
+ function composeStorage(backends, opts = {}) {
80
+ if (backends.length === 0) {
81
+ throw new Error("composeStorage: requires at least one backend");
82
+ }
83
+ const readStrategy = opts.readStrategy ?? "first";
84
+ const writeStrategy = opts.writeStrategy ?? "all";
85
+ const name = opts.name ?? `composeStorage(${backends.map((b) => b.name).join("+")})`;
86
+ return {
87
+ name,
88
+ // composeStorage runs on the server only if EVERY backend allows it.
89
+ // Otherwise the server skips the read and the engine uses defaults.
90
+ clientOnly: backends.every((b) => b.clientOnly === true),
91
+ read: () => {
92
+ if (readStrategy === "first") {
93
+ for (const b of backends) {
94
+ const v = b.read();
95
+ if (v !== void 0) return v;
96
+ }
97
+ return void 0;
98
+ }
99
+ let merged;
100
+ for (const b of backends) {
101
+ const v = b.read();
102
+ if (v === void 0) continue;
103
+ merged = deepMergeObjects(merged, v);
104
+ }
105
+ return merged;
106
+ },
107
+ readAsync: async () => {
108
+ if (readStrategy === "first") {
109
+ for (const b of backends) {
110
+ const v = b.readAsync ? await b.readAsync() : b.read();
111
+ if (v !== void 0) return v;
112
+ }
113
+ return void 0;
114
+ }
115
+ let merged;
116
+ for (const b of backends) {
117
+ const v = b.readAsync ? await b.readAsync() : b.read();
118
+ if (v === void 0) continue;
119
+ merged = deepMergeObjects(merged, v);
120
+ }
121
+ return merged;
122
+ },
123
+ write: async (values, changed, options) => {
124
+ if (writeStrategy === "first") {
125
+ try {
126
+ await backends[0]?.write(values, changed, options);
127
+ } catch {
128
+ }
129
+ return;
130
+ }
131
+ const promises = [];
132
+ for (const b of backends) {
133
+ try {
134
+ const result = b.write(values, changed, options);
135
+ if (result && typeof result.then === "function") {
136
+ promises.push(
137
+ result.catch((err) => {
138
+ warn(
139
+ `composeStorage: backend '${b.name}' write failed: ${err instanceof Error ? err.message : String(err)}`
140
+ );
141
+ })
142
+ );
143
+ }
144
+ } catch (err) {
145
+ warn(
146
+ `composeStorage: backend '${b.name}' write threw synchronously: ${err instanceof Error ? err.message : String(err)}`
147
+ );
148
+ }
149
+ }
150
+ await Promise.all(promises);
151
+ },
152
+ subscribe: (callback) => {
153
+ let lastEmit;
154
+ const unsubscribers = [];
155
+ for (const b of backends) {
156
+ if (!b.subscribe) continue;
157
+ const unsub = b.subscribe((values) => {
158
+ if (lastEmit !== void 0 && deepEqual(lastEmit, values)) return;
159
+ lastEmit = values;
160
+ callback(values);
161
+ });
162
+ unsubscribers.push(unsub);
163
+ }
164
+ return () => {
165
+ for (const unsub of unsubscribers) unsub();
166
+ };
167
+ },
168
+ clear: async (paths, options) => {
169
+ for (const b of backends) {
170
+ if (!b.clear) continue;
171
+ try {
172
+ const result = b.clear(paths, options);
173
+ if (result && typeof result.then === "function") {
174
+ await result;
175
+ }
176
+ } catch {
177
+ }
178
+ }
179
+ }
180
+ };
181
+ }
182
+ function deepMergeObjects(a, b) {
183
+ if (a === void 0) return b;
184
+ if (a === null || typeof a !== "object" || Array.isArray(a)) return b;
185
+ if (b === null || typeof b !== "object" || Array.isArray(b)) return b;
186
+ const out = { ...a };
187
+ for (const [key, value] of Object.entries(b)) {
188
+ out[key] = deepMergeObjects(out[key], value);
189
+ }
190
+ return out;
191
+ }
192
+ // Annotate the CommonJS export names for ESM import in node:
193
+ 0 && (module.exports = {
194
+ composeStorage
195
+ });
196
+ //# sourceMappingURL=compose.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/storage/compose/index.ts","../../../utils/src/deep.ts","../../src/dev.ts"],"sourcesContent":["import { deepEqual } from '@victorylabs/utils'\n\nimport { warn } from '../../dev'\nimport type { ParamsStorage } from '../../storage'\n\nexport interface ComposeStorageOptions {\n /**\n * Read strategy.\n * - `'first'` (default): walks the backends, returns the first non-undefined.\n * - `'merge'`: deep-merges all backends; later entries win on conflict. Arrays\n * are **replaced** (later wins), not concatenated — concatenation would\n * produce duplicates on every read.\n */\n readStrategy?: 'first' | 'merge'\n\n /**\n * Write strategy.\n * - `'all'` (default): fan out the write to every backend. Per-backend\n * failures are swallowed with a dev warning; one bad backend doesn't\n * block the others.\n * - `'first'`: writes only to the first backend. Subsequent backends are\n * read-only mirrors.\n */\n writeStrategy?: 'all' | 'first'\n\n /** Diagnostics name. Default: `'composeStorage(name1+name2+...)'`. */\n name?: string\n}\n\n/**\n * Layer multiple storage backends. Common case: URL primary + localStorage\n * fallback (URL works for shareable links; localStorage survives navigation\n * away from the URL-bearing page). Or: cookieStorage primary + serverStorage\n * mirror (fast local read; eventually-consistent server sync).\n *\n * Composing backends through `composeStorage` itself is allowed — recursive\n * layering nests cleanly.\n */\nexport function composeStorage<T>(\n backends: ReadonlyArray<ParamsStorage<T>>,\n opts: ComposeStorageOptions = {},\n): ParamsStorage<T> {\n if (backends.length === 0) {\n throw new Error('composeStorage: requires at least one backend')\n }\n\n const readStrategy = opts.readStrategy ?? 'first'\n const writeStrategy = opts.writeStrategy ?? 'all'\n const name = opts.name ?? `composeStorage(${backends.map((b) => b.name).join('+')})`\n\n return {\n name,\n // composeStorage runs on the server only if EVERY backend allows it.\n // Otherwise the server skips the read and the engine uses defaults.\n clientOnly: backends.every((b) => b.clientOnly === true),\n\n read: () => {\n if (readStrategy === 'first') {\n for (const b of backends) {\n const v = b.read()\n if (v !== undefined) return v\n }\n return undefined\n }\n // 'merge'\n let merged: Partial<T> | undefined\n for (const b of backends) {\n const v = b.read()\n if (v === undefined) continue\n merged = deepMergeObjects(merged, v) as Partial<T>\n }\n return merged\n },\n\n readAsync: async () => {\n if (readStrategy === 'first') {\n for (const b of backends) {\n const v = b.readAsync ? await b.readAsync() : b.read()\n if (v !== undefined) return v\n }\n return undefined\n }\n // 'merge'\n let merged: Partial<T> | undefined\n for (const b of backends) {\n const v = b.readAsync ? await b.readAsync() : b.read()\n if (v === undefined) continue\n merged = deepMergeObjects(merged, v) as Partial<T>\n }\n return merged\n },\n\n write: async (values, changed, options) => {\n if (writeStrategy === 'first') {\n try {\n await backends[0]?.write(values, changed, options)\n } catch {\n // Silent fallback per the storage error contract.\n }\n return\n }\n // 'all' — fan out, swallow individual failures with dev warning.\n const promises: Array<Promise<void>> = []\n for (const b of backends) {\n try {\n const result = b.write(values, changed, options)\n if (result && typeof (result as Promise<void>).then === 'function') {\n promises.push(\n (result as Promise<void>).catch((err: unknown) => {\n warn(\n `composeStorage: backend '${b.name}' write failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n }),\n )\n }\n } catch (err) {\n warn(\n `composeStorage: backend '${b.name}' write threw synchronously: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n }\n await Promise.all(promises)\n },\n\n subscribe: (callback) => {\n let lastEmit: Partial<T> | undefined\n const unsubscribers: Array<() => void> = []\n for (const b of backends) {\n if (!b.subscribe) continue\n const unsub = b.subscribe((values) => {\n // Cross-backend de-dup: drop back-to-back identical emits.\n if (lastEmit !== undefined && deepEqual(lastEmit, values)) return\n lastEmit = values\n callback(values)\n })\n unsubscribers.push(unsub)\n }\n return () => {\n for (const unsub of unsubscribers) unsub()\n }\n },\n\n clear: async (paths, options) => {\n for (const b of backends) {\n if (!b.clear) continue\n try {\n const result = b.clear(paths, options)\n if (result && typeof (result as Promise<void>).then === 'function') {\n await (result as Promise<void>)\n }\n } catch {\n // Silent fallback.\n }\n }\n },\n }\n}\n\n/**\n * Deep-merge two values with later-wins semantics. Arrays are REPLACED, not\n * concatenated — concatenation would produce duplicates on every read.\n * Plain objects merge recursively. Primitives are replaced.\n */\nfunction deepMergeObjects(a: unknown, b: unknown): unknown {\n if (a === undefined) return b\n if (a === null || typeof a !== 'object' || Array.isArray(a)) return b\n if (b === null || typeof b !== 'object' || Array.isArray(b)) return b\n const out: Record<string, unknown> = { ...(a as Record<string, unknown>) }\n for (const [key, value] of Object.entries(b as Record<string, unknown>)) {\n out[key] = deepMergeObjects(out[key], value)\n }\n return out\n}\n","/**\n * Path-aware deep helpers. Paths are dot-strings: 'address.street', 'tags.2'.\n * Numeric segments index into arrays; string segments index into objects.\n *\n * Limitation: keys containing literal '.' are not supported. Document this\n * for consumers; forms with such keys are vanishingly rare.\n */\n\nexport function splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('.')\n}\n\nexport function deepGet(obj: unknown, path: string): unknown {\n const segments = splitPath(path)\n let current: unknown = obj\n for (const seg of segments) {\n if (current === null || current === undefined) return undefined\n if (Array.isArray(current)) {\n const idx = Number(seg)\n if (!Number.isInteger(idx) || idx < 0) return undefined\n current = current[idx]\n } else if (typeof current === 'object') {\n current = (current as Record<string, unknown>)[seg]\n } else {\n return undefined\n }\n }\n return current\n}\n\nexport function deepSet<T>(obj: T, path: string, value: unknown): T {\n const segments = splitPath(path)\n if (segments.length === 0) return value as T\n return setRecursive(obj, segments, 0, value) as T\n}\n\nfunction setRecursive(current: unknown, segments: string[], i: number, value: unknown): unknown {\n const seg = segments[i]\n // Sanity: segments.length > i always when this runs (caller ensures).\n if (seg === undefined) return value\n const isLast = i === segments.length - 1\n const idx = Number(seg)\n const isNumericSeg = Number.isInteger(idx) && idx >= 0 && /^\\d+$/.test(seg)\n\n if (isNumericSeg) {\n const arr = Array.isArray(current) ? current.slice() : []\n arr[idx] = isLast ? value : setRecursive(arr[idx], segments, i + 1, value)\n return arr\n }\n\n const next: Record<string, unknown> =\n current && typeof current === 'object' && !Array.isArray(current)\n ? { ...(current as Record<string, unknown>) }\n : {}\n next[seg] = isLast ? value : setRecursive(next[seg], segments, i + 1, value)\n return next\n}\n\nexport function deepEqual(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true\n if (typeof a !== typeof b) return false\n if (a === null || b === null) return false\n if (typeof a !== 'object') return false\n\n if (Array.isArray(a)) {\n if (!Array.isArray(b)) return false\n if (a.length !== b.length) return false\n for (let i = 0; i < a.length; i++) {\n if (!deepEqual(a[i], b[i])) return false\n }\n return true\n }\n if (Array.isArray(b)) return false\n\n if (a instanceof Date) {\n return b instanceof Date && a.getTime() === b.getTime()\n }\n if (b instanceof Date) return false\n\n if (a instanceof RegExp) {\n return b instanceof RegExp && a.source === b.source && a.flags === b.flags\n }\n if (b instanceof RegExp) return false\n\n const aKeys = Object.keys(a as Record<string, unknown>)\n const bKeys = Object.keys(b as Record<string, unknown>)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (!Object.hasOwn(b as object, key)) return false\n if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])) {\n return false\n }\n }\n return true\n}\n\n/**\n * Deep merge: later sources overwrite earlier ones at scalar/array level;\n * objects merge recursively. Arrays are replaced wholesale (no concatenation),\n * which matches the form-state mental model — an array IS the value.\n */\nexport function deepMerge<T extends object>(...sources: Array<Partial<T> | undefined>): T {\n const result = {} as Record<string, unknown>\n for (const source of sources) {\n if (!source) continue\n for (const [key, value] of Object.entries(source)) {\n const existing = result[key]\n if (\n existing &&\n value &&\n typeof existing === 'object' &&\n typeof value === 'object' &&\n !Array.isArray(existing) &&\n !Array.isArray(value) &&\n !(existing instanceof Date) &&\n !(value instanceof Date)\n ) {\n result[key] = deepMerge(existing as Partial<object>, value as Partial<object>)\n } else {\n result[key] = value\n }\n }\n }\n return result as T\n}\n","// Bundlers (tsup, vite, webpack) replace `process.env.NODE_ENV` at build time.\n// We declare `process` minimally so TypeScript doesn't require @types/node.\n\ndeclare const process:\n | {\n env?: {\n NODE_ENV?: string\n }\n }\n | undefined\n\nconst isDev = ((): boolean => {\n try {\n return typeof process !== 'undefined' && process?.env?.NODE_ENV !== 'production'\n } catch {\n return false\n }\n})()\n\nconst warned = new Set<string>()\n\n/**\n * Emit a one-time dev-mode console warning. Identical messages are\n * suppressed after the first occurrence to avoid log spam.\n */\nexport function warn(message: string): void {\n if (!isDev) return\n if (warned.has(message)) return\n warned.add(message)\n console.warn(`[@victorylabs/params] ${message}`)\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,35 @@
1
+ import { P as ParamsStorage } from '../storage-DBLIRR-4.cjs';
2
+
3
+ interface ComposeStorageOptions {
4
+ /**
5
+ * Read strategy.
6
+ * - `'first'` (default): walks the backends, returns the first non-undefined.
7
+ * - `'merge'`: deep-merges all backends; later entries win on conflict. Arrays
8
+ * are **replaced** (later wins), not concatenated — concatenation would
9
+ * produce duplicates on every read.
10
+ */
11
+ readStrategy?: 'first' | 'merge';
12
+ /**
13
+ * Write strategy.
14
+ * - `'all'` (default): fan out the write to every backend. Per-backend
15
+ * failures are swallowed with a dev warning; one bad backend doesn't
16
+ * block the others.
17
+ * - `'first'`: writes only to the first backend. Subsequent backends are
18
+ * read-only mirrors.
19
+ */
20
+ writeStrategy?: 'all' | 'first';
21
+ /** Diagnostics name. Default: `'composeStorage(name1+name2+...)'`. */
22
+ name?: string;
23
+ }
24
+ /**
25
+ * Layer multiple storage backends. Common case: URL primary + localStorage
26
+ * fallback (URL works for shareable links; localStorage survives navigation
27
+ * away from the URL-bearing page). Or: cookieStorage primary + serverStorage
28
+ * mirror (fast local read; eventually-consistent server sync).
29
+ *
30
+ * Composing backends through `composeStorage` itself is allowed — recursive
31
+ * layering nests cleanly.
32
+ */
33
+ declare function composeStorage<T>(backends: ReadonlyArray<ParamsStorage<T>>, opts?: ComposeStorageOptions): ParamsStorage<T>;
34
+
35
+ export { type ComposeStorageOptions, composeStorage };
@@ -0,0 +1,35 @@
1
+ import { P as ParamsStorage } from '../storage-DBLIRR-4.js';
2
+
3
+ interface ComposeStorageOptions {
4
+ /**
5
+ * Read strategy.
6
+ * - `'first'` (default): walks the backends, returns the first non-undefined.
7
+ * - `'merge'`: deep-merges all backends; later entries win on conflict. Arrays
8
+ * are **replaced** (later wins), not concatenated — concatenation would
9
+ * produce duplicates on every read.
10
+ */
11
+ readStrategy?: 'first' | 'merge';
12
+ /**
13
+ * Write strategy.
14
+ * - `'all'` (default): fan out the write to every backend. Per-backend
15
+ * failures are swallowed with a dev warning; one bad backend doesn't
16
+ * block the others.
17
+ * - `'first'`: writes only to the first backend. Subsequent backends are
18
+ * read-only mirrors.
19
+ */
20
+ writeStrategy?: 'all' | 'first';
21
+ /** Diagnostics name. Default: `'composeStorage(name1+name2+...)'`. */
22
+ name?: string;
23
+ }
24
+ /**
25
+ * Layer multiple storage backends. Common case: URL primary + localStorage
26
+ * fallback (URL works for shareable links; localStorage survives navigation
27
+ * away from the URL-bearing page). Or: cookieStorage primary + serverStorage
28
+ * mirror (fast local read; eventually-consistent server sync).
29
+ *
30
+ * Composing backends through `composeStorage` itself is allowed — recursive
31
+ * layering nests cleanly.
32
+ */
33
+ declare function composeStorage<T>(backends: ReadonlyArray<ParamsStorage<T>>, opts?: ComposeStorageOptions): ParamsStorage<T>;
34
+
35
+ export { type ComposeStorageOptions, composeStorage };
@@ -0,0 +1,123 @@
1
+ import {
2
+ deepEqual,
3
+ warn
4
+ } from "../chunk-4T4THPFW.js";
5
+
6
+ // src/storage/compose/index.ts
7
+ function composeStorage(backends, opts = {}) {
8
+ if (backends.length === 0) {
9
+ throw new Error("composeStorage: requires at least one backend");
10
+ }
11
+ const readStrategy = opts.readStrategy ?? "first";
12
+ const writeStrategy = opts.writeStrategy ?? "all";
13
+ const name = opts.name ?? `composeStorage(${backends.map((b) => b.name).join("+")})`;
14
+ return {
15
+ name,
16
+ // composeStorage runs on the server only if EVERY backend allows it.
17
+ // Otherwise the server skips the read and the engine uses defaults.
18
+ clientOnly: backends.every((b) => b.clientOnly === true),
19
+ read: () => {
20
+ if (readStrategy === "first") {
21
+ for (const b of backends) {
22
+ const v = b.read();
23
+ if (v !== void 0) return v;
24
+ }
25
+ return void 0;
26
+ }
27
+ let merged;
28
+ for (const b of backends) {
29
+ const v = b.read();
30
+ if (v === void 0) continue;
31
+ merged = deepMergeObjects(merged, v);
32
+ }
33
+ return merged;
34
+ },
35
+ readAsync: async () => {
36
+ if (readStrategy === "first") {
37
+ for (const b of backends) {
38
+ const v = b.readAsync ? await b.readAsync() : b.read();
39
+ if (v !== void 0) return v;
40
+ }
41
+ return void 0;
42
+ }
43
+ let merged;
44
+ for (const b of backends) {
45
+ const v = b.readAsync ? await b.readAsync() : b.read();
46
+ if (v === void 0) continue;
47
+ merged = deepMergeObjects(merged, v);
48
+ }
49
+ return merged;
50
+ },
51
+ write: async (values, changed, options) => {
52
+ if (writeStrategy === "first") {
53
+ try {
54
+ await backends[0]?.write(values, changed, options);
55
+ } catch {
56
+ }
57
+ return;
58
+ }
59
+ const promises = [];
60
+ for (const b of backends) {
61
+ try {
62
+ const result = b.write(values, changed, options);
63
+ if (result && typeof result.then === "function") {
64
+ promises.push(
65
+ result.catch((err) => {
66
+ warn(
67
+ `composeStorage: backend '${b.name}' write failed: ${err instanceof Error ? err.message : String(err)}`
68
+ );
69
+ })
70
+ );
71
+ }
72
+ } catch (err) {
73
+ warn(
74
+ `composeStorage: backend '${b.name}' write threw synchronously: ${err instanceof Error ? err.message : String(err)}`
75
+ );
76
+ }
77
+ }
78
+ await Promise.all(promises);
79
+ },
80
+ subscribe: (callback) => {
81
+ let lastEmit;
82
+ const unsubscribers = [];
83
+ for (const b of backends) {
84
+ if (!b.subscribe) continue;
85
+ const unsub = b.subscribe((values) => {
86
+ if (lastEmit !== void 0 && deepEqual(lastEmit, values)) return;
87
+ lastEmit = values;
88
+ callback(values);
89
+ });
90
+ unsubscribers.push(unsub);
91
+ }
92
+ return () => {
93
+ for (const unsub of unsubscribers) unsub();
94
+ };
95
+ },
96
+ clear: async (paths, options) => {
97
+ for (const b of backends) {
98
+ if (!b.clear) continue;
99
+ try {
100
+ const result = b.clear(paths, options);
101
+ if (result && typeof result.then === "function") {
102
+ await result;
103
+ }
104
+ } catch {
105
+ }
106
+ }
107
+ }
108
+ };
109
+ }
110
+ function deepMergeObjects(a, b) {
111
+ if (a === void 0) return b;
112
+ if (a === null || typeof a !== "object" || Array.isArray(a)) return b;
113
+ if (b === null || typeof b !== "object" || Array.isArray(b)) return b;
114
+ const out = { ...a };
115
+ for (const [key, value] of Object.entries(b)) {
116
+ out[key] = deepMergeObjects(out[key], value);
117
+ }
118
+ return out;
119
+ }
120
+ export {
121
+ composeStorage
122
+ };
123
+ //# sourceMappingURL=compose.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/storage/compose/index.ts"],"sourcesContent":["import { deepEqual } from '@victorylabs/utils'\n\nimport { warn } from '../../dev'\nimport type { ParamsStorage } from '../../storage'\n\nexport interface ComposeStorageOptions {\n /**\n * Read strategy.\n * - `'first'` (default): walks the backends, returns the first non-undefined.\n * - `'merge'`: deep-merges all backends; later entries win on conflict. Arrays\n * are **replaced** (later wins), not concatenated — concatenation would\n * produce duplicates on every read.\n */\n readStrategy?: 'first' | 'merge'\n\n /**\n * Write strategy.\n * - `'all'` (default): fan out the write to every backend. Per-backend\n * failures are swallowed with a dev warning; one bad backend doesn't\n * block the others.\n * - `'first'`: writes only to the first backend. Subsequent backends are\n * read-only mirrors.\n */\n writeStrategy?: 'all' | 'first'\n\n /** Diagnostics name. Default: `'composeStorage(name1+name2+...)'`. */\n name?: string\n}\n\n/**\n * Layer multiple storage backends. Common case: URL primary + localStorage\n * fallback (URL works for shareable links; localStorage survives navigation\n * away from the URL-bearing page). Or: cookieStorage primary + serverStorage\n * mirror (fast local read; eventually-consistent server sync).\n *\n * Composing backends through `composeStorage` itself is allowed — recursive\n * layering nests cleanly.\n */\nexport function composeStorage<T>(\n backends: ReadonlyArray<ParamsStorage<T>>,\n opts: ComposeStorageOptions = {},\n): ParamsStorage<T> {\n if (backends.length === 0) {\n throw new Error('composeStorage: requires at least one backend')\n }\n\n const readStrategy = opts.readStrategy ?? 'first'\n const writeStrategy = opts.writeStrategy ?? 'all'\n const name = opts.name ?? `composeStorage(${backends.map((b) => b.name).join('+')})`\n\n return {\n name,\n // composeStorage runs on the server only if EVERY backend allows it.\n // Otherwise the server skips the read and the engine uses defaults.\n clientOnly: backends.every((b) => b.clientOnly === true),\n\n read: () => {\n if (readStrategy === 'first') {\n for (const b of backends) {\n const v = b.read()\n if (v !== undefined) return v\n }\n return undefined\n }\n // 'merge'\n let merged: Partial<T> | undefined\n for (const b of backends) {\n const v = b.read()\n if (v === undefined) continue\n merged = deepMergeObjects(merged, v) as Partial<T>\n }\n return merged\n },\n\n readAsync: async () => {\n if (readStrategy === 'first') {\n for (const b of backends) {\n const v = b.readAsync ? await b.readAsync() : b.read()\n if (v !== undefined) return v\n }\n return undefined\n }\n // 'merge'\n let merged: Partial<T> | undefined\n for (const b of backends) {\n const v = b.readAsync ? await b.readAsync() : b.read()\n if (v === undefined) continue\n merged = deepMergeObjects(merged, v) as Partial<T>\n }\n return merged\n },\n\n write: async (values, changed, options) => {\n if (writeStrategy === 'first') {\n try {\n await backends[0]?.write(values, changed, options)\n } catch {\n // Silent fallback per the storage error contract.\n }\n return\n }\n // 'all' — fan out, swallow individual failures with dev warning.\n const promises: Array<Promise<void>> = []\n for (const b of backends) {\n try {\n const result = b.write(values, changed, options)\n if (result && typeof (result as Promise<void>).then === 'function') {\n promises.push(\n (result as Promise<void>).catch((err: unknown) => {\n warn(\n `composeStorage: backend '${b.name}' write failed: ${err instanceof Error ? err.message : String(err)}`,\n )\n }),\n )\n }\n } catch (err) {\n warn(\n `composeStorage: backend '${b.name}' write threw synchronously: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n }\n await Promise.all(promises)\n },\n\n subscribe: (callback) => {\n let lastEmit: Partial<T> | undefined\n const unsubscribers: Array<() => void> = []\n for (const b of backends) {\n if (!b.subscribe) continue\n const unsub = b.subscribe((values) => {\n // Cross-backend de-dup: drop back-to-back identical emits.\n if (lastEmit !== undefined && deepEqual(lastEmit, values)) return\n lastEmit = values\n callback(values)\n })\n unsubscribers.push(unsub)\n }\n return () => {\n for (const unsub of unsubscribers) unsub()\n }\n },\n\n clear: async (paths, options) => {\n for (const b of backends) {\n if (!b.clear) continue\n try {\n const result = b.clear(paths, options)\n if (result && typeof (result as Promise<void>).then === 'function') {\n await (result as Promise<void>)\n }\n } catch {\n // Silent fallback.\n }\n }\n },\n }\n}\n\n/**\n * Deep-merge two values with later-wins semantics. Arrays are REPLACED, not\n * concatenated — concatenation would produce duplicates on every read.\n * Plain objects merge recursively. Primitives are replaced.\n */\nfunction deepMergeObjects(a: unknown, b: unknown): unknown {\n if (a === undefined) return b\n if (a === null || typeof a !== 'object' || Array.isArray(a)) return b\n if (b === null || typeof b !== 'object' || Array.isArray(b)) return b\n const out: Record<string, unknown> = { ...(a as Record<string, unknown>) }\n for (const [key, value] of Object.entries(b as Record<string, unknown>)) {\n out[key] = deepMergeObjects(out[key], value)\n }\n return out\n}\n"],"mappings":"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","names":[]}