@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,777 @@
|
|
|
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/react/integrations/forms-reverse/index.ts
|
|
21
|
+
var forms_reverse_exports = {};
|
|
22
|
+
__export(forms_reverse_exports, {
|
|
23
|
+
useFormToParamsSync: () => useFormToParamsSync
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(forms_reverse_exports);
|
|
26
|
+
var import_react = require("react");
|
|
27
|
+
|
|
28
|
+
// src/dev.ts
|
|
29
|
+
var isDev = (() => {
|
|
30
|
+
try {
|
|
31
|
+
return typeof process !== "undefined" && process?.env?.NODE_ENV !== "production";
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
36
|
+
var warned = /* @__PURE__ */ new Set();
|
|
37
|
+
function warn(message) {
|
|
38
|
+
if (!isDev) return;
|
|
39
|
+
if (warned.has(message)) return;
|
|
40
|
+
warned.add(message);
|
|
41
|
+
console.warn(`[@victorylabs/params] ${message}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ../utils/src/cascade.ts
|
|
45
|
+
var DEPTH_CAP = 10;
|
|
46
|
+
function resolveCascade(initialChanges, ctx) {
|
|
47
|
+
const changes = { ...initialChanges };
|
|
48
|
+
const warnings = [];
|
|
49
|
+
const working = { ...ctx.currentValues, ...initialChanges };
|
|
50
|
+
let frontier = new Set(Object.keys(initialChanges));
|
|
51
|
+
const visited = new Set(frontier);
|
|
52
|
+
let depth = 0;
|
|
53
|
+
while (frontier.size > 0) {
|
|
54
|
+
if (depth >= DEPTH_CAP) {
|
|
55
|
+
warnings.push(
|
|
56
|
+
`cascade depth cap (${DEPTH_CAP}) reached; stopping. Frontier: [${[...frontier].join(", ")}]. Likely a config error \u2014 check for accidental long chains.`
|
|
57
|
+
);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
const nextFrontier = /* @__PURE__ */ new Set();
|
|
61
|
+
for (const targetPath of ctx.allPaths) {
|
|
62
|
+
const config = ctx.fieldConfigs[targetPath];
|
|
63
|
+
if (!config?.onDepChange) continue;
|
|
64
|
+
if (frontier.has(targetPath)) continue;
|
|
65
|
+
const deps = resolveDeps(config, ctx.allPaths, targetPath);
|
|
66
|
+
const changedDeps = deps.filter((d) => frontier.has(d));
|
|
67
|
+
if (changedDeps.length === 0) continue;
|
|
68
|
+
if (visited.has(targetPath)) {
|
|
69
|
+
warnings.push(
|
|
70
|
+
`cycle detected: '${targetPath}' would cascade twice (triggered by [${changedDeps.join(", ")}]). Skipping re-entry.`
|
|
71
|
+
);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const newValue = applyDepChange(config.onDepChange, deps, working, ctx.defaults, targetPath);
|
|
75
|
+
if (Object.is(newValue, working[targetPath])) continue;
|
|
76
|
+
changes[targetPath] = newValue;
|
|
77
|
+
working[targetPath] = newValue;
|
|
78
|
+
visited.add(targetPath);
|
|
79
|
+
nextFrontier.add(targetPath);
|
|
80
|
+
}
|
|
81
|
+
frontier = nextFrontier;
|
|
82
|
+
depth++;
|
|
83
|
+
}
|
|
84
|
+
return { changes, warnings };
|
|
85
|
+
}
|
|
86
|
+
function resolveDeps(config, allPaths, selfPath) {
|
|
87
|
+
if (config.dependsOn === "*") {
|
|
88
|
+
const exclude = new Set(config.excludeDeps ?? []);
|
|
89
|
+
exclude.add(selfPath);
|
|
90
|
+
return allPaths.filter((p) => !exclude.has(p));
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(config.dependsOn)) {
|
|
93
|
+
return config.dependsOn.filter((p) => p !== selfPath);
|
|
94
|
+
}
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
function applyDepChange(onDepChange, deps, working, defaults, targetPath) {
|
|
98
|
+
if (onDepChange === "reset") return defaults[targetPath];
|
|
99
|
+
if (onDepChange === "clear") return void 0;
|
|
100
|
+
const depsRecord = {};
|
|
101
|
+
for (const d of deps) {
|
|
102
|
+
depsRecord[d] = working[d];
|
|
103
|
+
}
|
|
104
|
+
return onDepChange(depsRecord, working[targetPath]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ../utils/src/deep.ts
|
|
108
|
+
function splitPath(path) {
|
|
109
|
+
if (path === "") return [];
|
|
110
|
+
return path.split(".");
|
|
111
|
+
}
|
|
112
|
+
function deepGet(obj, path) {
|
|
113
|
+
const segments = splitPath(path);
|
|
114
|
+
let current = obj;
|
|
115
|
+
for (const seg of segments) {
|
|
116
|
+
if (current === null || current === void 0) return void 0;
|
|
117
|
+
if (Array.isArray(current)) {
|
|
118
|
+
const idx = Number(seg);
|
|
119
|
+
if (!Number.isInteger(idx) || idx < 0) return void 0;
|
|
120
|
+
current = current[idx];
|
|
121
|
+
} else if (typeof current === "object") {
|
|
122
|
+
current = current[seg];
|
|
123
|
+
} else {
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return current;
|
|
128
|
+
}
|
|
129
|
+
function deepSet(obj, path, value) {
|
|
130
|
+
const segments = splitPath(path);
|
|
131
|
+
if (segments.length === 0) return value;
|
|
132
|
+
return setRecursive(obj, segments, 0, value);
|
|
133
|
+
}
|
|
134
|
+
function setRecursive(current, segments, i, value) {
|
|
135
|
+
const seg = segments[i];
|
|
136
|
+
if (seg === void 0) return value;
|
|
137
|
+
const isLast = i === segments.length - 1;
|
|
138
|
+
const idx = Number(seg);
|
|
139
|
+
const isNumericSeg = Number.isInteger(idx) && idx >= 0 && /^\d+$/.test(seg);
|
|
140
|
+
if (isNumericSeg) {
|
|
141
|
+
const arr = Array.isArray(current) ? current.slice() : [];
|
|
142
|
+
arr[idx] = isLast ? value : setRecursive(arr[idx], segments, i + 1, value);
|
|
143
|
+
return arr;
|
|
144
|
+
}
|
|
145
|
+
const next = current && typeof current === "object" && !Array.isArray(current) ? { ...current } : {};
|
|
146
|
+
next[seg] = isLast ? value : setRecursive(next[seg], segments, i + 1, value);
|
|
147
|
+
return next;
|
|
148
|
+
}
|
|
149
|
+
function deepEqual(a, b) {
|
|
150
|
+
if (Object.is(a, b)) return true;
|
|
151
|
+
if (typeof a !== typeof b) return false;
|
|
152
|
+
if (a === null || b === null) return false;
|
|
153
|
+
if (typeof a !== "object") return false;
|
|
154
|
+
if (Array.isArray(a)) {
|
|
155
|
+
if (!Array.isArray(b)) return false;
|
|
156
|
+
if (a.length !== b.length) return false;
|
|
157
|
+
for (let i = 0; i < a.length; i++) {
|
|
158
|
+
if (!deepEqual(a[i], b[i])) return false;
|
|
159
|
+
}
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
if (Array.isArray(b)) return false;
|
|
163
|
+
if (a instanceof Date) {
|
|
164
|
+
return b instanceof Date && a.getTime() === b.getTime();
|
|
165
|
+
}
|
|
166
|
+
if (b instanceof Date) return false;
|
|
167
|
+
if (a instanceof RegExp) {
|
|
168
|
+
return b instanceof RegExp && a.source === b.source && a.flags === b.flags;
|
|
169
|
+
}
|
|
170
|
+
if (b instanceof RegExp) return false;
|
|
171
|
+
const aKeys = Object.keys(a);
|
|
172
|
+
const bKeys = Object.keys(b);
|
|
173
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
174
|
+
for (const key of aKeys) {
|
|
175
|
+
if (!Object.hasOwn(b, key)) return false;
|
|
176
|
+
if (!deepEqual(a[key], b[key])) {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ../utils/src/path-trie.ts
|
|
184
|
+
var makeNode = () => ({ listeners: /* @__PURE__ */ new Set(), children: /* @__PURE__ */ new Map() });
|
|
185
|
+
var PathTrie = class {
|
|
186
|
+
root = makeNode();
|
|
187
|
+
subscribe(path, listener) {
|
|
188
|
+
const node = this.ensure(path);
|
|
189
|
+
node.listeners.add(listener);
|
|
190
|
+
return () => {
|
|
191
|
+
node.listeners.delete(listener);
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
notify(path) {
|
|
195
|
+
const segments = splitPath(path);
|
|
196
|
+
let current = this.root;
|
|
197
|
+
fire(current);
|
|
198
|
+
for (const seg of segments) {
|
|
199
|
+
const next = current.children.get(seg);
|
|
200
|
+
if (!next) return;
|
|
201
|
+
current = next;
|
|
202
|
+
fire(current);
|
|
203
|
+
}
|
|
204
|
+
fireSubtree(current);
|
|
205
|
+
}
|
|
206
|
+
/** Test/diagnostic helper — total subscriber count below a given path. */
|
|
207
|
+
size(path = "") {
|
|
208
|
+
const node = this.find(path);
|
|
209
|
+
if (!node) return 0;
|
|
210
|
+
return countSubtree(node);
|
|
211
|
+
}
|
|
212
|
+
ensure(path) {
|
|
213
|
+
let node = this.root;
|
|
214
|
+
for (const seg of splitPath(path)) {
|
|
215
|
+
let child = node.children.get(seg);
|
|
216
|
+
if (!child) {
|
|
217
|
+
child = makeNode();
|
|
218
|
+
node.children.set(seg, child);
|
|
219
|
+
}
|
|
220
|
+
node = child;
|
|
221
|
+
}
|
|
222
|
+
return node;
|
|
223
|
+
}
|
|
224
|
+
find(path) {
|
|
225
|
+
let node = this.root;
|
|
226
|
+
for (const seg of splitPath(path)) {
|
|
227
|
+
node = node?.children.get(seg);
|
|
228
|
+
if (!node) return void 0;
|
|
229
|
+
}
|
|
230
|
+
return node;
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
function fire(node) {
|
|
234
|
+
for (const listener of node.listeners) listener();
|
|
235
|
+
}
|
|
236
|
+
function fireSubtree(node) {
|
|
237
|
+
for (const child of node.children.values()) {
|
|
238
|
+
fire(child);
|
|
239
|
+
fireSubtree(child);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function countSubtree(node) {
|
|
243
|
+
let total = node.listeners.size;
|
|
244
|
+
for (const child of node.children.values()) {
|
|
245
|
+
total += countSubtree(child);
|
|
246
|
+
}
|
|
247
|
+
return total;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// src/name-registry.ts
|
|
251
|
+
var preHydrationCache = /* @__PURE__ */ new Map();
|
|
252
|
+
function takePreHydrationValues(name) {
|
|
253
|
+
if (name === void 0) return void 0;
|
|
254
|
+
if (!preHydrationCache.has(name)) return void 0;
|
|
255
|
+
return preHydrationCache.get(name);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/schema.ts
|
|
259
|
+
function isStandardSchema(spec) {
|
|
260
|
+
return typeof spec === "object" && spec !== null && "~standard" in spec && typeof spec["~standard"] === "object";
|
|
261
|
+
}
|
|
262
|
+
function isPlainSpec(spec) {
|
|
263
|
+
return !isStandardSchema(spec) && typeof spec === "object" && spec !== null && "default" in spec;
|
|
264
|
+
}
|
|
265
|
+
function getDefault(spec) {
|
|
266
|
+
if (isStandardSchema(spec)) {
|
|
267
|
+
const result = spec["~standard"].validate(void 0);
|
|
268
|
+
if (result instanceof Promise) return void 0;
|
|
269
|
+
if ("value" in result && !result.issues) return result.value;
|
|
270
|
+
return void 0;
|
|
271
|
+
}
|
|
272
|
+
return spec.default;
|
|
273
|
+
}
|
|
274
|
+
function parseField(spec, raw) {
|
|
275
|
+
if (isStandardSchema(spec)) {
|
|
276
|
+
const result = spec["~standard"].validate(raw);
|
|
277
|
+
if (result instanceof Promise) {
|
|
278
|
+
return { ok: false, reason: "async-schema-not-supported-in-v0.1" };
|
|
279
|
+
}
|
|
280
|
+
if ("value" in result && !result.issues) return { ok: true, value: result.value };
|
|
281
|
+
return { ok: false, reason: result.issues?.[0]?.message ?? "parse-failed" };
|
|
282
|
+
}
|
|
283
|
+
const plainSpec = spec;
|
|
284
|
+
if (typeof raw === "string" && plainSpec.parse) {
|
|
285
|
+
try {
|
|
286
|
+
return { ok: true, value: plainSpec.parse(raw) };
|
|
287
|
+
} catch (err) {
|
|
288
|
+
return { ok: false, reason: err instanceof Error ? err.message : String(err) };
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
if (raw === void 0) return { ok: false, reason: "undefined-no-parse" };
|
|
292
|
+
return { ok: true, value: raw };
|
|
293
|
+
}
|
|
294
|
+
function defaultSerialize(value) {
|
|
295
|
+
if (value === void 0 || value === null) return "";
|
|
296
|
+
if (typeof value === "string") return value;
|
|
297
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
298
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "";
|
|
299
|
+
return JSON.stringify(value);
|
|
300
|
+
}
|
|
301
|
+
function extractEnumValues(spec) {
|
|
302
|
+
let current = spec;
|
|
303
|
+
for (let i = 0; i < 8 && current !== null && typeof current === "object"; i++) {
|
|
304
|
+
const def = current._def;
|
|
305
|
+
if (!def) return void 0;
|
|
306
|
+
if (Array.isArray(def.values)) return def.values;
|
|
307
|
+
if (typeof def.values === "object" && def.values !== null) {
|
|
308
|
+
const all = Object.values(def.values);
|
|
309
|
+
const hasNumber = all.some((v) => typeof v === "number");
|
|
310
|
+
return hasNumber ? all.filter((v) => typeof v === "number") : all;
|
|
311
|
+
}
|
|
312
|
+
if (def.innerType) {
|
|
313
|
+
current = def.innerType;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
return void 0;
|
|
317
|
+
}
|
|
318
|
+
return void 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/params-store.ts
|
|
322
|
+
var isClient = typeof window !== "undefined";
|
|
323
|
+
var ParamsStore = class {
|
|
324
|
+
spec;
|
|
325
|
+
storage;
|
|
326
|
+
fieldConfigs;
|
|
327
|
+
values;
|
|
328
|
+
defaults;
|
|
329
|
+
trie = new PathTrie();
|
|
330
|
+
storageErrorMap = /* @__PURE__ */ new Map();
|
|
331
|
+
lastWritten;
|
|
332
|
+
storageUnsubscribe;
|
|
333
|
+
toQueryCache;
|
|
334
|
+
disposed = false;
|
|
335
|
+
constructor(def) {
|
|
336
|
+
this.spec = def.spec;
|
|
337
|
+
this.storage = def.storage;
|
|
338
|
+
this.fieldConfigs = def.fields;
|
|
339
|
+
this.defaults = this.computeDefaults();
|
|
340
|
+
this.values = { ...this.defaults };
|
|
341
|
+
const seeded = takePreHydrationValues(def.name);
|
|
342
|
+
if (seeded !== void 0) {
|
|
343
|
+
this.values = { ...this.defaults, ...seeded };
|
|
344
|
+
} else if (this.storage.clientOnly && !isClient) {
|
|
345
|
+
} else {
|
|
346
|
+
this.hydrateFromStorage();
|
|
347
|
+
}
|
|
348
|
+
if (this.storage.subscribe) {
|
|
349
|
+
this.storageUnsubscribe = this.storage.subscribe((raw) => this.onExternalChange(raw));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// ─── Reads (synchronous, framework-agnostic) ──────────────────────────
|
|
353
|
+
getValues() {
|
|
354
|
+
return this.values;
|
|
355
|
+
}
|
|
356
|
+
getValue(path) {
|
|
357
|
+
return deepGet(this.values, path);
|
|
358
|
+
}
|
|
359
|
+
/** Storage parse failures discovered on hydrate or external change. */
|
|
360
|
+
get storageErrors() {
|
|
361
|
+
const out = {};
|
|
362
|
+
for (const [path, reason] of this.storageErrorMap) out[path] = reason;
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
/** Per-field config (for the React adapter to read debounce settings, etc.). */
|
|
366
|
+
getFieldConfig(path) {
|
|
367
|
+
return this.fieldConfigs[path];
|
|
368
|
+
}
|
|
369
|
+
set(pathOrPartial, valueOrOptions, maybeOptions) {
|
|
370
|
+
if (this.disposed) return;
|
|
371
|
+
let updates;
|
|
372
|
+
let options;
|
|
373
|
+
if (typeof pathOrPartial === "string") {
|
|
374
|
+
updates = { [pathOrPartial]: valueOrOptions };
|
|
375
|
+
options = maybeOptions;
|
|
376
|
+
} else {
|
|
377
|
+
updates = pathOrPartial;
|
|
378
|
+
options = valueOrOptions;
|
|
379
|
+
}
|
|
380
|
+
const initialChanges = {};
|
|
381
|
+
for (const [path, v] of Object.entries(updates)) {
|
|
382
|
+
const old = deepGet(this.values, path);
|
|
383
|
+
if (deepEqual(old, v)) continue;
|
|
384
|
+
initialChanges[path] = v;
|
|
385
|
+
}
|
|
386
|
+
if (Object.keys(initialChanges).length === 0) return;
|
|
387
|
+
const cascade = resolveCascade(initialChanges, {
|
|
388
|
+
fieldConfigs: this.fieldConfigs,
|
|
389
|
+
defaults: this.defaults,
|
|
390
|
+
currentValues: this.values,
|
|
391
|
+
allPaths: Object.keys(this.spec)
|
|
392
|
+
});
|
|
393
|
+
for (const w of cascade.warnings) warn(w);
|
|
394
|
+
const changed = [];
|
|
395
|
+
let next = this.values;
|
|
396
|
+
for (const [path, v] of Object.entries(cascade.changes)) {
|
|
397
|
+
const old = deepGet(next, path);
|
|
398
|
+
if (deepEqual(old, v)) continue;
|
|
399
|
+
next = deepSet(next, path, v);
|
|
400
|
+
changed.push(path);
|
|
401
|
+
}
|
|
402
|
+
if (changed.length === 0) return;
|
|
403
|
+
this.values = next;
|
|
404
|
+
this.invalidateToQueryCache();
|
|
405
|
+
for (const path of changed) this.trie.notify(path);
|
|
406
|
+
this.persistToStorage(changed, options);
|
|
407
|
+
}
|
|
408
|
+
/** Boolean-flip helper. */
|
|
409
|
+
toggle(path, options) {
|
|
410
|
+
const current = this.getValue(path);
|
|
411
|
+
this.set(path, !current, options);
|
|
412
|
+
}
|
|
413
|
+
/** Push a value onto an array field. */
|
|
414
|
+
append(path, value, options) {
|
|
415
|
+
const current = this.getValue(path);
|
|
416
|
+
if (!Array.isArray(current)) return;
|
|
417
|
+
this.set(path, [...current, value], options);
|
|
418
|
+
}
|
|
419
|
+
/** Remove the first array element matching `value` by deepEqual. */
|
|
420
|
+
remove(path, value, options) {
|
|
421
|
+
const current = this.getValue(path);
|
|
422
|
+
if (!Array.isArray(current)) return;
|
|
423
|
+
const idx = current.findIndex((item) => deepEqual(item, value));
|
|
424
|
+
if (idx === -1) return;
|
|
425
|
+
this.set(path, [...current.slice(0, idx), ...current.slice(idx + 1)], options);
|
|
426
|
+
}
|
|
427
|
+
/** Remove the array element at the given index. */
|
|
428
|
+
removeAt(path, index, options) {
|
|
429
|
+
const current = this.getValue(path);
|
|
430
|
+
if (!Array.isArray(current)) return;
|
|
431
|
+
if (index < 0 || index >= current.length) return;
|
|
432
|
+
this.set(path, [...current.slice(0, index), ...current.slice(index + 1)], options);
|
|
433
|
+
}
|
|
434
|
+
cycle(path, valuesOrOptions, maybeOptions) {
|
|
435
|
+
let resolved;
|
|
436
|
+
let setOpts;
|
|
437
|
+
if (Array.isArray(valuesOrOptions)) {
|
|
438
|
+
resolved = valuesOrOptions;
|
|
439
|
+
setOpts = maybeOptions;
|
|
440
|
+
} else {
|
|
441
|
+
resolved = void 0;
|
|
442
|
+
setOpts = valuesOrOptions;
|
|
443
|
+
}
|
|
444
|
+
if (resolved === void 0) {
|
|
445
|
+
const fieldSpec = this.spec[path];
|
|
446
|
+
const derived = fieldSpec !== void 0 ? extractEnumValues(fieldSpec) : void 0;
|
|
447
|
+
if (!derived || derived.length === 0) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`Cannot derive enum values for params field '${path}'; pass values explicitly to cycle().`
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
resolved = derived;
|
|
453
|
+
}
|
|
454
|
+
if (resolved.length === 0) return;
|
|
455
|
+
const current = this.getValue(path);
|
|
456
|
+
const idx = resolved.findIndex((o) => deepEqual(o, current));
|
|
457
|
+
const next = idx === -1 ? resolved[0] : resolved[(idx + 1) % resolved.length];
|
|
458
|
+
this.set(path, next, setOpts);
|
|
459
|
+
}
|
|
460
|
+
/** Reset a single field to its default. */
|
|
461
|
+
clear(path, options) {
|
|
462
|
+
const def = deepGet(this.defaults, path);
|
|
463
|
+
this.set(path, def, options);
|
|
464
|
+
}
|
|
465
|
+
/** Reset all fields to defaults; optional partial overrides + SetOptions. */
|
|
466
|
+
reset(values, options) {
|
|
467
|
+
if (this.disposed) return;
|
|
468
|
+
const next = values ? { ...this.defaults, ...values } : { ...this.defaults };
|
|
469
|
+
const changed = Object.keys({ ...this.values, ...next });
|
|
470
|
+
this.values = next;
|
|
471
|
+
this.invalidateToQueryCache();
|
|
472
|
+
for (const path of changed) this.trie.notify(path);
|
|
473
|
+
const writeOptions = options?.history !== void 0 ? { history: options.history } : void 0;
|
|
474
|
+
void this.storage.clear?.([], writeOptions);
|
|
475
|
+
this.lastWritten = void 0;
|
|
476
|
+
}
|
|
477
|
+
// ─── Subscriptions ────────────────────────────────────────────────────
|
|
478
|
+
subscribe(path, listener) {
|
|
479
|
+
return this.trie.subscribe(path, listener);
|
|
480
|
+
}
|
|
481
|
+
// ─── URL helpers ──────────────────────────────────────────────────────
|
|
482
|
+
toQuery(overrides) {
|
|
483
|
+
const effective = overrides ? { ...this.values, ...overrides } : this.values;
|
|
484
|
+
if (!overrides && this.toQueryCache && this.toQueryCache.values === this.values) {
|
|
485
|
+
return this.toQueryCache.query;
|
|
486
|
+
}
|
|
487
|
+
const params = new URLSearchParams();
|
|
488
|
+
const keys = Object.keys(effective).sort();
|
|
489
|
+
for (const key of keys) {
|
|
490
|
+
const value = effective[key];
|
|
491
|
+
if (value === void 0 || value === null) continue;
|
|
492
|
+
const config = this.fieldConfigs[key];
|
|
493
|
+
const def = deepGet(this.defaults, key);
|
|
494
|
+
if (config?.omitWhenDefault && deepEqual(value, def)) continue;
|
|
495
|
+
params.set(key, defaultSerialize(value));
|
|
496
|
+
}
|
|
497
|
+
const str = params.toString();
|
|
498
|
+
const out = str ? `?${str}` : "";
|
|
499
|
+
if (!overrides) this.toQueryCache = { values: this.values, query: out };
|
|
500
|
+
return out;
|
|
501
|
+
}
|
|
502
|
+
href(overrides) {
|
|
503
|
+
if (!overrides) {
|
|
504
|
+
const query = this.toQuery();
|
|
505
|
+
const pathname2 = isClient ? window.location.pathname : "";
|
|
506
|
+
const cached = this.toQueryCache;
|
|
507
|
+
if (cached && cached.values === this.values && cached.href !== void 0) {
|
|
508
|
+
const expected = pathname2 + query;
|
|
509
|
+
if (cached.href === expected) return cached.href;
|
|
510
|
+
}
|
|
511
|
+
const out = pathname2 + query;
|
|
512
|
+
if (cached && cached.values === this.values) cached.href = out;
|
|
513
|
+
return out;
|
|
514
|
+
}
|
|
515
|
+
const pathname = isClient ? window.location.pathname : "";
|
|
516
|
+
return pathname + this.toQuery(overrides);
|
|
517
|
+
}
|
|
518
|
+
// ─── Disposal ─────────────────────────────────────────────────────────
|
|
519
|
+
dispose() {
|
|
520
|
+
if (this.disposed) return;
|
|
521
|
+
this.disposed = true;
|
|
522
|
+
this.storageUnsubscribe?.();
|
|
523
|
+
this.storageUnsubscribe = void 0;
|
|
524
|
+
}
|
|
525
|
+
// ─── Devtools / debugging escape hatch (v0.5) ─────────────────────────
|
|
526
|
+
/**
|
|
527
|
+
* Devtools / debugging escape hatch. Returns a frozen, defensive-copied
|
|
528
|
+
* snapshot of all internal state. Pure read — no side effects, no
|
|
529
|
+
* subscriptions registered.
|
|
530
|
+
*
|
|
531
|
+
* Uses `structuredClone` for the values + defaults deep-copy. Because
|
|
532
|
+
* params values round-trip through storage backends, they're already
|
|
533
|
+
* restricted to JSON-safe shapes — so `__introspect()` is safer here than
|
|
534
|
+
* on `FormStore`. Still: marked unstable via the `__` prefix; field set
|
|
535
|
+
* may grow over releases.
|
|
536
|
+
*/
|
|
537
|
+
__introspect() {
|
|
538
|
+
const specTypes = {};
|
|
539
|
+
for (const [path, spec] of Object.entries(this.spec)) {
|
|
540
|
+
if (isPlainSpec(spec)) {
|
|
541
|
+
specTypes[path] = "plain";
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (spec?._def) {
|
|
545
|
+
specTypes[path] = "zod";
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
if (isStandardSchema(spec)) {
|
|
549
|
+
specTypes[path] = "standard-schema";
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
specTypes[path] = "plain";
|
|
553
|
+
}
|
|
554
|
+
let valuesClone;
|
|
555
|
+
let defaultsClone;
|
|
556
|
+
let lastWrittenClone;
|
|
557
|
+
try {
|
|
558
|
+
valuesClone = structuredClone(this.values);
|
|
559
|
+
defaultsClone = structuredClone(this.defaults);
|
|
560
|
+
lastWrittenClone = this.lastWritten ? structuredClone(this.lastWritten) : void 0;
|
|
561
|
+
} catch (err) {
|
|
562
|
+
throw new Error(
|
|
563
|
+
`ParamsStore.__introspect() failed: values contain non-cloneable data (functions, Symbols, DOM nodes are not allowed in params state). Original error: ${err instanceof Error ? err.message : String(err)}`
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return Object.freeze({
|
|
567
|
+
values: valuesClone,
|
|
568
|
+
defaults: defaultsClone,
|
|
569
|
+
fieldsConfigured: Object.freeze({ ...this.fieldConfigs }),
|
|
570
|
+
fieldsBySpecType: Object.freeze(specTypes),
|
|
571
|
+
storageErrors: Object.freeze({ ...this.storageErrors }),
|
|
572
|
+
storage: Object.freeze({
|
|
573
|
+
name: this.storage.name,
|
|
574
|
+
clientOnly: this.storage.clientOnly === true,
|
|
575
|
+
hasReadAsync: this.storage.readAsync !== void 0,
|
|
576
|
+
hasSubscribe: this.storage.subscribe !== void 0,
|
|
577
|
+
hasClear: this.storage.clear !== void 0
|
|
578
|
+
}),
|
|
579
|
+
lastWritten: lastWrittenClone
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
// ─── Internals ────────────────────────────────────────────────────────
|
|
583
|
+
computeDefaults() {
|
|
584
|
+
const out = {};
|
|
585
|
+
for (const [key, fieldSpec] of Object.entries(this.spec)) {
|
|
586
|
+
const def = getDefault(fieldSpec);
|
|
587
|
+
if (def !== void 0) out[key] = def;
|
|
588
|
+
}
|
|
589
|
+
return out;
|
|
590
|
+
}
|
|
591
|
+
hydrateFromStorage() {
|
|
592
|
+
let raw;
|
|
593
|
+
try {
|
|
594
|
+
raw = this.storage.read();
|
|
595
|
+
} catch (err) {
|
|
596
|
+
this.storageErrorMap.set("__read__", err instanceof Error ? err.message : String(err));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (!raw) return;
|
|
600
|
+
for (const [key, rawValue] of Object.entries(raw)) {
|
|
601
|
+
const fieldSpec = this.spec[key];
|
|
602
|
+
if (!fieldSpec) continue;
|
|
603
|
+
const result = parseField(fieldSpec, rawValue);
|
|
604
|
+
if (result.ok && result.value !== void 0) {
|
|
605
|
+
;
|
|
606
|
+
this.values[key] = result.value;
|
|
607
|
+
} else if (result.reason) {
|
|
608
|
+
this.storageErrorMap.set(key, result.reason);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
persistToStorage(changed, options) {
|
|
613
|
+
const filtered = {};
|
|
614
|
+
for (const path of changed) {
|
|
615
|
+
const value = this.values[path];
|
|
616
|
+
const config = this.fieldConfigs[path];
|
|
617
|
+
const def = deepGet(this.defaults, path);
|
|
618
|
+
if (config?.omitWhenDefault && deepEqual(value, def)) {
|
|
619
|
+
filtered[path] = void 0;
|
|
620
|
+
} else {
|
|
621
|
+
filtered[path] = value;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
const writeOptions = options?.history !== void 0 ? { history: options.history } : void 0;
|
|
625
|
+
this.lastWritten = { ...this.values };
|
|
626
|
+
try {
|
|
627
|
+
const result = this.storage.write(filtered, changed, writeOptions);
|
|
628
|
+
if (result && typeof result.then === "function") {
|
|
629
|
+
;
|
|
630
|
+
result.catch(() => void 0);
|
|
631
|
+
}
|
|
632
|
+
} catch {
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
onExternalChange(raw) {
|
|
636
|
+
if (this.disposed) return;
|
|
637
|
+
if (this.lastWritten && deepEqual(raw, this.lastWritten)) return;
|
|
638
|
+
const changed = [];
|
|
639
|
+
let next = this.values;
|
|
640
|
+
for (const [key, rawValue] of Object.entries(raw)) {
|
|
641
|
+
const fieldSpec = this.spec[key];
|
|
642
|
+
if (!fieldSpec) continue;
|
|
643
|
+
const result = parseField(fieldSpec, rawValue);
|
|
644
|
+
const newValue = result.ok ? result.value : deepGet(this.defaults, key);
|
|
645
|
+
const oldValue = deepGet(next, key);
|
|
646
|
+
if (deepEqual(oldValue, newValue)) continue;
|
|
647
|
+
next = deepSet(next, key, newValue);
|
|
648
|
+
changed.push(key);
|
|
649
|
+
if (!result.ok && result.reason) {
|
|
650
|
+
this.storageErrorMap.set(key, result.reason);
|
|
651
|
+
} else {
|
|
652
|
+
this.storageErrorMap.delete(key);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (changed.length === 0) return;
|
|
656
|
+
this.values = next;
|
|
657
|
+
this.invalidateToQueryCache();
|
|
658
|
+
for (const path of changed) this.trie.notify(path);
|
|
659
|
+
}
|
|
660
|
+
invalidateToQueryCache() {
|
|
661
|
+
this.toQueryCache = void 0;
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// src/store-cache.ts
|
|
666
|
+
var cache = /* @__PURE__ */ new WeakMap();
|
|
667
|
+
var seenNames = /* @__PURE__ */ new Map();
|
|
668
|
+
function acquire(def) {
|
|
669
|
+
warnOnDuplicateName(def);
|
|
670
|
+
let entry = cache.get(def);
|
|
671
|
+
if (!entry) {
|
|
672
|
+
entry = {
|
|
673
|
+
store: new ParamsStore(def),
|
|
674
|
+
refCount: 0,
|
|
675
|
+
disposalQueued: false
|
|
676
|
+
};
|
|
677
|
+
cache.set(def, entry);
|
|
678
|
+
} else {
|
|
679
|
+
entry.disposalQueued = false;
|
|
680
|
+
}
|
|
681
|
+
entry.refCount++;
|
|
682
|
+
return entry.store;
|
|
683
|
+
}
|
|
684
|
+
function release(def) {
|
|
685
|
+
const entry = cache.get(def);
|
|
686
|
+
if (!entry) return;
|
|
687
|
+
entry.refCount--;
|
|
688
|
+
if (entry.refCount > 0) return;
|
|
689
|
+
entry.disposalQueued = true;
|
|
690
|
+
queueMicrotask(() => {
|
|
691
|
+
if (!entry.disposalQueued) return;
|
|
692
|
+
entry.disposalQueued = false;
|
|
693
|
+
disposeAndClear(def, entry);
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
function disposeAndClear(def, entry) {
|
|
697
|
+
entry.store.dispose();
|
|
698
|
+
cache.delete(def);
|
|
699
|
+
if (def.name !== void 0 && seenNames.get(def.name) === def) {
|
|
700
|
+
seenNames.delete(def.name);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
function warnOnDuplicateName(def) {
|
|
704
|
+
if (def.name === void 0) return;
|
|
705
|
+
const existing = seenNames.get(def.name);
|
|
706
|
+
if (existing !== void 0 && existing !== def) {
|
|
707
|
+
warn(
|
|
708
|
+
`Duplicate definition name '${def.name}' detected. Names must be unique across definitions for v0.2 SSR snapshot keys.`
|
|
709
|
+
);
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
seenNames.set(def.name, def);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// src/react/integrations/forms-reverse/index.ts
|
|
716
|
+
var activeReverseBridges = /* @__PURE__ */ new WeakMap();
|
|
717
|
+
function useFormToParamsSync(form, paramsDef) {
|
|
718
|
+
const lastPushedRef = (0, import_react.useRef)(void 0);
|
|
719
|
+
(0, import_react.useEffect)(() => {
|
|
720
|
+
const formStore = form.store;
|
|
721
|
+
const existing = activeReverseBridges.get(formStore);
|
|
722
|
+
if (existing && existing === paramsDef) {
|
|
723
|
+
throw new Error(
|
|
724
|
+
`useFormToParamsSync: this (form, paramsDef) pair is already bridged. Calling the hook twice for the same pair would cycle.`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
if (existing && existing !== paramsDef) {
|
|
728
|
+
throw new Error(
|
|
729
|
+
"useFormToParamsSync: this form already has an active reverse bridge to a different params definition. Use one bridge per form."
|
|
730
|
+
);
|
|
731
|
+
}
|
|
732
|
+
activeReverseBridges.set(formStore, paramsDef);
|
|
733
|
+
const internalStore = formStore;
|
|
734
|
+
const entries = internalStore.syncEngine?.entries;
|
|
735
|
+
if (Array.isArray(entries)) {
|
|
736
|
+
const forwardName = `paramsToFormSync(${paramsDef.name ?? paramsDef.storage.name})`;
|
|
737
|
+
if (entries.some((e) => e?.adapter?.name === forwardName)) {
|
|
738
|
+
warn(
|
|
739
|
+
`useFormToParamsSync: form already has a paramsToFormSync(${paramsDef.name ?? paramsDef.storage.name}) adapter in its sync array. Pairing both directions on the same (form, paramsDef) creates a cycle.`
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
const paramsStore = acquire(paramsDef);
|
|
744
|
+
const push = () => {
|
|
745
|
+
const values = form.getValues();
|
|
746
|
+
const last = lastPushedRef.current;
|
|
747
|
+
if (last !== void 0 && deepEqualShallow(last, values)) return;
|
|
748
|
+
lastPushedRef.current = values;
|
|
749
|
+
paramsStore.set(values);
|
|
750
|
+
};
|
|
751
|
+
push();
|
|
752
|
+
const unsubscribe = formStore.subscribe("", push);
|
|
753
|
+
return () => {
|
|
754
|
+
unsubscribe();
|
|
755
|
+
release(paramsDef);
|
|
756
|
+
activeReverseBridges.delete(formStore);
|
|
757
|
+
};
|
|
758
|
+
}, [form, paramsDef]);
|
|
759
|
+
}
|
|
760
|
+
function deepEqualShallow(a, b) {
|
|
761
|
+
if (Object.is(a, b)) return true;
|
|
762
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
763
|
+
const aKeys = Object.keys(a);
|
|
764
|
+
const bKeys = Object.keys(b);
|
|
765
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
766
|
+
for (const k of aKeys) {
|
|
767
|
+
if (!Object.is(a[k], b[k])) {
|
|
768
|
+
return false;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
return true;
|
|
772
|
+
}
|
|
773
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
774
|
+
0 && (module.exports = {
|
|
775
|
+
useFormToParamsSync
|
|
776
|
+
});
|
|
777
|
+
//# sourceMappingURL=forms-reverse.cjs.map
|