@victorylabs/params 0.2.0 → 0.3.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/package.json +2 -2
- package/dist/chunk-S276UVQK.js.map +0 -1
package/dist/react.js
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
import {
|
|
2
2
|
acquire,
|
|
3
3
|
release
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-DSAHBEAQ.js";
|
|
5
5
|
import "./chunk-4T4THPFW.js";
|
|
6
6
|
import "./chunk-5NSLHAHG.js";
|
|
7
7
|
import {
|
|
8
8
|
defaultSerialize
|
|
9
9
|
} from "./chunk-5UKBDZTP.js";
|
|
10
10
|
|
|
11
|
+
// src/react/use-debounced-value.ts
|
|
12
|
+
import { useEffect, useState } from "react";
|
|
13
|
+
function useDebouncedValue(value, ms) {
|
|
14
|
+
const [debounced, setDebounced] = useState(value);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (ms <= 0) {
|
|
17
|
+
setDebounced(value);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const id = setTimeout(() => setDebounced(value), ms);
|
|
21
|
+
return () => clearTimeout(id);
|
|
22
|
+
}, [value, ms]);
|
|
23
|
+
return debounced;
|
|
24
|
+
}
|
|
25
|
+
|
|
11
26
|
// src/react/use-field-input.tsx
|
|
12
|
-
import { useCallback, useEffect as
|
|
27
|
+
import { useCallback, useEffect as useEffect3, useRef, useState as useState2 } from "react";
|
|
13
28
|
|
|
14
29
|
// src/react/use-field-value.ts
|
|
15
|
-
import { useEffect, useReducer } from "react";
|
|
30
|
+
import { useEffect as useEffect2, useReducer } from "react";
|
|
16
31
|
function useFieldValue(store, path) {
|
|
17
32
|
const [, forceRender] = useReducer((tick) => tick + 1, 0);
|
|
18
|
-
|
|
33
|
+
useEffect2(() => store.subscribe(path, forceRender), [store, path]);
|
|
19
34
|
return store.getValue(path);
|
|
20
35
|
}
|
|
21
36
|
|
|
@@ -24,10 +39,10 @@ function useFieldInput(store, path) {
|
|
|
24
39
|
const storeValue = useFieldValue(store, path);
|
|
25
40
|
const config = store.getFieldConfig(path);
|
|
26
41
|
const debounceMs = config?.debounce ?? 0;
|
|
27
|
-
const [shadow, setShadow] =
|
|
42
|
+
const [shadow, setShadow] = useState2(null);
|
|
28
43
|
const debouncerRef = useRef(null);
|
|
29
44
|
const lastStoreValueRef = useRef(storeValue);
|
|
30
|
-
|
|
45
|
+
useEffect3(() => {
|
|
31
46
|
if (Object.is(lastStoreValueRef.current, storeValue)) return;
|
|
32
47
|
lastStoreValueRef.current = storeValue;
|
|
33
48
|
if (shadow !== null && shadow !== defaultSerialize(storeValue)) {
|
|
@@ -45,7 +60,7 @@ function useFieldInput(store, path) {
|
|
|
45
60
|
setShadow(next);
|
|
46
61
|
debouncerRef.current?.trigger(next);
|
|
47
62
|
} else {
|
|
48
|
-
store.
|
|
63
|
+
store.setField(path, next);
|
|
49
64
|
}
|
|
50
65
|
},
|
|
51
66
|
[store, path, debounceMs]
|
|
@@ -65,7 +80,7 @@ function createDebouncer(store, path, ms, onCommit) {
|
|
|
65
80
|
let pendingValue = null;
|
|
66
81
|
const commit = () => {
|
|
67
82
|
if (pendingValue !== null) {
|
|
68
|
-
store.
|
|
83
|
+
store.setField(path, pendingValue);
|
|
69
84
|
pendingValue = null;
|
|
70
85
|
onCommit();
|
|
71
86
|
}
|
|
@@ -93,16 +108,16 @@ function createDebouncer(store, path, ms, onCommit) {
|
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
// src/react/use-params.tsx
|
|
96
|
-
import { useCallback as useCallback2, useDeferredValue, useEffect as
|
|
111
|
+
import { useCallback as useCallback2, useDeferredValue, useEffect as useEffect5, useReducer as useReducer2 } from "react";
|
|
97
112
|
|
|
98
113
|
// src/react/use-shared-store.ts
|
|
99
|
-
import { useEffect as
|
|
114
|
+
import { useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
100
115
|
function useSharedStore(def) {
|
|
101
116
|
const storeRef = useRef2(null);
|
|
102
117
|
if (storeRef.current === null) {
|
|
103
118
|
storeRef.current = acquire(def);
|
|
104
119
|
}
|
|
105
|
-
|
|
120
|
+
useEffect4(
|
|
106
121
|
() => () => {
|
|
107
122
|
release(def);
|
|
108
123
|
storeRef.current = null;
|
|
@@ -116,15 +131,13 @@ function useSharedStore(def) {
|
|
|
116
131
|
function useParams(def) {
|
|
117
132
|
const store = useSharedStore(def);
|
|
118
133
|
const [, forceRender] = useReducer2((tick) => tick + 1, 0);
|
|
119
|
-
|
|
134
|
+
useEffect5(() => store.subscribe("", forceRender), [store]);
|
|
120
135
|
const set = useCallback2(
|
|
121
|
-
(
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
}),
|
|
136
|
+
(partial, options) => store.set(partial, options),
|
|
137
|
+
[store]
|
|
138
|
+
);
|
|
139
|
+
const setField = useCallback2(
|
|
140
|
+
(path, value2, options) => store.setField(path, value2, options),
|
|
128
141
|
[store]
|
|
129
142
|
);
|
|
130
143
|
const toggle = useCallback2(
|
|
@@ -176,11 +189,15 @@ function useParams(def) {
|
|
|
176
189
|
const deferred = (path) => useDeferredValue(useFieldValue(store, path));
|
|
177
190
|
const input = (path) => useFieldInput(store, path);
|
|
178
191
|
return {
|
|
192
|
+
// Cast: store.getValues() returns Partial<T> for the storage layer's
|
|
193
|
+
// sake, but every field in ParamsDefinition declares a default so the
|
|
194
|
+
// hydrated view is structurally complete. See ParamsController.values.
|
|
179
195
|
values: store.getValues(),
|
|
180
196
|
value,
|
|
181
197
|
deferred,
|
|
182
198
|
input,
|
|
183
199
|
set,
|
|
200
|
+
setField,
|
|
184
201
|
toggle,
|
|
185
202
|
append,
|
|
186
203
|
remove: removeFn,
|
|
@@ -195,6 +212,7 @@ function useParams(def) {
|
|
|
195
212
|
};
|
|
196
213
|
}
|
|
197
214
|
export {
|
|
215
|
+
useDebouncedValue,
|
|
198
216
|
useFieldInput,
|
|
199
217
|
useFieldValue,
|
|
200
218
|
useParams
|
package/dist/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/react/use-field-input.tsx","../src/react/use-field-value.ts","../src/react/use-params.tsx","../src/react/use-shared-store.ts"],"sourcesContent":["import type { ChangeEvent } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { ParamsStore } from '../params-store'\nimport { defaultSerialize } from '../schema'\nimport { useFieldValue } from './use-field-value'\n\nexport interface InputBindings {\n name: string\n value: string\n onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void\n onBlur: () => void\n}\n\ninterface DebounceController {\n trigger: (value: string) => void\n flush: () => void\n cancel: () => void\n}\n\n/**\n * Hook that returns input bindings for a single path. Handles:\n * - Reading the current store value (debounced shadow during typing)\n * - Per-field debounce from the field config\n * - Shadow cancellation when an external `set()` invalidates the pending value\n * - `onBlur` flushes any pending debounce immediately\n *\n * The bindings are spreadable directly: `<input {...p.input('query')} />`.\n */\nexport function useFieldInput(store: ParamsStore, path: string): InputBindings {\n const storeValue = useFieldValue<unknown>(store, path)\n const config = store.getFieldConfig(path)\n const debounceMs = config?.debounce ?? 0\n\n const [shadow, setShadow] = useState<string | null>(null)\n const debouncerRef = useRef<DebounceController | null>(null)\n const lastStoreValueRef = useRef(storeValue)\n\n // External change → drop shadow + cancel pending debounce.\n // Track storeValue identity so the effect ONLY reacts to actual store\n // changes — not to local shadow updates while the user is typing.\n useEffect(() => {\n if (Object.is(lastStoreValueRef.current, storeValue)) return\n lastStoreValueRef.current = storeValue\n if (shadow !== null && shadow !== defaultSerialize(storeValue)) {\n debouncerRef.current?.cancel()\n setShadow(null)\n }\n }, [storeValue, shadow])\n\n // Lazy-create the debounce controller for this hook instance\n if (debounceMs > 0 && debouncerRef.current === null) {\n debouncerRef.current = createDebouncer(store, path, debounceMs, () => setShadow(null))\n }\n\n const onChange = useCallback(\n (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {\n const next = event.target.value\n if (debounceMs > 0) {\n setShadow(next)\n debouncerRef.current?.trigger(next)\n } else {\n store.set(path, next)\n }\n },\n [store, path, debounceMs],\n )\n\n const onBlur = useCallback(() => {\n debouncerRef.current?.flush()\n }, [])\n\n return {\n name: path,\n value: shadow ?? defaultSerialize(storeValue),\n onChange,\n onBlur,\n }\n}\n\nfunction createDebouncer(\n store: ParamsStore,\n path: string,\n ms: number,\n onCommit: () => void,\n): DebounceController {\n let timer: ReturnType<typeof setTimeout> | undefined\n let pendingValue: string | null = null\n\n const commit = () => {\n if (pendingValue !== null) {\n store.set(path, pendingValue)\n pendingValue = null\n onCommit()\n }\n }\n\n return {\n trigger: (value: string) => {\n pendingValue = value\n if (timer !== undefined) clearTimeout(timer)\n timer = setTimeout(() => {\n timer = undefined\n commit()\n }, ms)\n },\n flush: () => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n commit()\n },\n cancel: () => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n pendingValue = null\n },\n }\n}\n","import { useEffect, useReducer } from 'react'\n\nimport type { ParamsStore } from '../params-store'\n\n/**\n * Subscribe to a single path on the store. Re-renders the calling component\n * only when that path's value changes. Sibling-path changes don't fire.\n */\nexport function useFieldValue<TValue = unknown>(store: ParamsStore, path: string): TValue {\n const [, forceRender] = useReducer((tick: number) => tick + 1, 0)\n useEffect(() => store.subscribe(path, forceRender), [store, path])\n return store.getValue(path) as TValue\n}\n","import { useCallback, useDeferredValue, useEffect, useReducer } from 'react'\n\nimport type { ParamsStore, SetOptions } from '../params-store'\nimport type { ParamsDefinition } from '../types'\nimport type { InputBindings } from './use-field-input'\nimport { useFieldInput } from './use-field-input'\nimport { useFieldValue } from './use-field-value'\nimport { useSharedStore } from './use-shared-store'\n\nexport interface ParamsController<T> {\n /** Snapshot of all values. Re-renders the calling component on any change. */\n readonly values: Readonly<Partial<T>>\n\n /** Hook — subscribes to a single path. Call at top level of render. */\n value<TValue = unknown>(path: string): TValue\n\n /** Hook — `value(path)` wrapped in `useDeferredValue`. */\n deferred<TValue = unknown>(path: string): TValue\n\n /** Hook — input bindings for `<input {...p.input('path')} />`. */\n input(path: string): InputBindings\n\n // Pass-through to the store (callable anywhere — not hooks)\n set: ParamsStore<T>['set']\n toggle: ParamsStore<T>['toggle']\n append: ParamsStore<T>['append']\n remove: ParamsStore<T>['remove']\n removeAt: ParamsStore<T>['removeAt']\n cycle: ParamsStore<T>['cycle']\n clear: ParamsStore<T>['clear']\n reset: ParamsStore<T>['reset']\n subscribe: ParamsStore<T>['subscribe']\n toQuery: ParamsStore<T>['toQuery']\n href: ParamsStore<T>['href']\n\n /** Unstable escape hatch — for devtools, advanced integrations. */\n readonly store: ParamsStore<T>\n}\n\n/**\n * Single-hook React API for `@victorylabs/params`. Returns a controller with\n * everything a component needs:\n *\n * ```tsx\n * function Filters() {\n * const p = useParams(filtersDef)\n * return (\n * <>\n * <input {...p.input('query')} />\n * <Pagination page={p.value('page')} onPage={(page) => p.set({ page })} />\n * <ResultList query={p.deferred('query')} />\n * </>\n * )\n * }\n * ```\n *\n * Cross-component sharing is automatic — multiple `useParams(def)` calls with\n * the same definition share the same store instance via the WeakMap cache.\n */\nexport function useParams<T extends Record<string, unknown> = Record<string, unknown>>(\n def: ParamsDefinition<T>,\n): ParamsController<T> {\n const store = useSharedStore(def) as ParamsStore<T>\n\n // Subscribe to root — re-renders this caller on any field change.\n // Granular subscriptions (avoid parent re-renders) are achieved via\n // `p.value(path)` / `p.input(path)` in CHILD components.\n const [, forceRender] = useReducer((tick: number) => tick + 1, 0)\n useEffect(() => store.subscribe('', forceRender), [store])\n\n // Stable bindings for store methods (delegated as-is). The overloaded `set`\n // needs an explicit branch so TypeScript can pick the right call signature.\n const set = useCallback(\n ((\n pathOrPartial: string | Partial<T>,\n valueOrOptions?: unknown,\n maybeOptions?: SetOptions,\n ): void => {\n if (typeof pathOrPartial === 'string') {\n store.set(pathOrPartial, valueOrOptions, maybeOptions)\n } else {\n store.set(pathOrPartial, valueOrOptions as SetOptions | undefined)\n }\n }) as ParamsStore<T>['set'],\n [store],\n )\n const toggle = useCallback<ParamsStore<T>['toggle']>(\n (path, options) => store.toggle(path, options),\n [store],\n )\n const append = useCallback<ParamsStore<T>['append']>(\n (path, value, options) => store.append(path, value, options),\n [store],\n )\n const removeFn = useCallback<ParamsStore<T>['remove']>(\n (path, value, options) => store.remove(path, value, options),\n [store],\n )\n const removeAt = useCallback<ParamsStore<T>['removeAt']>(\n (path, index, options) => store.removeAt(path, index, options),\n [store],\n )\n const cycle = useCallback(\n ((\n path: string,\n valuesOrOptions?: ReadonlyArray<unknown> | SetOptions,\n maybeOptions?: SetOptions,\n ): void => {\n if (Array.isArray(valuesOrOptions)) {\n store.cycle(path, valuesOrOptions, maybeOptions)\n } else if (valuesOrOptions !== undefined) {\n store.cycle(path, valuesOrOptions as SetOptions)\n } else {\n store.cycle(path)\n }\n }) as ParamsStore<T>['cycle'],\n [store],\n )\n const clear = useCallback<ParamsStore<T>['clear']>(\n (path, options) => store.clear(path, options),\n [store],\n )\n const reset = useCallback<ParamsStore<T>['reset']>(\n (values, options) => store.reset(values, options),\n [store],\n )\n const subscribe = useCallback<ParamsStore<T>['subscribe']>(\n (path, listener) => store.subscribe(path, listener),\n [store],\n )\n const toQuery = useCallback<ParamsStore<T>['toQuery']>(\n (overrides) => store.toQuery(overrides),\n [store],\n )\n const href = useCallback<ParamsStore<T>['href']>((overrides) => store.href(overrides), [store])\n\n // The hook-based methods need access to `store` via closure. Each call\n // (`p.value('x')`) invokes its hook in the consumer's render scope.\n const value = <TValue = unknown>(path: string): TValue => useFieldValue<TValue>(store, path)\n const deferred = <TValue = unknown>(path: string): TValue =>\n useDeferredValue(useFieldValue<TValue>(store, path))\n const input = (path: string): InputBindings => useFieldInput(store, path)\n\n return {\n values: store.getValues(),\n value,\n deferred,\n input,\n set,\n toggle,\n append,\n remove: removeFn,\n removeAt,\n cycle,\n clear,\n reset,\n subscribe,\n toQuery,\n href,\n store,\n }\n}\n","import { useEffect, useRef } from 'react'\n\nimport type { ParamsStore } from '../params-store'\nimport { acquire, release } from '../store-cache'\nimport type { ParamsDefinition } from '../types'\n\n/**\n * Internal hook: acquires the shared store on mount, releases on unmount.\n * Survives React 18 Strict Mode (microtask-deferred disposal cancels itself\n * when a fresh acquire arrives in the same tick).\n *\n * Multiple components using the same definition get the same store instance\n * — that's the cross-component-sharing primitive for params.\n */\nexport function useSharedStore<T>(def: ParamsDefinition<T>): ParamsStore<T> {\n const storeRef = useRef<ParamsStore<T> | null>(null)\n if (storeRef.current === null) {\n storeRef.current = acquire(def)\n }\n\n useEffect(\n () => () => {\n release(def)\n storeRef.current = null\n },\n [def],\n )\n\n return storeRef.current\n}\n"],"mappings":"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","names":["useEffect","useEffect","useCallback","useEffect","useReducer","useEffect","useRef","useRef","useEffect","useReducer","useEffect","useCallback","value"]}
|
|
1
|
+
{"version":3,"sources":["../src/react/use-debounced-value.ts","../src/react/use-field-input.tsx","../src/react/use-field-value.ts","../src/react/use-params.tsx","../src/react/use-shared-store.ts"],"sourcesContent":["import { useEffect, useState } from 'react'\n\n/**\n * Returns a copy of `value` that updates only after `ms` milliseconds of\n * stability. Rapid changes cancel pending updates so only the last one\n * settles. `ms <= 0` short-circuits to a synchronous pass-through.\n *\n * Pairs with `@victorylabs/params`' field-level `debounce:` config (which\n * lives at the URL/storage layer) for cases where a component wants to\n * debounce arbitrary local state — e.g., a search input that drives a\n * downstream query but isn't itself a params field.\n *\n * @example\n * ```tsx\n * function SearchBox() {\n * const [query, setQuery] = useState('')\n * const debounced = useDebouncedValue(query, 200)\n *\n * const { data } = api.queries.search({ params: { q: debounced } })\n * return <input value={query} onChange={(e) => setQuery(e.target.value)} />\n * }\n * ```\n */\nexport function useDebouncedValue<T>(value: T, ms: number): T {\n const [debounced, setDebounced] = useState(value)\n\n useEffect(() => {\n if (ms <= 0) {\n setDebounced(value)\n return\n }\n const id = setTimeout(() => setDebounced(value), ms)\n return () => clearTimeout(id)\n }, [value, ms])\n\n return debounced\n}\n","import type { ChangeEvent } from 'react'\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { ParamsStore } from '../params-store'\nimport { defaultSerialize } from '../schema'\nimport { useFieldValue } from './use-field-value'\n\nexport interface InputBindings {\n name: string\n value: string\n onChange: (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => void\n onBlur: () => void\n}\n\ninterface DebounceController {\n trigger: (value: string) => void\n flush: () => void\n cancel: () => void\n}\n\n/**\n * Hook that returns input bindings for a single path. Handles:\n * - Reading the current store value (debounced shadow during typing)\n * - Per-field debounce from the field config\n * - Shadow cancellation when an external `set()` invalidates the pending value\n * - `onBlur` flushes any pending debounce immediately\n *\n * The bindings are spreadable directly: `<input {...p.input('query')} />`.\n */\nexport function useFieldInput(store: ParamsStore, path: string): InputBindings {\n const storeValue = useFieldValue<unknown>(store, path)\n const config = store.getFieldConfig(path)\n const debounceMs = config?.debounce ?? 0\n\n const [shadow, setShadow] = useState<string | null>(null)\n const debouncerRef = useRef<DebounceController | null>(null)\n const lastStoreValueRef = useRef(storeValue)\n\n // External change → drop shadow + cancel pending debounce.\n // Track storeValue identity so the effect ONLY reacts to actual store\n // changes — not to local shadow updates while the user is typing.\n useEffect(() => {\n if (Object.is(lastStoreValueRef.current, storeValue)) return\n lastStoreValueRef.current = storeValue\n if (shadow !== null && shadow !== defaultSerialize(storeValue)) {\n debouncerRef.current?.cancel()\n setShadow(null)\n }\n }, [storeValue, shadow])\n\n // Lazy-create the debounce controller for this hook instance\n if (debounceMs > 0 && debouncerRef.current === null) {\n debouncerRef.current = createDebouncer(store, path, debounceMs, () => setShadow(null))\n }\n\n const onChange = useCallback(\n (event: ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {\n const next = event.target.value\n if (debounceMs > 0) {\n setShadow(next)\n debouncerRef.current?.trigger(next)\n } else {\n store.setField(path, next)\n }\n },\n [store, path, debounceMs],\n )\n\n const onBlur = useCallback(() => {\n debouncerRef.current?.flush()\n }, [])\n\n return {\n name: path,\n value: shadow ?? defaultSerialize(storeValue),\n onChange,\n onBlur,\n }\n}\n\nfunction createDebouncer(\n store: ParamsStore,\n path: string,\n ms: number,\n onCommit: () => void,\n): DebounceController {\n let timer: ReturnType<typeof setTimeout> | undefined\n let pendingValue: string | null = null\n\n const commit = () => {\n if (pendingValue !== null) {\n store.setField(path, pendingValue)\n pendingValue = null\n onCommit()\n }\n }\n\n return {\n trigger: (value: string) => {\n pendingValue = value\n if (timer !== undefined) clearTimeout(timer)\n timer = setTimeout(() => {\n timer = undefined\n commit()\n }, ms)\n },\n flush: () => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n commit()\n },\n cancel: () => {\n if (timer !== undefined) clearTimeout(timer)\n timer = undefined\n pendingValue = null\n },\n }\n}\n","import { useEffect, useReducer } from 'react'\n\nimport type { ParamsStore } from '../params-store'\n\n/**\n * Subscribe to a single path on the store. Re-renders the calling component\n * only when that path's value changes. Sibling-path changes don't fire.\n */\nexport function useFieldValue<TValue = unknown>(store: ParamsStore, path: string): TValue {\n const [, forceRender] = useReducer((tick: number) => tick + 1, 0)\n useEffect(() => store.subscribe(path, forceRender), [store, path])\n return store.getValue(path) as TValue\n}\n","import { useCallback, useDeferredValue, useEffect, useReducer } from 'react'\n\nimport type { ParamsStore, SetOptions } from '../params-store'\nimport type { ParamsDefinition } from '../types'\nimport type { InputBindings } from './use-field-input'\nimport { useFieldInput } from './use-field-input'\nimport { useFieldValue } from './use-field-value'\nimport { useSharedStore } from './use-shared-store'\n\nexport interface ParamsController<T> {\n /**\n * Snapshot of all values, with defaults hydrated. Re-renders the calling\n * component on any change.\n *\n * Typed as `Readonly<T>` (not `Readonly<Partial<T>>`) because every field\n * in a `ParamsDefinition` declares a default — either via the schema's\n * `.default()` or a plain spec's `default:` field — so the controller's\n * view is always complete. The underlying storage layer remains `Partial`\n * (URL/localStorage may be missing keys); the controller fills those in.\n */\n readonly values: Readonly<T>\n\n /** Hook — subscribes to a single path. Call at top level of render. */\n value<TValue = unknown>(path: string): TValue\n\n /** Hook — `value(path)` wrapped in `useDeferredValue`. */\n deferred<TValue = unknown>(path: string): TValue\n\n /** Hook — input bindings for `<input {...p.input('path')} />`. */\n input(path: string): InputBindings\n\n // Pass-through to the store (callable anywhere — not hooks)\n /**\n * Apply a partial update — write multiple fields at once.\n * For single-field writes use {@link setField}.\n */\n set: ParamsStore<T>['set']\n /** Write a single field by path. Replaces the old `set(path, value)` form. */\n setField: ParamsStore<T>['setField']\n toggle: ParamsStore<T>['toggle']\n append: ParamsStore<T>['append']\n remove: ParamsStore<T>['remove']\n removeAt: ParamsStore<T>['removeAt']\n cycle: ParamsStore<T>['cycle']\n clear: ParamsStore<T>['clear']\n reset: ParamsStore<T>['reset']\n subscribe: ParamsStore<T>['subscribe']\n toQuery: ParamsStore<T>['toQuery']\n href: ParamsStore<T>['href']\n\n /** Unstable escape hatch — for devtools, advanced integrations. */\n readonly store: ParamsStore<T>\n}\n\n/**\n * Single-hook React API for `@victorylabs/params`. Returns a controller with\n * everything a component needs:\n *\n * ```tsx\n * function Filters() {\n * const p = useParams(filtersDef)\n * return (\n * <>\n * <input {...p.input('query')} />\n * <Pagination page={p.value('page')} onPage={(page) => p.set({ page })} />\n * <ResultList query={p.deferred('query')} />\n * </>\n * )\n * }\n * ```\n *\n * Cross-component sharing is automatic — multiple `useParams(def)` calls with\n * the same definition share the same store instance via the WeakMap cache.\n */\nexport function useParams<T extends Record<string, unknown> = Record<string, unknown>>(\n def: ParamsDefinition<T>,\n): ParamsController<T> {\n const store = useSharedStore(def) as ParamsStore<T>\n\n // Subscribe to root — re-renders this caller on any field change.\n // Granular subscriptions (avoid parent re-renders) are achieved via\n // `p.value(path)` / `p.input(path)` in CHILD components.\n const [, forceRender] = useReducer((tick: number) => tick + 1, 0)\n useEffect(() => store.subscribe('', forceRender), [store])\n\n // Stable bindings for store methods (delegated as-is).\n const set = useCallback<ParamsStore<T>['set']>(\n (partial, options) => store.set(partial, options),\n [store],\n )\n const setField = useCallback<ParamsStore<T>['setField']>(\n (path, value, options) => store.setField(path, value, options),\n [store],\n )\n const toggle = useCallback<ParamsStore<T>['toggle']>(\n (path, options) => store.toggle(path, options),\n [store],\n )\n const append = useCallback<ParamsStore<T>['append']>(\n (path, value, options) => store.append(path, value, options),\n [store],\n )\n const removeFn = useCallback<ParamsStore<T>['remove']>(\n (path, value, options) => store.remove(path, value, options),\n [store],\n )\n const removeAt = useCallback<ParamsStore<T>['removeAt']>(\n (path, index, options) => store.removeAt(path, index, options),\n [store],\n )\n const cycle = useCallback(\n ((\n path: string,\n valuesOrOptions?: ReadonlyArray<unknown> | SetOptions,\n maybeOptions?: SetOptions,\n ): void => {\n if (Array.isArray(valuesOrOptions)) {\n store.cycle(path, valuesOrOptions, maybeOptions)\n } else if (valuesOrOptions !== undefined) {\n store.cycle(path, valuesOrOptions as SetOptions)\n } else {\n store.cycle(path)\n }\n }) as ParamsStore<T>['cycle'],\n [store],\n )\n const clear = useCallback<ParamsStore<T>['clear']>(\n (path, options) => store.clear(path, options),\n [store],\n )\n const reset = useCallback<ParamsStore<T>['reset']>(\n (values, options) => store.reset(values, options),\n [store],\n )\n const subscribe = useCallback<ParamsStore<T>['subscribe']>(\n (path, listener) => store.subscribe(path, listener),\n [store],\n )\n const toQuery = useCallback<ParamsStore<T>['toQuery']>(\n (overrides) => store.toQuery(overrides),\n [store],\n )\n const href = useCallback<ParamsStore<T>['href']>((overrides) => store.href(overrides), [store])\n\n // The hook-based methods need access to `store` via closure. Each call\n // (`p.value('x')`) invokes its hook in the consumer's render scope.\n const value = <TValue = unknown>(path: string): TValue => useFieldValue<TValue>(store, path)\n const deferred = <TValue = unknown>(path: string): TValue =>\n useDeferredValue(useFieldValue<TValue>(store, path))\n const input = (path: string): InputBindings => useFieldInput(store, path)\n\n return {\n // Cast: store.getValues() returns Partial<T> for the storage layer's\n // sake, but every field in ParamsDefinition declares a default so the\n // hydrated view is structurally complete. See ParamsController.values.\n values: store.getValues() as Readonly<T>,\n value,\n deferred,\n input,\n set,\n setField,\n toggle,\n append,\n remove: removeFn,\n removeAt,\n cycle,\n clear,\n reset,\n subscribe,\n toQuery,\n href,\n store,\n }\n}\n","import { useEffect, useRef } from 'react'\n\nimport type { ParamsStore } from '../params-store'\nimport { acquire, release } from '../store-cache'\nimport type { ParamsDefinition } from '../types'\n\n/**\n * Internal hook: acquires the shared store on mount, releases on unmount.\n * Survives React 18 Strict Mode (microtask-deferred disposal cancels itself\n * when a fresh acquire arrives in the same tick).\n *\n * Multiple components using the same definition get the same store instance\n * — that's the cross-component-sharing primitive for params.\n */\nexport function useSharedStore<T>(def: ParamsDefinition<T>): ParamsStore<T> {\n const storeRef = useRef<ParamsStore<T> | null>(null)\n if (storeRef.current === null) {\n storeRef.current = acquire(def)\n }\n\n useEffect(\n () => () => {\n release(def)\n storeRef.current = null\n },\n [def],\n )\n\n return storeRef.current\n}\n"],"mappings":"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","names":["useEffect","useState","useEffect","useState","useEffect","useCallback","useEffect","useReducer","useEffect","useRef","useRef","useEffect","useReducer","useEffect","useCallback","value"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@victorylabs/params",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Centralized view-state config (filters, sort, pagination, app config) with pluggable storage. Memory by default; URL/localStorage/sessionStorage opt-in. Single React hook DX, schema-validated reads, cross-component sharing.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"fake-indexeddb": "^6.0.0",
|
|
88
88
|
"valibot": "^1.3.1",
|
|
89
89
|
"zod": "^3.23.8",
|
|
90
|
-
"@victorylabs/forms": "0.
|
|
90
|
+
"@victorylabs/forms": "0.3.0",
|
|
91
91
|
"@victorylabs/utils": "0.1.0"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
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 set(path: string, value: unknown, options?: SetOptions): void\n set(partial: Partial<T>, options?: SetOptions): void\n set(\n pathOrPartial: string | Partial<T>,\n valueOrOptions?: unknown,\n maybeOptions?: SetOptions,\n ): void {\n if (this.disposed) return\n\n let updates: Record<string, unknown>\n let options: SetOptions | undefined\n if (typeof pathOrPartial === 'string') {\n updates = { [pathOrPartial]: valueOrOptions }\n options = maybeOptions\n } else {\n updates = pathOrPartial as Record<string, unknown>\n options = valueOrOptions as SetOptions | undefined\n }\n\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.set(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.set(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.set(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.set(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.set(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.set(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":";;;;;;;;;;;;;;;;;;;;AAgBA,IAAM,YAAY;AAmDX,SAAS,eACd,gBACA,KACe;AACf,QAAM,UAAmC,EAAE,GAAG,eAAe;AAC7D,QAAM,WAAqB,CAAC;AAI5B,QAAM,UAAmC,EAAE,GAAG,IAAI,eAAe,GAAG,eAAe;AAGnF,MAAI,WAAW,IAAI,IAAI,OAAO,KAAK,cAAc,CAAC;AAElD,QAAM,UAAU,IAAI,IAAY,QAAQ;AAExC,MAAI,QAAQ;AACZ,SAAO,SAAS,OAAO,GAAG;AACxB,QAAI,SAAS,WAAW;AACtB,eAAS;AAAA,QACP,sBAAsB,SAAS,mCAAmC,CAAC,GAAG,QAAQ,EAAE,KAAK,IAAI,CAAC;AAAA,MAC5F;AACA;AAAA,IACF;AAEA,UAAM,eAAe,oBAAI,IAAY;AAErC,eAAW,cAAc,IAAI,UAAU;AACrC,YAAM,SAAS,IAAI,aAAa,UAAU;AAC1C,UAAI,CAAC,QAAQ,YAAa;AAC1B,UAAI,SAAS,IAAI,UAAU,EAAG;AAE9B,YAAM,OAAO,YAAY,QAAQ,IAAI,UAAU,UAAU;AAEzD,YAAM,cAAc,KAAK,OAAO,CAAC,MAAM,SAAS,IAAI,CAAC,CAAC;AACtD,UAAI,YAAY,WAAW,EAAG;AAE9B,UAAI,QAAQ,IAAI,UAAU,GAAG;AAC3B,iBAAS;AAAA,UACP,oBAAoB,UAAU,wCAAwC,YAAY,KAAK,IAAI,CAAC;AAAA,QAC9F;AACA;AAAA,MACF;AAGA,YAAM,WAAW,eAAe,OAAO,aAAa,MAAM,SAAS,IAAI,UAAU,UAAU;AAI3F,UAAI,OAAO,GAAG,UAAU,QAAQ,UAAU,CAAC,EAAG;AAE9C,cAAQ,UAAU,IAAI;AACtB,cAAQ,UAAU,IAAI;AACtB,cAAQ,IAAI,UAAU;AACtB,mBAAa,IAAI,UAAU;AAAA,IAC7B;AAEA,eAAW;AACX;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,SAAS;AAC7B;AAMA,SAAS,YAAY,QAA4B,UAAoB,UAA4B;AAC/F,MAAI,OAAO,cAAc,KAAK;AAC5B,UAAM,UAAU,IAAI,IAAI,OAAO,eAAe,CAAC,CAAC;AAChD,YAAQ,IAAI,QAAQ;AACpB,WAAO,SAAS,OAAO,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAC;AAAA,EAC/C;AACA,MAAI,MAAM,QAAQ,OAAO,SAAS,GAAG;AAEnC,WAAO,OAAO,UAAU,OAAO,CAAC,MAAM,MAAM,QAAQ;AAAA,EACtD;AACA,SAAO,CAAC;AACV;AAKA,SAAS,eACP,aACA,MACA,SACA,UACA,YACS;AACT,MAAI,gBAAgB,QAAS,QAAO,SAAS,UAAU;AACvD,MAAI,gBAAgB,QAAS,QAAO;AAIpC,QAAM,aAAsC,CAAC;AAC7C,aAAW,KAAK,MAAM;AACpB,eAAW,CAAC,IAAI,QAAQ,CAAC;AAAA,EAC3B;AACA,SAAO,YAAY,YAAY,QAAQ,UAAU,CAAC;AACpD;;;ACjKA,IAAM,WAAW,OAAa,EAAE,WAAW,oBAAI,IAAI,GAAG,UAAU,oBAAI,IAAI,EAAE;AAenE,IAAM,WAAN,MAAe;AAAA,EACH,OAAa,SAAS;AAAA,EAEvC,UAAU,MAAc,UAAkC;AACxD,UAAM,OAAO,KAAK,OAAO,IAAI;AAC7B,SAAK,UAAU,IAAI,QAAQ;AAC3B,WAAO,MAAM;AACX,WAAK,UAAU,OAAO,QAAQ;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,OAAO,MAAoB;AACzB,UAAM,WAAW,UAAU,IAAI;AAE/B,QAAI,UAAgB,KAAK;AACzB,SAAK,OAAO;AAEZ,eAAW,OAAO,UAAU;AAC1B,YAAM,OAAO,QAAQ,SAAS,IAAI,GAAG;AACrC,UAAI,CAAC,KAAM;AACX,gBAAU;AACV,WAAK,OAAO;AAAA,IACd;AAEA,gBAAY,OAAO;AAAA,EACrB;AAAA;AAAA,EAGA,KAAK,OAAO,IAAY;AACtB,UAAM,OAAO,KAAK,KAAK,IAAI;AAC3B,QAAI,CAAC,KAAM,QAAO;AAClB,WAAO,aAAa,IAAI;AAAA,EAC1B;AAAA,EAEQ,OAAO,MAAoB;AACjC,QAAI,OAAO,KAAK;AAChB,eAAW,OAAO,UAAU,IAAI,GAAG;AACjC,UAAI,QAAQ,KAAK,SAAS,IAAI,GAAG;AACjC,UAAI,CAAC,OAAO;AACV,gBAAQ,SAAS;AACjB,aAAK,SAAS,IAAI,KAAK,KAAK;AAAA,MAC9B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,KAAK,MAAgC;AAC3C,QAAI,OAAyB,KAAK;AAClC,eAAW,OAAO,UAAU,IAAI,GAAG;AACjC,aAAO,MAAM,SAAS,IAAI,GAAG;AAC7B,UAAI,CAAC,KAAM,QAAO;AAAA,IACpB;AACA,WAAO;AAAA,EACT;AACF;AAEA,SAAS,KAAK,MAAkB;AAC9B,aAAW,YAAY,KAAK,UAAW,UAAS;AAClD;AAEA,SAAS,YAAY,MAAkB;AACrC,aAAW,SAAS,KAAK,SAAS,OAAO,GAAG;AAC1C,SAAK,KAAK;AACV,gBAAY,KAAK;AAAA,EACnB;AACF;AAEA,SAAS,aAAa,MAAoB;AACxC,MAAI,QAAQ,KAAK,UAAU;AAC3B,aAAW,SAAS,KAAK,SAAS,OAAO,GAAG;AAC1C,aAAS,aAAa,KAAK;AAAA,EAC7B;AACA,SAAO;AACT;;;AChDA,IAAM,WAAW,OAAO,WAAW;AAe5B,IAAM,cAAN,MAA+C;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EAET;AAAA,EACS;AAAA,EACA,OAAO,IAAI,SAAS;AAAA,EACpB,kBAAkB,oBAAI,IAAoB;AAAA,EAEnD;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EAEnB,YAAY,KAA0B;AACpC,SAAK,OAAO,IAAI;AAChB,SAAK,UAAU,IAAI;AACnB,SAAK,eAAe,IAAI;AAExB,SAAK,WAAW,KAAK,gBAAgB;AACrC,SAAK,SAAS,EAAE,GAAG,KAAK,SAAS;AAKjC,UAAM,SAAS,uBAA0B,IAAI,IAAI;AACjD,QAAI,WAAW,QAAW;AACxB,WAAK,SAAS,EAAE,GAAG,KAAK,UAAU,GAAG,OAAO;AAAA,IAC9C,WAAW,KAAK,QAAQ,cAAc,CAAC,UAAU;AAAA,IAEjD,OAAO;AACL,WAAK,mBAAmB;AAAA,IAC1B;AAEA,QAAI,KAAK,QAAQ,WAAW;AAC1B,WAAK,qBAAqB,KAAK,QAAQ,UAAU,CAAC,QAAQ,KAAK,iBAAiB,GAAG,CAAC;AAAA,IACtF;AAAA,EACF;AAAA;AAAA,EAIA,YAAkC;AAChC,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,SAA2B,MAAkB;AAC3C,WAAO,QAAQ,KAAK,QAAQ,IAAI;AAAA,EAClC;AAAA;AAAA,EAGA,IAAI,gBAAkD;AACpD,UAAM,MAA8B,CAAC;AACrC,eAAW,CAAC,MAAM,MAAM,KAAK,KAAK,gBAAiB,KAAI,IAAI,IAAI;AAC/D,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,eAAe,MAAuC;AACpD,WAAO,KAAK,aAAa,IAAI;AAAA,EAC/B;AAAA,EAMA,IACE,eACA,gBACA,cACM;AACN,QAAI,KAAK,SAAU;AAEnB,QAAI;AACJ,QAAI;AACJ,QAAI,OAAO,kBAAkB,UAAU;AACrC,gBAAU,EAAE,CAAC,aAAa,GAAG,eAAe;AAC5C,gBAAU;AAAA,IACZ,OAAO;AACL,gBAAU;AACV,gBAAU;AAAA,IACZ;AAGA,UAAM,iBAA0C,CAAC;AACjD,eAAW,CAAC,MAAM,CAAC,KAAK,OAAO,QAAQ,OAAO,GAAG;AAC/C,YAAM,MAAM,QAAQ,KAAK,QAAQ,IAAI;AACrC,UAAI,UAAU,KAAK,CAAC,EAAG;AACvB,qBAAe,IAAI,IAAI;AAAA,IACzB;AACA,QAAI,OAAO,KAAK,cAAc,EAAE,WAAW,EAAG;AAG9C,UAAM,UAAU,eAAe,gBAAgB;AAAA,MAC7C,cAAc,KAAK;AAAA,MACnB,UAAU,KAAK;AAAA,MACf,eAAe,KAAK;AAAA,MACpB,UAAU,OAAO,KAAK,KAAK,IAAI;AAAA,IACjC,CAAC;AAED,eAAW,KAAK,QAAQ,SAAU,MAAK,CAAC;AAGxC,UAAM,UAAoB,CAAC;AAC3B,QAAI,OAAO,KAAK;AAChB,eAAW,CAAC,MAAM,CAAC,KAAK,OAAO,QAAQ,QAAQ,OAAO,GAAG;AACvD,YAAM,MAAM,QAAQ,MAAM,IAAI;AAC9B,UAAI,UAAU,KAAK,CAAC,EAAG;AACvB,aAAO,QAAQ,MAAM,MAAM,CAAC;AAC5B,cAAQ,KAAK,IAAI;AAAA,IACnB;AACA,QAAI,QAAQ,WAAW,EAAG;AAE1B,SAAK,SAAS;AACd,SAAK,uBAAuB;AAG5B,eAAW,QAAQ,QAAS,MAAK,KAAK,OAAO,IAAI;AAIjD,SAAK,iBAAiB,SAAS,OAAO;AAAA,EACxC;AAAA;AAAA,EAGA,OAAO,MAAc,SAA4B;AAC/C,UAAM,UAAU,KAAK,SAAS,IAAI;AAClC,SAAK,IAAI,MAAM,CAAC,SAAS,OAAO;AAAA,EAClC;AAAA;AAAA,EAGA,OAAO,MAAc,OAAgB,SAA4B;AAC/D,UAAM,UAAU,KAAK,SAAS,IAAI;AAClC,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,SAAK,IAAI,MAAM,CAAC,GAAG,SAAS,KAAK,GAAG,OAAO;AAAA,EAC7C;AAAA;AAAA,EAGA,OAAO,MAAc,OAAgB,SAA4B;AAC/D,UAAM,UAAU,KAAK,SAAS,IAAI;AAClC,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,UAAM,MAAM,QAAQ,UAAU,CAAC,SAAS,UAAU,MAAM,KAAK,CAAC;AAC9D,QAAI,QAAQ,GAAI;AAChB,SAAK,IAAI,MAAM,CAAC,GAAG,QAAQ,MAAM,GAAG,GAAG,GAAG,GAAG,QAAQ,MAAM,MAAM,CAAC,CAAC,GAAG,OAAO;AAAA,EAC/E;AAAA;AAAA,EAGA,SAAS,MAAc,OAAe,SAA4B;AAChE,UAAM,UAAU,KAAK,SAAS,IAAI;AAClC,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,QAAI,QAAQ,KAAK,SAAS,QAAQ,OAAQ;AAC1C,SAAK,IAAI,MAAM,CAAC,GAAG,QAAQ,MAAM,GAAG,KAAK,GAAG,GAAG,QAAQ,MAAM,QAAQ,CAAC,CAAC,GAAG,OAAO;AAAA,EACnF;AAAA,EAcA,MACE,MACA,iBACA,cACM;AAGN,QAAI;AACJ,QAAI;AACJ,QAAI,MAAM,QAAQ,eAAe,GAAG;AAClC,iBAAW;AACX,gBAAU;AAAA,IACZ,OAAO;AACL,iBAAW;AACX,gBAAU;AAAA,IACZ;AAEA,QAAI,aAAa,QAAW;AAC1B,YAAM,YAAY,KAAK,KAAK,IAAI;AAChC,YAAM,UAAU,cAAc,SAAY,kBAAkB,SAAS,IAAI;AACzE,UAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,cAAM,IAAI;AAAA,UACR,+CAA+C,IAAI;AAAA,QACrD;AAAA,MACF;AACA,iBAAW;AAAA,IACb;AACA,QAAI,SAAS,WAAW,EAAG;AAC3B,UAAM,UAAU,KAAK,SAAS,IAAI;AAClC,UAAM,MAAM,SAAS,UAAU,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC;AAC3D,UAAM,OAAO,QAAQ,KAAK,SAAS,CAAC,IAAI,UAAU,MAAM,KAAK,SAAS,MAAM;AAC5E,SAAK,IAAI,MAAM,MAAM,OAAO;AAAA,EAC9B;AAAA;AAAA,EAGA,MAAM,MAAc,SAA4B;AAC9C,UAAM,MAAM,QAAQ,KAAK,UAAU,IAAI;AACvC,SAAK,IAAI,MAAM,KAAK,OAAO;AAAA,EAC7B;AAAA;AAAA,EAGA,MAAM,QAAqB,SAA4B;AACrD,QAAI,KAAK,SAAU;AACnB,UAAM,OAAO,SAAS,EAAE,GAAG,KAAK,UAAU,GAAG,OAAO,IAAI,EAAE,GAAG,KAAK,SAAS;AAC3E,UAAM,UAAU,OAAO,KAAK,EAAE,GAAG,KAAK,QAAQ,GAAG,KAAK,CAAC;AACvD,SAAK,SAAS;AACd,SAAK,uBAAuB;AAC5B,eAAW,QAAQ,QAAS,MAAK,KAAK,OAAO,IAAI;AACjD,UAAM,eACJ,SAAS,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI;AAClE,SAAK,KAAK,QAAQ,QAAQ,CAAC,GAAG,YAAY;AAC1C,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAIA,UAAU,MAAc,UAAkC;AACxD,WAAO,KAAK,KAAK,UAAU,MAAM,QAAQ;AAAA,EAC3C;AAAA;AAAA,EAIA,QAAQ,WAAgC;AACtC,UAAM,YAAY,YACd,EAAE,GAAI,KAAK,QAAoC,GAAI,UAAsC,IACxF,KAAK;AAEV,QAAI,CAAC,aAAa,KAAK,gBAAgB,KAAK,aAAa,WAAW,KAAK,QAAQ;AAC/E,aAAO,KAAK,aAAa;AAAA,IAC3B;AAEA,UAAM,SAAS,IAAI,gBAAgB;AACnC,UAAM,OAAO,OAAO,KAAK,SAAS,EAAE,KAAK;AACzC,eAAW,OAAO,MAAM;AACtB,YAAM,QAAQ,UAAU,GAAG;AAC3B,UAAI,UAAU,UAAa,UAAU,KAAM;AAC3C,YAAM,SAAS,KAAK,aAAa,GAAG;AACpC,YAAM,MAAM,QAAQ,KAAK,UAAU,GAAG;AACtC,UAAI,QAAQ,mBAAmB,UAAU,OAAO,GAAG,EAAG;AACtD,aAAO,IAAI,KAAK,iBAAiB,KAAK,CAAC;AAAA,IACzC;AAEA,UAAM,MAAM,OAAO,SAAS;AAC5B,UAAM,MAAM,MAAM,IAAI,GAAG,KAAK;AAC9B,QAAI,CAAC,UAAW,MAAK,eAAe,EAAE,QAAQ,KAAK,QAAQ,OAAO,IAAI;AACtE,WAAO;AAAA,EACT;AAAA,EAEA,KAAK,WAAgC;AAInC,QAAI,CAAC,WAAW;AACd,YAAM,QAAQ,KAAK,QAAQ;AAE3B,YAAMA,YAAW,WAAW,OAAO,SAAS,WAAW;AACvD,YAAM,SAAS,KAAK;AACpB,UAAI,UAAU,OAAO,WAAW,KAAK,UAAU,OAAO,SAAS,QAAW;AAExE,cAAM,WAAWA,YAAW;AAC5B,YAAI,OAAO,SAAS,SAAU,QAAO,OAAO;AAAA,MAC9C;AACA,YAAM,MAAMA,YAAW;AACvB,UAAI,UAAU,OAAO,WAAW,KAAK,OAAQ,QAAO,OAAO;AAC3D,aAAO;AAAA,IACT;AACA,UAAM,WAAW,WAAW,OAAO,SAAS,WAAW;AACvD,WAAO,WAAW,KAAK,QAAQ,SAAS;AAAA,EAC1C;AAAA;AAAA,EAIA,UAAgB;AACd,QAAI,KAAK,SAAU;AACnB,SAAK,WAAW;AAChB,SAAK,qBAAqB;AAC1B,SAAK,qBAAqB;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,eAA4C;AAC1C,UAAM,YAAiE,CAAC;AACxE,eAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,KAAK,IAAI,GAAG;AACpD,UAAI,YAAY,IAAI,GAAG;AACrB,kBAAU,IAAI,IAAI;AAClB;AAAA,MACF;AAEA,UAAK,MAAc,MAAM;AACvB,kBAAU,IAAI,IAAI;AAClB;AAAA,MACF;AACA,UAAI,iBAAiB,IAAI,GAAG;AAC1B,kBAAU,IAAI,IAAI;AAClB;AAAA,MACF;AACA,gBAAU,IAAI,IAAI;AAAA,IACpB;AAEA,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,oBAAc,gBAAgB,KAAK,MAAM;AACzC,sBAAgB,gBAAgB,KAAK,QAAQ;AAC7C,yBAAmB,KAAK,cAAc,gBAAgB,KAAK,WAAW,IAAI;AAAA,IAC5E,SAAS,KAAK;AACZ,YAAM,IAAI;AAAA,QACR,yJAEqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACvE;AAAA,IACF;AAEA,WAAO,OAAO,OAAO;AAAA,MACnB,QAAQ;AAAA,MACR,UAAU;AAAA,MACV,kBAAkB,OAAO,OAAO,EAAE,GAAG,KAAK,aAAa,CAAC;AAAA,MACxD,kBAAkB,OAAO,OAAO,SAAS;AAAA,MACzC,eAAe,OAAO,OAAO,EAAE,GAAG,KAAK,cAAc,CAAC;AAAA,MACtD,SAAS,OAAO,OAAO;AAAA,QACrB,MAAM,KAAK,QAAQ;AAAA,QACnB,YAAY,KAAK,QAAQ,eAAe;AAAA,QACxC,cAAc,KAAK,QAAQ,cAAc;AAAA,QACzC,cAAc,KAAK,QAAQ,cAAc;AAAA,QACzC,UAAU,KAAK,QAAQ,UAAU;AAAA,MACnC,CAAC;AAAA,MACD,aAAa;AAAA,IACf,CAAC;AAAA,EACH;AAAA;AAAA,EAIQ,kBAA8B;AACpC,UAAM,MAA+B,CAAC;AACtC,eAAW,CAAC,KAAK,SAAS,KAAK,OAAO,QAAQ,KAAK,IAAI,GAAG;AACxD,YAAM,MAAM,WAAW,SAAS;AAChC,UAAI,QAAQ,OAAW,KAAI,GAAG,IAAI;AAAA,IACpC;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,qBAA2B;AACjC,QAAI;AACJ,QAAI;AACF,YAAM,KAAK,QAAQ,KAAK;AAAA,IAC1B,SAAS,KAAK;AAEZ,WAAK,gBAAgB,IAAI,YAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AACrF;AAAA,IACF;AACA,QAAI,CAAC,IAAK;AAEV,eAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,GAAG,GAAG;AACjD,YAAM,YAAY,KAAK,KAAK,GAAG;AAC/B,UAAI,CAAC,UAAW;AAChB,YAAM,SAAS,WAAW,WAAW,QAAQ;AAC7C,UAAI,OAAO,MAAM,OAAO,UAAU,QAAW;AAC3C;AAAC,QAAC,KAAK,OAAmC,GAAG,IAAI,OAAO;AAAA,MAC1D,WAAW,OAAO,QAAQ;AACxB,aAAK,gBAAgB,IAAI,KAAK,OAAO,MAAM;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,iBAAiB,SAAmB,SAA4B;AACtE,UAAM,WAAoC,CAAC;AAC3C,eAAW,QAAQ,SAAS;AAC1B,YAAM,QAAS,KAAK,OAAmC,IAAI;AAC3D,YAAM,SAAS,KAAK,aAAa,IAAI;AACrC,YAAM,MAAM,QAAQ,KAAK,UAAU,IAAI;AACvC,UAAI,QAAQ,mBAAmB,UAAU,OAAO,GAAG,GAAG;AAEpD,iBAAS,IAAI,IAAI;AAAA,MACnB,OAAO;AACL,iBAAS,IAAI,IAAI;AAAA,MACnB;AAAA,IACF;AAIA,UAAM,eACJ,SAAS,YAAY,SAAY,EAAE,SAAS,QAAQ,QAAQ,IAAI;AAElE,SAAK,cAAc,EAAE,GAAG,KAAK,OAAO;AACpC,QAAI;AAKF,YAAM,SAAS,KAAK,QAAQ,MAAM,UAAwB,SAAS,YAAY;AAC/E,UAAI,UAAU,OAAQ,OAAyB,SAAS,YAAY;AAClE;AAAC,QAAC,OAAyB,MAAM,MAAM,MAAS;AAAA,MAClD;AAAA,IACF,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEQ,iBAAiB,KAAuB;AAC9C,QAAI,KAAK,SAAU;AAEnB,QAAI,KAAK,eAAe,UAAU,KAAK,KAAK,WAAW,EAAG;AAE1D,UAAM,UAAoB,CAAC;AAC3B,QAAI,OAAO,KAAK;AAChB,eAAW,CAAC,KAAK,QAAQ,KAAK,OAAO,QAAQ,GAAG,GAAG;AACjD,YAAM,YAAY,KAAK,KAAK,GAAG;AAC/B,UAAI,CAAC,UAAW;AAChB,YAAM,SAAS,WAAW,WAAW,QAAQ;AAC7C,YAAM,WAAW,OAAO,KAAK,OAAO,QAAQ,QAAQ,KAAK,UAAU,GAAG;AACtE,YAAM,WAAW,QAAQ,MAAM,GAAG;AAClC,UAAI,UAAU,UAAU,QAAQ,EAAG;AACnC,aAAO,QAAQ,MAAM,KAAK,QAAQ;AAClC,cAAQ,KAAK,GAAG;AAChB,UAAI,CAAC,OAAO,MAAM,OAAO,QAAQ;AAC/B,aAAK,gBAAgB,IAAI,KAAK,OAAO,MAAM;AAAA,MAC7C,OAAO;AACL,aAAK,gBAAgB,OAAO,GAAG;AAAA,MACjC;AAAA,IACF;AACA,QAAI,QAAQ,WAAW,EAAG;AAC1B,SAAK,SAAS;AACd,SAAK,uBAAuB;AAC5B,eAAW,QAAQ,QAAS,MAAK,KAAK,OAAO,IAAI;AAAA,EACnD;AAAA,EAEQ,yBAA+B;AACrC,SAAK,eAAe;AAAA,EACtB;AACF;;;ACjfA,IAAM,QAAQ,oBAAI,QAA+C;AACjE,IAAM,YAAY,oBAAI,IAA8B;AAQ7C,SAAS,QAAW,KAA0C;AACnE,sBAAoB,GAAG;AACvB,MAAI,QAAQ,MAAM,IAAI,GAAG;AACzB,MAAI,CAAC,OAAO;AACV,YAAQ;AAAA,MACN,OAAO,IAAI,YAAe,GAAG;AAAA,MAC7B,UAAU;AAAA,MACV,gBAAgB;AAAA,IAClB;AACA,UAAM,IAAI,KAAK,KAA4B;AAAA,EAC7C,OAAO;AAEL,UAAM,iBAAiB;AAAA,EACzB;AACA,QAAM;AACN,SAAO,MAAM;AACf;AAQO,SAAS,QAAW,KAAgC;AACzD,QAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,MAAI,CAAC,MAAO;AACZ,QAAM;AACN,MAAI,MAAM,WAAW,EAAG;AAExB,QAAM,iBAAiB;AACvB,iBAAe,MAAM;AACnB,QAAI,CAAC,MAAM,eAAgB;AAC3B,UAAM,iBAAiB;AACvB,oBAAgB,KAAK,KAAK;AAAA,EAC5B,CAAC;AACH;AAEA,SAAS,gBAAmB,KAA0B,OAA4B;AAChF,QAAM,MAAM,QAAQ;AACpB,QAAM,OAAO,GAAG;AAChB,MAAI,IAAI,SAAS,UAAa,UAAU,IAAI,IAAI,IAAI,MAAM,KAAK;AAC7D,cAAU,OAAO,IAAI,IAAI;AAAA,EAC3B;AACF;AAUO,SAAS,eAAkB,KAA0C;AAC1E,MAAI,QAAQ,MAAM,IAAI,GAAG;AACzB,MAAI,CAAC,OAAO;AACV,YAAQ;AAAA,MACN,OAAO,IAAI,YAAe,GAAG;AAAA,MAC7B,UAAU;AAAA,MACV,gBAAgB;AAAA,IAClB;AACA,UAAM,IAAI,KAAK,KAA4B;AAAA,EAC7C;AACA,SAAO,MAAM;AACf;AAGO,SAAS,mBAAsB,KAAgC;AACpE,QAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,MAAI,CAAC,OAAO;AAIV,QAAI,IAAI,SAAS,UAAa,UAAU,IAAI,IAAI,IAAI,MAAM,KAAK;AAC7D,gBAAU,OAAO,IAAI,IAAI;AAAA,IAC3B;AACA;AAAA,EACF;AACA,kBAAgB,KAAK,KAAK;AAC5B;AAEA,SAAS,oBAAoB,KAA6B;AACxD,MAAI,IAAI,SAAS,OAAW;AAC5B,QAAM,WAAW,UAAU,IAAI,IAAI,IAAI;AACvC,MAAI,aAAa,UAAa,aAAa,KAAK;AAC9C;AAAA,MACE,8BAA8B,IAAI,IAAI;AAAA,IACxC;AACA;AAAA,EACF;AACA,YAAU,IAAI,IAAI,MAAM,GAAG;AAC7B;","names":["pathname"]}
|