@victorylabs/params 0.2.0 → 0.4.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/dist/{chunk-S276UVQK.js → chunk-DSAHBEAQ.js} +43 -17
- package/dist/chunk-DSAHBEAQ.js.map +1 -0
- package/dist/devtools.d.cts +1 -1
- package/dist/devtools.d.ts +1 -1
- package/dist/index.cjs +42 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/integrations/forms-reverse.cjs +42 -16
- package/dist/integrations/forms-reverse.cjs.map +1 -1
- package/dist/integrations/forms-reverse.js +1 -1
- package/dist/integrations/forms.cjs +42 -16
- package/dist/integrations/forms.cjs.map +1 -1
- package/dist/integrations/forms.js +1 -1
- package/dist/{params-store-Cgbtn53j.d.cts → params-store-4Lcb1M_X.d.cts} +29 -1
- package/dist/{params-store-CguA9-yr.d.ts → params-store-f3pmPdw3.d.ts} +29 -1
- package/dist/react.cjs +98 -53
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +42 -4
- package/dist/react.d.ts +42 -4
- package/dist/react.js +37 -19
- package/dist/react.js.map +1 -1
- package/dist/snapshot.cjs.map +1 -1
- package/dist/snapshot.d.cts +2 -2
- package/dist/snapshot.d.ts +2 -2
- package/dist/snapshot.js.map +1 -1
- package/dist/storage/cookie.cjs +2 -2
- package/dist/storage/cookie.cjs.map +1 -1
- package/dist/storage/cookie.js +2 -2
- package/dist/storage/cookie.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-S276UVQK.js.map +0 -1
|
@@ -195,17 +195,43 @@ var ParamsStore = class {
|
|
|
195
195
|
getFieldConfig(path) {
|
|
196
196
|
return this.fieldConfigs[path];
|
|
197
197
|
}
|
|
198
|
-
|
|
198
|
+
// ─── Writes ───────────────────────────────────────────────────────────
|
|
199
|
+
/**
|
|
200
|
+
* Apply a partial update — write multiple fields at once.
|
|
201
|
+
*
|
|
202
|
+
* `set(partial)` is the canonical write form. The path-and-value form
|
|
203
|
+
* (`set(path, value)` historically) is now `setField(path, value)` —
|
|
204
|
+
* see {@link setField}. The split keeps `set`'s single-signature
|
|
205
|
+
* inference clean for the common `set({ [key]: value })` pattern, which
|
|
206
|
+
* previously required `as Partial<T>` casts because TypeScript couldn't
|
|
207
|
+
* resolve the overload from a generic-keyed object literal.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```ts
|
|
211
|
+
* p.set({ page: 1, sort: 'updated' })
|
|
212
|
+
* p.set({ [key]: value }) // works without a cast now
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
set(partial, options) {
|
|
199
216
|
if (this.disposed) return;
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
217
|
+
this.applyUpdates(partial, options);
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Write a single field by path. Equivalent to `set({ [path]: value })`
|
|
221
|
+
* but skips the object-literal allocation and avoids the inference issue
|
|
222
|
+
* that motivated splitting the API.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* p.setField('query', 'react')
|
|
227
|
+
* p.setField('filters.tags', ['ts', 'react'])
|
|
228
|
+
* ```
|
|
229
|
+
*/
|
|
230
|
+
setField(path, value, options) {
|
|
231
|
+
if (this.disposed) return;
|
|
232
|
+
this.applyUpdates({ [path]: value }, options);
|
|
233
|
+
}
|
|
234
|
+
applyUpdates(updates, options) {
|
|
209
235
|
const initialChanges = {};
|
|
210
236
|
for (const [path, v] of Object.entries(updates)) {
|
|
211
237
|
const old = deepGet(this.values, path);
|
|
@@ -237,13 +263,13 @@ var ParamsStore = class {
|
|
|
237
263
|
/** Boolean-flip helper. */
|
|
238
264
|
toggle(path, options) {
|
|
239
265
|
const current = this.getValue(path);
|
|
240
|
-
this.
|
|
266
|
+
this.setField(path, !current, options);
|
|
241
267
|
}
|
|
242
268
|
/** Push a value onto an array field. */
|
|
243
269
|
append(path, value, options) {
|
|
244
270
|
const current = this.getValue(path);
|
|
245
271
|
if (!Array.isArray(current)) return;
|
|
246
|
-
this.
|
|
272
|
+
this.setField(path, [...current, value], options);
|
|
247
273
|
}
|
|
248
274
|
/** Remove the first array element matching `value` by deepEqual. */
|
|
249
275
|
remove(path, value, options) {
|
|
@@ -251,14 +277,14 @@ var ParamsStore = class {
|
|
|
251
277
|
if (!Array.isArray(current)) return;
|
|
252
278
|
const idx = current.findIndex((item) => deepEqual(item, value));
|
|
253
279
|
if (idx === -1) return;
|
|
254
|
-
this.
|
|
280
|
+
this.setField(path, [...current.slice(0, idx), ...current.slice(idx + 1)], options);
|
|
255
281
|
}
|
|
256
282
|
/** Remove the array element at the given index. */
|
|
257
283
|
removeAt(path, index, options) {
|
|
258
284
|
const current = this.getValue(path);
|
|
259
285
|
if (!Array.isArray(current)) return;
|
|
260
286
|
if (index < 0 || index >= current.length) return;
|
|
261
|
-
this.
|
|
287
|
+
this.setField(path, [...current.slice(0, index), ...current.slice(index + 1)], options);
|
|
262
288
|
}
|
|
263
289
|
cycle(path, valuesOrOptions, maybeOptions) {
|
|
264
290
|
let resolved;
|
|
@@ -284,12 +310,12 @@ var ParamsStore = class {
|
|
|
284
310
|
const current = this.getValue(path);
|
|
285
311
|
const idx = resolved.findIndex((o) => deepEqual(o, current));
|
|
286
312
|
const next = idx === -1 ? resolved[0] : resolved[(idx + 1) % resolved.length];
|
|
287
|
-
this.
|
|
313
|
+
this.setField(path, next, setOpts);
|
|
288
314
|
}
|
|
289
315
|
/** Reset a single field to its default. */
|
|
290
316
|
clear(path, options) {
|
|
291
317
|
const def = deepGet(this.defaults, path);
|
|
292
|
-
this.
|
|
318
|
+
this.setField(path, def, options);
|
|
293
319
|
}
|
|
294
320
|
/** Reset all fields to defaults; optional partial overrides + SetOptions. */
|
|
295
321
|
reset(values, options) {
|
|
@@ -570,4 +596,4 @@ export {
|
|
|
570
596
|
getCachedStore,
|
|
571
597
|
releaseCachedStore
|
|
572
598
|
};
|
|
573
|
-
//# sourceMappingURL=chunk-
|
|
599
|
+
//# sourceMappingURL=chunk-DSAHBEAQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../utils/src/cascade.ts","../../utils/src/path-trie.ts","../src/params-store.ts","../src/store-cache.ts"],"sourcesContent":["/**\n * Cascade resolver for `dependsOn` / `excludeDeps` / `onDepChange` field configs.\n * Used by both `@victorylabs/forms` and `@victorylabs/params` v0.2.\n *\n * Pure function — no side effects, no I/O. Consumers wire the result into their\n * own commit phase (params: PathTrie notify; forms: notify + revalidate + dirty/error reset).\n *\n * Algorithm: BFS over the dependency graph.\n * - Each round computes the next layer of cascaded changes.\n * - Visited paths are tracked to detect cycles (warn + skip re-entry).\n * - Depth cap stops runaway chains at 10 levels.\n *\n * The `'*'` wildcard for `dependsOn` is resolved at cascade time (not config\n * time), so adding fields to a definition doesn't invalidate `'*'` consumers.\n */\n\nconst DEPTH_CAP = 10\n\nexport interface CascadeFieldConfig {\n /**\n * Paths whose changes trigger this field's `onDepChange`. `'*'` means\n * \"every other field\"; combine with `excludeDeps` to carve out exceptions.\n */\n dependsOn?: string[] | '*'\n\n /**\n * Paths to exclude from the `'*'` wildcard. Only meaningful when\n * `dependsOn === '*'`. Implicitly always includes the field itself\n * (a field never depends on itself).\n */\n excludeDeps?: string[]\n\n /**\n * What to do when a dep changes. `'reset'` restores the field's default;\n * `'clear'` sets it to `undefined`; a function returns the new value.\n * The function form receives ALL dep values (changed + unchanged) keyed\n * by path, plus the field's own current value.\n */\n onDepChange?: 'reset' | 'clear' | ((deps: Record<string, unknown>, current: unknown) => unknown)\n}\n\nexport interface CascadeContext {\n /** Per-field config keyed by path. Fields without cascade config are inert. */\n fieldConfigs: Record<string, CascadeFieldConfig>\n\n /** Default values keyed by path — resolves `onDepChange === 'reset'`. */\n defaults: Record<string, unknown>\n\n /** Current values keyed by path — resolves the function-form `onDepChange`. */\n currentValues: Record<string, unknown>\n\n /** Every path in the definition — resolves `'*'` wildcard. */\n allPaths: string[]\n}\n\nexport interface CascadeResult {\n /** Initial changes plus all cascaded changes, keyed by path. */\n changes: Record<string, unknown>\n\n /** Cycle-detection / depth-cap warnings (one entry per anomaly). */\n warnings: string[]\n}\n\n/**\n * Resolve a cascade. Given a set of initial changes, compute every dependent\n * field that should also change.\n */\nexport function resolveCascade(\n initialChanges: Record<string, unknown>,\n ctx: CascadeContext,\n): CascadeResult {\n const changes: Record<string, unknown> = { ...initialChanges }\n const warnings: string[] = []\n\n // Working values during BFS — start from current, override with initial changes.\n // Each round's cascaded changes accumulate here so dependent fields see them.\n const working: Record<string, unknown> = { ...ctx.currentValues, ...initialChanges }\n\n // Seed: paths changed in this round become the next round's triggers.\n let frontier = new Set(Object.keys(initialChanges))\n // Cycle guard: if a path is re-targeted within the same cascade, warn + skip.\n const visited = new Set<string>(frontier)\n\n let depth = 0\n while (frontier.size > 0) {\n if (depth >= DEPTH_CAP) {\n warnings.push(\n `cascade depth cap (${DEPTH_CAP}) reached; stopping. Frontier: [${[...frontier].join(', ')}]. Likely a config error — check for accidental long chains.`,\n )\n break\n }\n\n const nextFrontier = new Set<string>()\n\n for (const targetPath of ctx.allPaths) {\n const config = ctx.fieldConfigs[targetPath]\n if (!config?.onDepChange) continue\n if (frontier.has(targetPath)) continue // a path doesn't cascade to itself\n\n const deps = resolveDeps(config, ctx.allPaths, targetPath)\n // Did any of this field's deps change in the current frontier?\n const changedDeps = deps.filter((d) => frontier.has(d))\n if (changedDeps.length === 0) continue\n\n if (visited.has(targetPath)) {\n warnings.push(\n `cycle detected: '${targetPath}' would cascade twice (triggered by [${changedDeps.join(', ')}]). Skipping re-entry.`,\n )\n continue\n }\n\n // Compute the new value via onDepChange.\n const newValue = applyDepChange(config.onDepChange, deps, working, ctx.defaults, targetPath)\n\n // Skip no-op cascades: if the resolved value equals the field's current\n // working value, don't propagate further (avoids spurious re-renders).\n if (Object.is(newValue, working[targetPath])) continue\n\n changes[targetPath] = newValue\n working[targetPath] = newValue\n visited.add(targetPath)\n nextFrontier.add(targetPath)\n }\n\n frontier = nextFrontier\n depth++\n }\n\n return { changes, warnings }\n}\n\n/**\n * Resolve a field's effective dependency list. `'*'` expands to every OTHER\n * path minus `excludeDeps` and the field itself.\n */\nfunction resolveDeps(config: CascadeFieldConfig, allPaths: string[], selfPath: string): string[] {\n if (config.dependsOn === '*') {\n const exclude = new Set(config.excludeDeps ?? [])\n exclude.add(selfPath) // a field doesn't depend on itself\n return allPaths.filter((p) => !exclude.has(p))\n }\n if (Array.isArray(config.dependsOn)) {\n // Filter out self-references defensively (config error otherwise).\n return config.dependsOn.filter((p) => p !== selfPath)\n }\n return []\n}\n\n/**\n * Compute the new value for a cascaded field via its `onDepChange` config.\n */\nfunction applyDepChange(\n onDepChange: NonNullable<CascadeFieldConfig['onDepChange']>,\n deps: string[],\n working: Record<string, unknown>,\n defaults: Record<string, unknown>,\n targetPath: string,\n): unknown {\n if (onDepChange === 'reset') return defaults[targetPath]\n if (onDepChange === 'clear') return undefined\n\n // Function form: build the deps record from the working values\n // (so cascaded changes within the same set() call are visible).\n const depsRecord: Record<string, unknown> = {}\n for (const d of deps) {\n depsRecord[d] = working[d]\n }\n return onDepChange(depsRecord, working[targetPath])\n}\n","import { splitPath } from './deep'\n\ninterface Node {\n listeners: Set<() => void>\n children: Map<string, Node>\n}\n\nconst makeNode = (): Node => ({ listeners: new Set(), children: new Map() })\n\n/**\n * Path-scoped subscription bus.\n *\n * Notification semantics for `notify(path)`:\n * - Subscribers AT `path` fire (exact match).\n * - Subscribers at every ANCESTOR of `path` fire (a parent observing the\n * aggregated subtree should re-render when any descendant changes).\n * - Subscribers at every DESCENDANT of `path` fire (a subtree replacement\n * at `path` invalidates any leaf below it).\n * - Sibling-branch subscribers do NOT fire.\n *\n * notify('') notifies the root + every subscriber in the trie.\n */\nexport class PathTrie {\n private readonly root: Node = makeNode()\n\n subscribe(path: string, listener: () => void): () => void {\n const node = this.ensure(path)\n node.listeners.add(listener)\n return () => {\n node.listeners.delete(listener)\n }\n }\n\n notify(path: string): void {\n const segments = splitPath(path)\n\n let current: Node = this.root\n fire(current)\n\n for (const seg of segments) {\n const next = current.children.get(seg)\n if (!next) return\n current = next\n fire(current)\n }\n\n fireSubtree(current)\n }\n\n /** Test/diagnostic helper — total subscriber count below a given path. */\n size(path = ''): number {\n const node = this.find(path)\n if (!node) return 0\n return countSubtree(node)\n }\n\n private ensure(path: string): Node {\n let node = this.root\n for (const seg of splitPath(path)) {\n let child = node.children.get(seg)\n if (!child) {\n child = makeNode()\n node.children.set(seg, child)\n }\n node = child\n }\n return node\n }\n\n private find(path: string): Node | undefined {\n let node: Node | undefined = this.root\n for (const seg of splitPath(path)) {\n node = node?.children.get(seg)\n if (!node) return undefined\n }\n return node\n }\n}\n\nfunction fire(node: Node): void {\n for (const listener of node.listeners) listener()\n}\n\nfunction fireSubtree(node: Node): void {\n for (const child of node.children.values()) {\n fire(child)\n fireSubtree(child)\n }\n}\n\nfunction countSubtree(node: Node): number {\n let total = node.listeners.size\n for (const child of node.children.values()) {\n total += countSubtree(child)\n }\n return total\n}\n","import { deepEqual, deepGet, deepSet, PathTrie, resolveCascade } from '@victorylabs/utils'\n\nimport { warn } from './dev'\nimport { takePreHydrationValues } from './name-registry'\nimport {\n defaultSerialize,\n extractEnumValues,\n getDefault,\n isPlainSpec,\n isStandardSchema,\n parseField,\n} from './schema'\nimport type { ParamsStorage, WriteOptions } from './storage'\nimport type { FieldConfig, FieldSpec, ParamsDefinition } from './types'\n\n/**\n * Per-call write options accepted by `set()` and `clear()`. Currently shapes\n * the URL `history` strategy override; backends ignore fields they don't\n * recognize.\n */\nexport interface SetOptions {\n /** History API override for URL-like backends. `'push'` beats `'replace'` across batched writes. */\n readonly history?: 'push' | 'replace'\n}\n\n/**\n * Frozen, defensive-copied snapshot returned by `ParamsStore.__introspect()`.\n *\n * Surface is **unstable** — fields may be added freely across releases; do\n * not depend on the type identity. Use for devtools panels, debug logging,\n * or test assertions that don't fit through the public observable API.\n */\nexport interface ParamsStoreIntrospection<T = Record<string, unknown>> {\n readonly values: Readonly<Partial<T>>\n readonly defaults: Readonly<Partial<T>>\n readonly fieldsConfigured: Readonly<Record<string, FieldConfig>>\n readonly fieldsBySpecType: Readonly<Record<string, 'zod' | 'standard-schema' | 'plain'>>\n readonly storageErrors: Readonly<Record<string, string>>\n readonly storage: {\n readonly name: string\n readonly clientOnly: boolean\n readonly hasReadAsync: boolean\n readonly hasSubscribe: boolean\n readonly hasClear: boolean\n }\n readonly lastWritten: Readonly<Partial<T>> | undefined\n}\n\nconst isClient = typeof window !== 'undefined'\n\n/**\n * Framework-agnostic store. Owns:\n * - values + storage round-tripping\n * - schema validation on read (silent fallback to defaults)\n * - PathTrie-based subscriptions\n * - all state-mutation helpers (set, toggle, append, …)\n * - loop prevention against external storage echoes\n * - memoized toQuery\n *\n * The React adapter (`@victorylabs/params/react`) wraps this with\n * useSyncExternalStore-style hooks; non-React consumers use the methods\n * directly via `getParamsStore(def)`.\n */\nexport class ParamsStore<T = Record<string, unknown>> {\n private readonly spec: Readonly<Record<string, FieldSpec>>\n private readonly storage: ParamsStorage<T>\n private readonly fieldConfigs: Readonly<Record<string, FieldConfig>>\n\n private values: Partial<T>\n private readonly defaults: Partial<T>\n private readonly trie = new PathTrie()\n private readonly storageErrorMap = new Map<string, string>()\n\n private lastWritten: Partial<T> | undefined\n private storageUnsubscribe: (() => void) | undefined\n private toQueryCache: { values: Partial<T>; query: string; href?: string } | undefined\n private disposed = false\n\n constructor(def: ParamsDefinition<T>) {\n this.spec = def.spec\n this.storage = def.storage\n this.fieldConfigs = def.fields\n\n this.defaults = this.computeDefaults()\n this.values = { ...this.defaults }\n\n // SSR pre-hydration: if hydrateParams() seeded values for this def's name,\n // those win over the storage backend's read() — the snapshot represents\n // the authoritative server-rendered state.\n const seeded = takePreHydrationValues<T>(def.name)\n if (seeded !== undefined) {\n this.values = { ...this.defaults, ...seeded }\n } else if (this.storage.clientOnly && !isClient) {\n // Server: skip read; values stay at defaults\n } else {\n this.hydrateFromStorage()\n }\n\n if (this.storage.subscribe) {\n this.storageUnsubscribe = this.storage.subscribe((raw) => this.onExternalChange(raw))\n }\n }\n\n // ─── Reads (synchronous, framework-agnostic) ──────────────────────────\n\n getValues(): Readonly<Partial<T>> {\n return this.values\n }\n\n getValue<P extends string>(path: P): unknown {\n return deepGet(this.values, path)\n }\n\n /** Storage parse failures discovered on hydrate or external change. */\n get storageErrors(): Readonly<Record<string, string>> {\n const out: Record<string, string> = {}\n for (const [path, reason] of this.storageErrorMap) out[path] = reason\n return out\n }\n\n /** Per-field config (for the React adapter to read debounce settings, etc.). */\n getFieldConfig(path: string): FieldConfig | undefined {\n return this.fieldConfigs[path]\n }\n\n // ─── Writes ───────────────────────────────────────────────────────────\n\n /**\n * Apply a partial update — write multiple fields at once.\n *\n * `set(partial)` is the canonical write form. The path-and-value form\n * (`set(path, value)` historically) is now `setField(path, value)` —\n * see {@link setField}. The split keeps `set`'s single-signature\n * inference clean for the common `set({ [key]: value })` pattern, which\n * previously required `as Partial<T>` casts because TypeScript couldn't\n * resolve the overload from a generic-keyed object literal.\n *\n * @example\n * ```ts\n * p.set({ page: 1, sort: 'updated' })\n * p.set({ [key]: value }) // works without a cast now\n * ```\n */\n set(partial: Partial<T>, options?: SetOptions): void {\n if (this.disposed) return\n this.applyUpdates(partial as Record<string, unknown>, options)\n }\n\n /**\n * Write a single field by path. Equivalent to `set({ [path]: value })`\n * but skips the object-literal allocation and avoids the inference issue\n * that motivated splitting the API.\n *\n * @example\n * ```ts\n * p.setField('query', 'react')\n * p.setField('filters.tags', ['ts', 'react'])\n * ```\n */\n setField(path: string, value: unknown, options?: SetOptions): void {\n if (this.disposed) return\n this.applyUpdates({ [path]: value }, options)\n }\n\n private applyUpdates(updates: Record<string, unknown>, options?: SetOptions): void {\n // First pass: filter out no-op updates (value already deep-equals current).\n const initialChanges: Record<string, unknown> = {}\n for (const [path, v] of Object.entries(updates)) {\n const old = deepGet(this.values, path)\n if (deepEqual(old, v)) continue\n initialChanges[path] = v\n }\n if (Object.keys(initialChanges).length === 0) return\n\n // Resolve cascading dependencies (no-op when no field has dependsOn).\n const cascade = resolveCascade(initialChanges, {\n fieldConfigs: this.fieldConfigs,\n defaults: this.defaults as Record<string, unknown>,\n currentValues: this.values as Record<string, unknown>,\n allPaths: Object.keys(this.spec),\n })\n\n for (const w of cascade.warnings) warn(w)\n\n // Second pass: apply initial + cascaded changes atomically.\n const changed: string[] = []\n let next = this.values\n for (const [path, v] of Object.entries(cascade.changes)) {\n const old = deepGet(next, path)\n if (deepEqual(old, v)) continue\n next = deepSet(next, path, v) as Partial<T>\n changed.push(path)\n }\n if (changed.length === 0) return\n\n this.values = next\n this.invalidateToQueryCache()\n\n // Notify subscribers (React 18 batches these into one render per consumer)\n for (const path of changed) this.trie.notify(path)\n\n // Persist to storage with omitWhenDefault filtering. Per-call options\n // (history strategy) flow straight through to storage.write.\n this.persistToStorage(changed, options)\n }\n\n /** Boolean-flip helper. */\n toggle(path: string, options?: SetOptions): void {\n const current = this.getValue(path)\n this.setField(path, !current, options)\n }\n\n /** Push a value onto an array field. */\n append(path: string, value: unknown, options?: SetOptions): void {\n const current = this.getValue(path)\n if (!Array.isArray(current)) return\n this.setField(path, [...current, value], options)\n }\n\n /** Remove the first array element matching `value` by deepEqual. */\n remove(path: string, value: unknown, options?: SetOptions): void {\n const current = this.getValue(path)\n if (!Array.isArray(current)) return\n const idx = current.findIndex((item) => deepEqual(item, value))\n if (idx === -1) return\n this.setField(path, [...current.slice(0, idx), ...current.slice(idx + 1)], options)\n }\n\n /** Remove the array element at the given index. */\n removeAt(path: string, index: number, options?: SetOptions): void {\n const current = this.getValue(path)\n if (!Array.isArray(current)) return\n if (index < 0 || index >= current.length) return\n this.setField(path, [...current.slice(0, index), ...current.slice(index + 1)], options)\n }\n\n /**\n * Cycle the value through the given values (e.g., 'asc' → 'desc' → 'asc').\n * If the current value isn't in the values, jumps to the first one.\n *\n * v0.4: when called without an explicit values array, derives the rotation\n * from the field's Zod `z.enum` / `z.nativeEnum` schema. Throws if the schema\n * doesn't expose enum metadata.\n *\n * v0.5: optional `SetOptions` arg (history strategy) on every overload.\n */\n cycle(path: string, options?: SetOptions): void\n cycle(path: string, values: ReadonlyArray<unknown>, options?: SetOptions): void\n cycle(\n path: string,\n valuesOrOptions?: ReadonlyArray<unknown> | SetOptions,\n maybeOptions?: SetOptions,\n ): void {\n // Disambiguate the second argument: a ReadonlyArray means explicit values;\n // a non-array object means SetOptions; undefined means \"derive from schema\".\n let resolved: ReadonlyArray<unknown> | undefined\n let setOpts: SetOptions | undefined\n if (Array.isArray(valuesOrOptions)) {\n resolved = valuesOrOptions\n setOpts = maybeOptions\n } else {\n resolved = undefined\n setOpts = valuesOrOptions as SetOptions | undefined\n }\n\n if (resolved === undefined) {\n const fieldSpec = this.spec[path]\n const derived = fieldSpec !== undefined ? extractEnumValues(fieldSpec) : undefined\n if (!derived || derived.length === 0) {\n throw new Error(\n `Cannot derive enum values for params field '${path}'; pass values explicitly to cycle().`,\n )\n }\n resolved = derived\n }\n if (resolved.length === 0) return\n const current = this.getValue(path)\n const idx = resolved.findIndex((o) => deepEqual(o, current))\n const next = idx === -1 ? resolved[0] : resolved[(idx + 1) % resolved.length]\n this.setField(path, next, setOpts)\n }\n\n /** Reset a single field to its default. */\n clear(path: string, options?: SetOptions): void {\n const def = deepGet(this.defaults, path)\n this.setField(path, def, options)\n }\n\n /** Reset all fields to defaults; optional partial overrides + SetOptions. */\n reset(values?: Partial<T>, options?: SetOptions): void {\n if (this.disposed) return\n const next = values ? { ...this.defaults, ...values } : { ...this.defaults }\n const changed = Object.keys({ ...this.values, ...next })\n this.values = next\n this.invalidateToQueryCache()\n for (const path of changed) this.trie.notify(path)\n const writeOptions: WriteOptions | undefined =\n options?.history !== undefined ? { history: options.history } : undefined\n void this.storage.clear?.([], writeOptions)\n this.lastWritten = undefined\n }\n\n // ─── Subscriptions ────────────────────────────────────────────────────\n\n subscribe(path: string, listener: () => void): () => void {\n return this.trie.subscribe(path, listener)\n }\n\n // ─── URL helpers ──────────────────────────────────────────────────────\n\n toQuery(overrides?: Partial<T>): string {\n const effective = overrides\n ? { ...(this.values as Record<string, unknown>), ...(overrides as Record<string, unknown>) }\n : (this.values as Record<string, unknown>)\n\n if (!overrides && this.toQueryCache && this.toQueryCache.values === this.values) {\n return this.toQueryCache.query\n }\n\n const params = new URLSearchParams()\n const keys = Object.keys(effective).sort()\n for (const key of keys) {\n const value = effective[key]\n if (value === undefined || value === null) continue\n const config = this.fieldConfigs[key]\n const def = deepGet(this.defaults, key)\n if (config?.omitWhenDefault && deepEqual(value, def)) continue\n params.set(key, defaultSerialize(value))\n }\n\n const str = params.toString()\n const out = str ? `?${str}` : ''\n if (!overrides) this.toQueryCache = { values: this.values, query: out }\n return out\n }\n\n href(overrides?: Partial<T>): string {\n // Memoize the no-overrides case alongside toQuery's cache. Pathname is\n // read live (could change via routing within a page), so the cache key\n // includes (values, pathname). With overrides, no caching — fresh string.\n if (!overrides) {\n const query = this.toQuery()\n // toQuery() refreshed the cache for `this.values`; safe to extend it.\n const pathname = isClient ? window.location.pathname : ''\n const cached = this.toQueryCache\n if (cached && cached.values === this.values && cached.href !== undefined) {\n // Confirm pathname hasn't changed — if it has, recompute.\n const expected = pathname + query\n if (cached.href === expected) return cached.href\n }\n const out = pathname + query\n if (cached && cached.values === this.values) cached.href = out\n return out\n }\n const pathname = isClient ? window.location.pathname : ''\n return pathname + this.toQuery(overrides)\n }\n\n // ─── Disposal ─────────────────────────────────────────────────────────\n\n dispose(): void {\n if (this.disposed) return\n this.disposed = true\n this.storageUnsubscribe?.()\n this.storageUnsubscribe = undefined\n }\n\n // ─── Devtools / debugging escape hatch (v0.5) ─────────────────────────\n\n /**\n * Devtools / debugging escape hatch. Returns a frozen, defensive-copied\n * snapshot of all internal state. Pure read — no side effects, no\n * subscriptions registered.\n *\n * Uses `structuredClone` for the values + defaults deep-copy. Because\n * params values round-trip through storage backends, they're already\n * restricted to JSON-safe shapes — so `__introspect()` is safer here than\n * on `FormStore`. Still: marked unstable via the `__` prefix; field set\n * may grow over releases.\n */\n __introspect(): ParamsStoreIntrospection<T> {\n const specTypes: Record<string, 'zod' | 'standard-schema' | 'plain'> = {}\n for (const [path, spec] of Object.entries(this.spec)) {\n if (isPlainSpec(spec)) {\n specTypes[path] = 'plain'\n continue\n }\n // biome-ignore lint/suspicious/noExplicitAny: Zod internal `_def` API\n if ((spec as any)?._def) {\n specTypes[path] = 'zod'\n continue\n }\n if (isStandardSchema(spec)) {\n specTypes[path] = 'standard-schema'\n continue\n }\n specTypes[path] = 'plain'\n }\n\n let valuesClone: Partial<T>\n let defaultsClone: Partial<T>\n let lastWrittenClone: Partial<T> | undefined\n try {\n valuesClone = structuredClone(this.values)\n defaultsClone = structuredClone(this.defaults)\n lastWrittenClone = this.lastWritten ? structuredClone(this.lastWritten) : undefined\n } catch (err) {\n throw new Error(\n 'ParamsStore.__introspect() failed: values contain non-cloneable data ' +\n '(functions, Symbols, DOM nodes are not allowed in params state). ' +\n `Original error: ${err instanceof Error ? err.message : String(err)}`,\n )\n }\n\n return Object.freeze({\n values: valuesClone,\n defaults: defaultsClone,\n fieldsConfigured: Object.freeze({ ...this.fieldConfigs }),\n fieldsBySpecType: Object.freeze(specTypes),\n storageErrors: Object.freeze({ ...this.storageErrors }),\n storage: Object.freeze({\n name: this.storage.name,\n clientOnly: this.storage.clientOnly === true,\n hasReadAsync: this.storage.readAsync !== undefined,\n hasSubscribe: this.storage.subscribe !== undefined,\n hasClear: this.storage.clear !== undefined,\n }),\n lastWritten: lastWrittenClone,\n })\n }\n\n // ─── Internals ────────────────────────────────────────────────────────\n\n private computeDefaults(): Partial<T> {\n const out: Record<string, unknown> = {}\n for (const [key, fieldSpec] of Object.entries(this.spec)) {\n const def = getDefault(fieldSpec)\n if (def !== undefined) out[key] = def\n }\n return out as Partial<T>\n }\n\n private hydrateFromStorage(): void {\n let raw: Partial<T> | undefined\n try {\n raw = this.storage.read()\n } catch (err) {\n // Read failure → keep defaults, record reason\n this.storageErrorMap.set('__read__', err instanceof Error ? err.message : String(err))\n return\n }\n if (!raw) return\n\n for (const [key, rawValue] of Object.entries(raw)) {\n const fieldSpec = this.spec[key]\n if (!fieldSpec) continue // unknown field — ignore\n const result = parseField(fieldSpec, rawValue)\n if (result.ok && result.value !== undefined) {\n ;(this.values as Record<string, unknown>)[key] = result.value\n } else if (result.reason) {\n this.storageErrorMap.set(key, result.reason)\n }\n }\n }\n\n private persistToStorage(changed: string[], options?: SetOptions): void {\n const filtered: Record<string, unknown> = {}\n for (const path of changed) {\n const value = (this.values as Record<string, unknown>)[path]\n const config = this.fieldConfigs[path]\n const def = deepGet(this.defaults, path)\n if (config?.omitWhenDefault && deepEqual(value, def)) {\n // Mark for clear instead of write\n filtered[path] = undefined\n } else {\n filtered[path] = value\n }\n }\n\n // Forward per-call options (history strategy) to the backend. Backends\n // ignore fields they don't recognize.\n const writeOptions: WriteOptions | undefined =\n options?.history !== undefined ? { history: options.history } : undefined\n\n this.lastWritten = { ...this.values }\n try {\n // Storage write is `void | Promise<void>`. Catch sync throws here; for\n // async rejections, attach a `.catch` so the promise doesn't surface\n // as an unhandled rejection. Either way: silent fallback to in-memory\n // state — storage drift accepted (the standard storage error contract).\n const result = this.storage.write(filtered as Partial<T>, changed, writeOptions)\n if (result && typeof (result as Promise<void>).then === 'function') {\n ;(result as Promise<void>).catch(() => undefined)\n }\n } catch {\n // Sync write failure — same silent contract.\n }\n }\n\n private onExternalChange(raw: Partial<T>): void {\n if (this.disposed) return\n // Loop prevention: drop echoes that match what we just wrote\n if (this.lastWritten && deepEqual(raw, this.lastWritten)) return\n\n const changed: string[] = []\n let next = this.values\n for (const [key, rawValue] of Object.entries(raw)) {\n const fieldSpec = this.spec[key]\n if (!fieldSpec) continue\n const result = parseField(fieldSpec, rawValue)\n const newValue = result.ok ? result.value : deepGet(this.defaults, key)\n const oldValue = deepGet(next, key)\n if (deepEqual(oldValue, newValue)) continue\n next = deepSet(next, key, newValue) as Partial<T>\n changed.push(key)\n if (!result.ok && result.reason) {\n this.storageErrorMap.set(key, result.reason)\n } else {\n this.storageErrorMap.delete(key)\n }\n }\n if (changed.length === 0) return\n this.values = next\n this.invalidateToQueryCache()\n for (const path of changed) this.trie.notify(path)\n }\n\n private invalidateToQueryCache(): void {\n this.toQueryCache = undefined\n }\n}\n","import { warn } from './dev'\nimport { ParamsStore } from './params-store'\nimport type { ParamsDefinition } from './types'\n\ninterface CacheEntry<T> {\n store: ParamsStore<T>\n refCount: number\n pendingDispose?: ReturnType<typeof queueMicrotask> | null\n // Microtask scheduled disposal flag — `true` = a release-triggered disposal\n // is queued; `acquire()` cancels it by clearing this and incrementing\n // refCount.\n disposalQueued: boolean\n}\n\nconst cache = new WeakMap<ParamsDefinition, CacheEntry<unknown>>()\nconst seenNames = new Map<string, ParamsDefinition>()\n\n/**\n * Acquire (or reuse) the cached store for a definition. Increments the\n * reference count; the store is disposed only when the last subscriber\n * releases it (and the microtask-deferred disposal isn't preempted by\n * another acquire — Strict Mode safety).\n */\nexport function acquire<T>(def: ParamsDefinition<T>): ParamsStore<T> {\n warnOnDuplicateName(def)\n let entry = cache.get(def) as CacheEntry<T> | undefined\n if (!entry) {\n entry = {\n store: new ParamsStore<T>(def),\n refCount: 0,\n disposalQueued: false,\n }\n cache.set(def, entry as CacheEntry<unknown>)\n } else {\n // Cancel any pending disposal — Strict Mode and rapid mount/unmount/remount\n entry.disposalQueued = false\n }\n entry.refCount++\n return entry.store\n}\n\n/**\n * Release a previously-acquired store. When the last subscriber releases,\n * disposal is **scheduled in a microtask** — if a fresh `acquire()` arrives\n * before the microtask runs (Strict Mode dev double-invoke), the disposal\n * is cancelled and the store survives.\n */\nexport function release<T>(def: ParamsDefinition<T>): void {\n const entry = cache.get(def) as CacheEntry<T> | undefined\n if (!entry) return\n entry.refCount--\n if (entry.refCount > 0) return\n\n entry.disposalQueued = true\n queueMicrotask(() => {\n if (!entry.disposalQueued) return // cancelled by acquire()\n entry.disposalQueued = false\n disposeAndClear(def, entry)\n })\n}\n\nfunction disposeAndClear<T>(def: ParamsDefinition<T>, entry: CacheEntry<T>): void {\n entry.store.dispose()\n cache.delete(def)\n if (def.name !== undefined && seenNames.get(def.name) === def) {\n seenNames.delete(def.name)\n }\n}\n\n/**\n * Public, no-React imperative read of the cached store. The store lives\n * for the program lifetime once acquired (no ref counting). Use\n * `releaseParamsStore(def)` for explicit cleanup in long-running non-React\n * contexts (daemons, electron apps, server processes).\n *\n * Not safe for shared-process SSR contexts — see plan §SSR.\n */\nexport function getCachedStore<T>(def: ParamsDefinition<T>): ParamsStore<T> {\n let entry = cache.get(def) as CacheEntry<T> | undefined\n if (!entry) {\n entry = {\n store: new ParamsStore<T>(def),\n refCount: 0,\n disposalQueued: false,\n }\n cache.set(def, entry as CacheEntry<unknown>)\n }\n return entry.store\n}\n\n/** Explicit cleanup for non-React long-running apps. */\nexport function releaseCachedStore<T>(def: ParamsDefinition<T>): void {\n const entry = cache.get(def) as CacheEntry<T> | undefined\n if (!entry) {\n // Cache entry already gone (e.g., microtask-deferred React release ran\n // first), but the name registration may still be live. Clear it so the\n // same name can be reused without a spurious duplicate-name warning.\n if (def.name !== undefined && seenNames.get(def.name) === def) {\n seenNames.delete(def.name)\n }\n return\n }\n disposeAndClear(def, entry)\n}\n\nfunction warnOnDuplicateName(def: ParamsDefinition): void {\n if (def.name === undefined) return\n const existing = seenNames.get(def.name)\n if (existing !== undefined && existing !== def) {\n warn(\n `Duplicate definition name '${def.name}' detected. Names must be unique across definitions for v0.2 SSR snapshot keys.`,\n )\n return\n }\n seenNames.set(def.name, def)\n}\n"],"mappings":"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","names":["pathname"]}
|
package/dist/devtools.d.cts
CHANGED
package/dist/devtools.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -460,17 +460,43 @@ var ParamsStore = class {
|
|
|
460
460
|
getFieldConfig(path) {
|
|
461
461
|
return this.fieldConfigs[path];
|
|
462
462
|
}
|
|
463
|
-
|
|
463
|
+
// ─── Writes ───────────────────────────────────────────────────────────
|
|
464
|
+
/**
|
|
465
|
+
* Apply a partial update — write multiple fields at once.
|
|
466
|
+
*
|
|
467
|
+
* `set(partial)` is the canonical write form. The path-and-value form
|
|
468
|
+
* (`set(path, value)` historically) is now `setField(path, value)` —
|
|
469
|
+
* see {@link setField}. The split keeps `set`'s single-signature
|
|
470
|
+
* inference clean for the common `set({ [key]: value })` pattern, which
|
|
471
|
+
* previously required `as Partial<T>` casts because TypeScript couldn't
|
|
472
|
+
* resolve the overload from a generic-keyed object literal.
|
|
473
|
+
*
|
|
474
|
+
* @example
|
|
475
|
+
* ```ts
|
|
476
|
+
* p.set({ page: 1, sort: 'updated' })
|
|
477
|
+
* p.set({ [key]: value }) // works without a cast now
|
|
478
|
+
* ```
|
|
479
|
+
*/
|
|
480
|
+
set(partial, options) {
|
|
464
481
|
if (this.disposed) return;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
482
|
+
this.applyUpdates(partial, options);
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Write a single field by path. Equivalent to `set({ [path]: value })`
|
|
486
|
+
* but skips the object-literal allocation and avoids the inference issue
|
|
487
|
+
* that motivated splitting the API.
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* ```ts
|
|
491
|
+
* p.setField('query', 'react')
|
|
492
|
+
* p.setField('filters.tags', ['ts', 'react'])
|
|
493
|
+
* ```
|
|
494
|
+
*/
|
|
495
|
+
setField(path, value, options) {
|
|
496
|
+
if (this.disposed) return;
|
|
497
|
+
this.applyUpdates({ [path]: value }, options);
|
|
498
|
+
}
|
|
499
|
+
applyUpdates(updates, options) {
|
|
474
500
|
const initialChanges = {};
|
|
475
501
|
for (const [path, v] of Object.entries(updates)) {
|
|
476
502
|
const old = deepGet(this.values, path);
|
|
@@ -502,13 +528,13 @@ var ParamsStore = class {
|
|
|
502
528
|
/** Boolean-flip helper. */
|
|
503
529
|
toggle(path, options) {
|
|
504
530
|
const current = this.getValue(path);
|
|
505
|
-
this.
|
|
531
|
+
this.setField(path, !current, options);
|
|
506
532
|
}
|
|
507
533
|
/** Push a value onto an array field. */
|
|
508
534
|
append(path, value, options) {
|
|
509
535
|
const current = this.getValue(path);
|
|
510
536
|
if (!Array.isArray(current)) return;
|
|
511
|
-
this.
|
|
537
|
+
this.setField(path, [...current, value], options);
|
|
512
538
|
}
|
|
513
539
|
/** Remove the first array element matching `value` by deepEqual. */
|
|
514
540
|
remove(path, value, options) {
|
|
@@ -516,14 +542,14 @@ var ParamsStore = class {
|
|
|
516
542
|
if (!Array.isArray(current)) return;
|
|
517
543
|
const idx = current.findIndex((item) => deepEqual(item, value));
|
|
518
544
|
if (idx === -1) return;
|
|
519
|
-
this.
|
|
545
|
+
this.setField(path, [...current.slice(0, idx), ...current.slice(idx + 1)], options);
|
|
520
546
|
}
|
|
521
547
|
/** Remove the array element at the given index. */
|
|
522
548
|
removeAt(path, index, options) {
|
|
523
549
|
const current = this.getValue(path);
|
|
524
550
|
if (!Array.isArray(current)) return;
|
|
525
551
|
if (index < 0 || index >= current.length) return;
|
|
526
|
-
this.
|
|
552
|
+
this.setField(path, [...current.slice(0, index), ...current.slice(index + 1)], options);
|
|
527
553
|
}
|
|
528
554
|
cycle(path, valuesOrOptions, maybeOptions) {
|
|
529
555
|
let resolved;
|
|
@@ -549,12 +575,12 @@ var ParamsStore = class {
|
|
|
549
575
|
const current = this.getValue(path);
|
|
550
576
|
const idx = resolved.findIndex((o) => deepEqual(o, current));
|
|
551
577
|
const next = idx === -1 ? resolved[0] : resolved[(idx + 1) % resolved.length];
|
|
552
|
-
this.
|
|
578
|
+
this.setField(path, next, setOpts);
|
|
553
579
|
}
|
|
554
580
|
/** Reset a single field to its default. */
|
|
555
581
|
clear(path, options) {
|
|
556
582
|
const def = deepGet(this.defaults, path);
|
|
557
|
-
this.
|
|
583
|
+
this.setField(path, def, options);
|
|
558
584
|
}
|
|
559
585
|
/** Reset all fields to defaults; optional partial overrides + SetOptions. */
|
|
560
586
|
reset(values, options) {
|