@victorylabs/params 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/chunk-43PUAYQP.js +573 -0
- package/dist/chunk-43PUAYQP.js.map +1 -0
- package/dist/chunk-4T4THPFW.js +100 -0
- package/dist/chunk-4T4THPFW.js.map +1 -0
- package/dist/chunk-5NSLHAHG.js +26 -0
- package/dist/chunk-5NSLHAHG.js.map +1 -0
- package/dist/chunk-NHCH2WKC.js +96 -0
- package/dist/chunk-NHCH2WKC.js.map +1 -0
- package/dist/chunk-NUO3GOXV.js +72 -0
- package/dist/chunk-NUO3GOXV.js.map +1 -0
- package/dist/devtools.cjs +41 -0
- package/dist/devtools.cjs.map +1 -0
- package/dist/devtools.d.cts +45 -0
- package/dist/devtools.d.ts +45 -0
- package/dist/devtools.js +16 -0
- package/dist/devtools.js.map +1 -0
- package/dist/index.cjs +777 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +133 -0
- package/dist/index.d.ts +133 -0
- package/dist/index.js +83 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/forms-reverse.cjs +777 -0
- package/dist/integrations/forms-reverse.cjs.map +1 -0
- package/dist/integrations/forms-reverse.d.cts +32 -0
- package/dist/integrations/forms-reverse.d.ts +32 -0
- package/dist/integrations/forms-reverse.js +73 -0
- package/dist/integrations/forms-reverse.js.map +1 -0
- package/dist/integrations/forms.cjs +771 -0
- package/dist/integrations/forms.cjs.map +1 -0
- package/dist/integrations/forms.d.cts +25 -0
- package/dist/integrations/forms.d.ts +25 -0
- package/dist/integrations/forms.js +65 -0
- package/dist/integrations/forms.js.map +1 -0
- package/dist/params-store-Cgbtn53j.d.cts +115 -0
- package/dist/params-store-CguA9-yr.d.ts +115 -0
- package/dist/react.cjs +910 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +75 -0
- package/dist/react.d.ts +75 -0
- package/dist/react.js +202 -0
- package/dist/react.js.map +1 -0
- package/dist/snapshot.cjs +75 -0
- package/dist/snapshot.cjs.map +1 -0
- package/dist/snapshot.d.cts +42 -0
- package/dist/snapshot.d.ts +42 -0
- package/dist/snapshot.js +42 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/storage/compose.cjs +196 -0
- package/dist/storage/compose.cjs.map +1 -0
- package/dist/storage/compose.d.cts +35 -0
- package/dist/storage/compose.d.ts +35 -0
- package/dist/storage/compose.js +123 -0
- package/dist/storage/compose.js.map +1 -0
- package/dist/storage/cookie.cjs +136 -0
- package/dist/storage/cookie.cjs.map +1 -0
- package/dist/storage/cookie.d.cts +57 -0
- package/dist/storage/cookie.d.ts +57 -0
- package/dist/storage/cookie.js +111 -0
- package/dist/storage/cookie.js.map +1 -0
- package/dist/storage/idb.cjs +144 -0
- package/dist/storage/idb.cjs.map +1 -0
- package/dist/storage/idb.d.cts +31 -0
- package/dist/storage/idb.d.ts +31 -0
- package/dist/storage/idb.js +119 -0
- package/dist/storage/idb.js.map +1 -0
- package/dist/storage/local.cjs +121 -0
- package/dist/storage/local.cjs.map +1 -0
- package/dist/storage/local.d.cts +23 -0
- package/dist/storage/local.d.ts +23 -0
- package/dist/storage/local.js +9 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/storage/server.cjs +158 -0
- package/dist/storage/server.cjs.map +1 -0
- package/dist/storage/server.d.cts +57 -0
- package/dist/storage/server.d.ts +57 -0
- package/dist/storage/server.js +133 -0
- package/dist/storage/server.js.map +1 -0
- package/dist/storage/session.cjs +123 -0
- package/dist/storage/session.cjs.map +1 -0
- package/dist/storage/session.d.cts +14 -0
- package/dist/storage/session.d.ts +14 -0
- package/dist/storage/session.js +12 -0
- package/dist/storage/session.js.map +1 -0
- package/dist/storage/url.cjs +132 -0
- package/dist/storage/url.cjs.map +1 -0
- package/dist/storage/url.d.cts +37 -0
- package/dist/storage/url.d.ts +37 -0
- package/dist/storage/url.js +100 -0
- package/dist/storage/url.js.map +1 -0
- package/dist/storage-DBLIRR-4.d.cts +59 -0
- package/dist/storage-DBLIRR-4.d.ts +59 -0
- package/dist/types-BSWKH-jw.d.cts +68 -0
- package/dist/types-BUmNpSyP.d.ts +68 -0
- package/package.json +114 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// src/storage/server/index.ts
|
|
2
|
+
var RETRY_CAP = 10;
|
|
3
|
+
var DEFAULT_RETRY = 3;
|
|
4
|
+
var MAX_BACKOFF_MS = 5e3;
|
|
5
|
+
function serverStorage(opts) {
|
|
6
|
+
const name = opts.name ?? "serverStorage";
|
|
7
|
+
const retryDelay = opts.retryDelay ?? defaultBackoff;
|
|
8
|
+
let cache;
|
|
9
|
+
let lastFetchAt = 0;
|
|
10
|
+
const controllers = /* @__PURE__ */ new Set();
|
|
11
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
12
|
+
let revalidateTimer;
|
|
13
|
+
const cancelInFlight = () => {
|
|
14
|
+
for (const c of controllers) c.abort();
|
|
15
|
+
controllers.clear();
|
|
16
|
+
if (revalidateTimer !== void 0) {
|
|
17
|
+
clearTimeout(revalidateTimer);
|
|
18
|
+
revalidateTimer = void 0;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
const performRead = async () => {
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
controllers.add(controller);
|
|
24
|
+
try {
|
|
25
|
+
const values = await runWithRetry(
|
|
26
|
+
(signal) => opts.read(signal),
|
|
27
|
+
controller.signal,
|
|
28
|
+
opts.retry ?? DEFAULT_RETRY,
|
|
29
|
+
retryDelay
|
|
30
|
+
);
|
|
31
|
+
if (controller.signal.aborted) return void 0;
|
|
32
|
+
if (values !== void 0) {
|
|
33
|
+
cache = values;
|
|
34
|
+
lastFetchAt = Date.now();
|
|
35
|
+
for (const sub of subscribers) sub(values);
|
|
36
|
+
}
|
|
37
|
+
return values ?? void 0;
|
|
38
|
+
} finally {
|
|
39
|
+
controllers.delete(controller);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const scheduleRevalidate = (delayMs) => {
|
|
43
|
+
if (revalidateTimer !== void 0) return;
|
|
44
|
+
revalidateTimer = setTimeout(() => {
|
|
45
|
+
revalidateTimer = void 0;
|
|
46
|
+
void performRead();
|
|
47
|
+
}, delayMs);
|
|
48
|
+
};
|
|
49
|
+
return {
|
|
50
|
+
name,
|
|
51
|
+
clientOnly: false,
|
|
52
|
+
// Sync read returns the cached snapshot. Returns undefined until the
|
|
53
|
+
// first successful readAsync — the engine then falls back to defaults.
|
|
54
|
+
read: () => cache,
|
|
55
|
+
// Async read with retry + cancellation + optional SWR.
|
|
56
|
+
readAsync: async () => {
|
|
57
|
+
const staleTime = opts.staleTime ?? 0;
|
|
58
|
+
const elapsed = Date.now() - lastFetchAt;
|
|
59
|
+
if (staleTime > 0 && cache !== void 0 && elapsed < staleTime) {
|
|
60
|
+
return cache;
|
|
61
|
+
}
|
|
62
|
+
if (staleTime > 0 && cache !== void 0 && elapsed >= staleTime) {
|
|
63
|
+
scheduleRevalidate(0);
|
|
64
|
+
return cache;
|
|
65
|
+
}
|
|
66
|
+
return performRead();
|
|
67
|
+
},
|
|
68
|
+
write: async (values) => {
|
|
69
|
+
await runWithRetry(
|
|
70
|
+
() => opts.write(values),
|
|
71
|
+
// Writes are committal — no engine-driven cancellation.
|
|
72
|
+
new AbortController().signal,
|
|
73
|
+
opts.retry ?? DEFAULT_RETRY,
|
|
74
|
+
retryDelay
|
|
75
|
+
);
|
|
76
|
+
cache = { ...cache, ...values };
|
|
77
|
+
},
|
|
78
|
+
subscribe: (callback) => {
|
|
79
|
+
subscribers.add(callback);
|
|
80
|
+
return () => {
|
|
81
|
+
subscribers.delete(callback);
|
|
82
|
+
if (subscribers.size === 0) cancelInFlight();
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
clear: async () => {
|
|
86
|
+
cache = void 0;
|
|
87
|
+
lastFetchAt = 0;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function runWithRetry(op, signal, retry, retryDelay) {
|
|
92
|
+
let attempt = 0;
|
|
93
|
+
let lastError;
|
|
94
|
+
while (attempt <= RETRY_CAP) {
|
|
95
|
+
if (signal.aborted) throw new DOMException("aborted", "AbortError");
|
|
96
|
+
try {
|
|
97
|
+
return await op(signal);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
lastError = err;
|
|
100
|
+
if (signal.aborted) throw err;
|
|
101
|
+
const shouldRetry = typeof retry === "number" ? attempt < retry : retry(attempt, err) && attempt < RETRY_CAP;
|
|
102
|
+
if (!shouldRetry) throw err;
|
|
103
|
+
const delay = Math.min(retryDelay(attempt), MAX_BACKOFF_MS);
|
|
104
|
+
await sleep(delay, signal);
|
|
105
|
+
attempt++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
throw lastError;
|
|
109
|
+
}
|
|
110
|
+
function defaultBackoff(attempt) {
|
|
111
|
+
return Math.min(100 * 2 ** attempt, MAX_BACKOFF_MS);
|
|
112
|
+
}
|
|
113
|
+
function sleep(ms, signal) {
|
|
114
|
+
return new Promise((resolve, reject) => {
|
|
115
|
+
if (signal.aborted) {
|
|
116
|
+
reject(new DOMException("aborted", "AbortError"));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const timer = setTimeout(() => {
|
|
120
|
+
signal.removeEventListener("abort", onAbort);
|
|
121
|
+
resolve();
|
|
122
|
+
}, ms);
|
|
123
|
+
const onAbort = () => {
|
|
124
|
+
clearTimeout(timer);
|
|
125
|
+
reject(new DOMException("aborted", "AbortError"));
|
|
126
|
+
};
|
|
127
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export {
|
|
131
|
+
serverStorage
|
|
132
|
+
};
|
|
133
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/storage/server/index.ts"],"sourcesContent":["import type { ParamsStorage } from '../../storage'\n\nexport interface ServerStorageOptions<T> {\n /**\n * Async fetch; returns the latest server values or undefined. Closure-bound\n * at factory time. Receives an `AbortSignal` so the consumer's HTTP client\n * can short-circuit when the store is disposed mid-flight.\n */\n read: (signal: AbortSignal) => Promise<Partial<T> | undefined>\n\n /**\n * Async write; throws on failure. The engine catches the throw and silently\n * falls back to in-memory state — the standard storage error contract.\n */\n write: (values: Partial<T>) => Promise<void>\n\n /**\n * Stale-while-revalidate window in ms. When > 0, `readAsync` returns the\n * cached snapshot immediately and revalidates in the background after this\n * many ms have elapsed since the last successful read.\n *\n * Default: 0 (no SWR — every readAsync hits the wire).\n */\n staleTime?: number\n\n /**\n * Retry policy. Number of retries on read/write failure, OR a predicate\n * receiving `(attempt, error)` returning `true` to retry. Default: 3.\n * Capped at 10 internally regardless of predicate behavior.\n */\n retry?: number | ((attempt: number, error: unknown) => boolean)\n\n /**\n * Backoff delay (ms) for the Nth retry (0-indexed: attempt 0 = first retry).\n * Default: exponential `100 * 2^attempt`, capped at 5000.\n */\n retryDelay?: (attempt: number) => number\n\n /** Diagnostics name. Default: 'serverStorage'. */\n name?: string\n}\n\nconst RETRY_CAP = 10\nconst DEFAULT_RETRY = 3\nconst MAX_BACKOFF_MS = 5000\n\n/**\n * Server-state backed storage. Pair an async `read`/`write` adapter (your\n * HTTP client of choice) with built-in retry, AbortController cancellation,\n * and optional stale-while-revalidate semantics.\n *\n * Use cases:\n * - User-account-tied app config (theme + locale stored on the server).\n * - Saved filter views, pinned dashboards, etc.\n * - Anything that should round-trip through your API on every change.\n *\n * For high-frequency state (filters, search), prefer `urlStorage`. Server\n * storage shines when the values must persist across devices.\n *\n * Cancellation: when the params store disposes, the subscribe cleanup runs\n * and aborts any in-flight `readAsync` via the signal threaded into the\n * consumer's `read()` adapter. Wire the signal through your fetch call to\n * get full short-circuit behavior.\n */\nexport function serverStorage<T>(opts: ServerStorageOptions<T>): ParamsStorage<T> {\n const name = opts.name ?? 'serverStorage'\n const retryDelay = opts.retryDelay ?? defaultBackoff\n let cache: Partial<T> | undefined\n let lastFetchAt = 0\n const controllers = new Set<AbortController>()\n const subscribers = new Set<(values: Partial<T>) => void>()\n let revalidateTimer: ReturnType<typeof setTimeout> | undefined\n\n const cancelInFlight = (): void => {\n for (const c of controllers) c.abort()\n controllers.clear()\n if (revalidateTimer !== undefined) {\n clearTimeout(revalidateTimer)\n revalidateTimer = undefined\n }\n }\n\n const performRead = async (): Promise<Partial<T> | undefined> => {\n const controller = new AbortController()\n controllers.add(controller)\n try {\n const values = await runWithRetry(\n (signal) => opts.read(signal),\n controller.signal,\n opts.retry ?? DEFAULT_RETRY,\n retryDelay,\n )\n if (controller.signal.aborted) return undefined\n if (values !== undefined) {\n cache = values\n lastFetchAt = Date.now()\n for (const sub of subscribers) sub(values)\n }\n return values ?? undefined\n } finally {\n controllers.delete(controller)\n }\n }\n\n const scheduleRevalidate = (delayMs: number): void => {\n if (revalidateTimer !== undefined) return\n revalidateTimer = setTimeout(() => {\n revalidateTimer = undefined\n void performRead()\n }, delayMs)\n }\n\n return {\n name,\n clientOnly: false,\n\n // Sync read returns the cached snapshot. Returns undefined until the\n // first successful readAsync — the engine then falls back to defaults.\n read: () => cache,\n\n // Async read with retry + cancellation + optional SWR.\n readAsync: async () => {\n const staleTime = opts.staleTime ?? 0\n const elapsed = Date.now() - lastFetchAt\n // SWR fast path: cache is fresh, return it without hitting the wire.\n if (staleTime > 0 && cache !== undefined && elapsed < staleTime) {\n return cache\n }\n // SWR background path: cache is stale but present — return it,\n // schedule a background revalidation.\n if (staleTime > 0 && cache !== undefined && elapsed >= staleTime) {\n scheduleRevalidate(0)\n return cache\n }\n // Cold path: no cache yet, must wait for the read.\n return performRead()\n },\n\n write: async (values) => {\n await runWithRetry(\n () => opts.write(values),\n // Writes are committal — no engine-driven cancellation.\n new AbortController().signal,\n opts.retry ?? DEFAULT_RETRY,\n retryDelay,\n )\n // Optimistic cache update so subsequent sync reads see the new values\n // without waiting for a server round-trip.\n cache = { ...cache, ...values }\n },\n\n subscribe: (callback) => {\n subscribers.add(callback)\n return () => {\n subscribers.delete(callback)\n // When the LAST subscriber unsubscribes (typical: store disposes),\n // abort in-flight reads + cancel any pending revalidation.\n if (subscribers.size === 0) cancelInFlight()\n }\n },\n\n clear: async () => {\n // Reset cache; consumers can choose to also call write({}) to wipe\n // the server state — that's a higher-level decision.\n cache = undefined\n lastFetchAt = 0\n },\n }\n}\n\n/**\n * Run an async operation with retry + abort. The signal short-circuits both\n * the in-flight call and the inter-attempt backoff sleeps.\n */\nasync function runWithRetry<R>(\n op: (signal: AbortSignal) => Promise<R>,\n signal: AbortSignal,\n retry: number | ((attempt: number, error: unknown) => boolean),\n retryDelay: (attempt: number) => number,\n): Promise<R> {\n let attempt = 0\n let lastError: unknown\n while (attempt <= RETRY_CAP) {\n if (signal.aborted) throw new DOMException('aborted', 'AbortError')\n try {\n return await op(signal)\n } catch (err) {\n lastError = err\n if (signal.aborted) throw err\n const shouldRetry =\n typeof retry === 'number' ? attempt < retry : retry(attempt, err) && attempt < RETRY_CAP\n if (!shouldRetry) throw err\n const delay = Math.min(retryDelay(attempt), MAX_BACKOFF_MS)\n await sleep(delay, signal)\n attempt++\n }\n }\n throw lastError\n}\n\nfunction defaultBackoff(attempt: number): number {\n return Math.min(100 * 2 ** attempt, MAX_BACKOFF_MS)\n}\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal.aborted) {\n reject(new DOMException('aborted', 'AbortError'))\n return\n }\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n const onAbort = (): void => {\n clearTimeout(timer)\n reject(new DOMException('aborted', 'AbortError'))\n }\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/storage/session/index.ts
|
|
21
|
+
var session_exports = {};
|
|
22
|
+
__export(session_exports, {
|
|
23
|
+
sessionStorage: () => sessionStorage
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(session_exports);
|
|
26
|
+
|
|
27
|
+
// src/storage/local/index.ts
|
|
28
|
+
var isClient = typeof window !== "undefined";
|
|
29
|
+
function getStore(kind) {
|
|
30
|
+
if (!isClient) return void 0;
|
|
31
|
+
try {
|
|
32
|
+
return kind === "localStorage" ? window.localStorage : window.sessionStorage;
|
|
33
|
+
} catch {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function browserStorage(kind, options) {
|
|
38
|
+
const serialize = options.serialize ?? JSON.stringify;
|
|
39
|
+
const deserialize = options.deserialize ?? JSON.parse;
|
|
40
|
+
const readImpl = () => {
|
|
41
|
+
const store = getStore(kind);
|
|
42
|
+
if (!store) return void 0;
|
|
43
|
+
try {
|
|
44
|
+
const raw = store.getItem(options.key);
|
|
45
|
+
if (raw === null) return void 0;
|
|
46
|
+
const parsed = deserialize(raw);
|
|
47
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
48
|
+
return void 0;
|
|
49
|
+
}
|
|
50
|
+
return parsed;
|
|
51
|
+
} catch {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
name: kind === "localStorage" ? "localStorage" : "sessionStorage",
|
|
57
|
+
clientOnly: true,
|
|
58
|
+
read: readImpl,
|
|
59
|
+
write: (values) => {
|
|
60
|
+
const store = getStore(kind);
|
|
61
|
+
if (!store) return;
|
|
62
|
+
try {
|
|
63
|
+
const existing = readImpl() ?? {};
|
|
64
|
+
const merged = { ...existing };
|
|
65
|
+
for (const [path, value] of Object.entries(values)) {
|
|
66
|
+
if (value === void 0) {
|
|
67
|
+
delete merged[path];
|
|
68
|
+
} else {
|
|
69
|
+
merged[path] = value;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (Object.keys(merged).length === 0) {
|
|
73
|
+
store.removeItem(options.key);
|
|
74
|
+
} else {
|
|
75
|
+
store.setItem(options.key, serialize(merged));
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
clear: (paths) => {
|
|
81
|
+
const store = getStore(kind);
|
|
82
|
+
if (!store) return;
|
|
83
|
+
try {
|
|
84
|
+
if (paths.length === 0) {
|
|
85
|
+
store.removeItem(options.key);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const existing = readImpl();
|
|
89
|
+
if (!existing) return;
|
|
90
|
+
const next = { ...existing };
|
|
91
|
+
for (const path of paths) delete next[path];
|
|
92
|
+
if (Object.keys(next).length === 0) {
|
|
93
|
+
store.removeItem(options.key);
|
|
94
|
+
} else {
|
|
95
|
+
store.setItem(options.key, serialize(next));
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
subscribe: (callback) => {
|
|
101
|
+
if (!isClient) return () => void 0;
|
|
102
|
+
const targetStore = getStore(kind);
|
|
103
|
+
const handler = (event) => {
|
|
104
|
+
if (event.key !== options.key && event.key !== null) return;
|
|
105
|
+
if (targetStore && event.storageArea !== targetStore) return;
|
|
106
|
+
const values = readImpl();
|
|
107
|
+
if (values) callback(values);
|
|
108
|
+
};
|
|
109
|
+
window.addEventListener("storage", handler);
|
|
110
|
+
return () => window.removeEventListener("storage", handler);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/storage/session/index.ts
|
|
116
|
+
function sessionStorage(options) {
|
|
117
|
+
return browserStorage("sessionStorage", options);
|
|
118
|
+
}
|
|
119
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
120
|
+
0 && (module.exports = {
|
|
121
|
+
sessionStorage
|
|
122
|
+
});
|
|
123
|
+
//# sourceMappingURL=session.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/storage/session/index.ts","../../src/storage/local/index.ts"],"sourcesContent":["import type { ParamsStorage } from '../../storage'\nimport type { LocalStorageOptions } from '../local'\nimport { browserStorage } from '../local'\n\nexport type { LocalStorageOptions as SessionStorageOptions } from '../local'\n\n/**\n * `sessionStorage`-backed storage. Same shape as `localStorage` but scoped\n * to the current tab/session — values clear when the tab closes.\n *\n * Cross-tab sync works through `storage` events (browsers fire them for\n * sessionStorage updates within the same origin), but the data itself is\n * tab-scoped — opening the URL in a new tab starts fresh.\n */\nexport function sessionStorage<T = Record<string, unknown>>(\n options: LocalStorageOptions,\n): ParamsStorage<T> {\n return browserStorage<T>('sessionStorage', options)\n}\n","import type { ParamsStorage } from '../../storage'\n\nexport interface LocalStorageOptions {\n /** Storage key the backend owns. */\n readonly key: string\n /** Whole-blob serializer. Default: `JSON.stringify`. */\n readonly serialize?: (values: unknown) => string\n /** Whole-blob deserializer. Default: `JSON.parse`. */\n readonly deserialize?: (raw: string) => unknown\n}\n\nconst isClient = typeof window !== 'undefined'\n\n/**\n * `localStorage`-backed storage. Persists managed paths under a single\n * JSON-encoded key. Cross-tab sync via `storage` events.\n *\n * Limitations:\n * - Same-tab `localStorage.setItem(key, ...)` calls from outside the lib\n * are not observed (`storage` event doesn't fire for same-tab writes).\n * - Storage failures (quota, private mode) silently fall back to in-memory.\n */\nexport function localStorage<T = Record<string, unknown>>(\n options: LocalStorageOptions,\n): ParamsStorage<T> {\n return browserStorage<T>('localStorage', options)\n}\n\nfunction getStore(kind: 'localStorage' | 'sessionStorage'): Storage | undefined {\n if (!isClient) return undefined\n try {\n return kind === 'localStorage' ? window.localStorage : window.sessionStorage\n } catch {\n // Some browsers throw accessing storage in private mode\n return undefined\n }\n}\n\nexport function browserStorage<T>(\n kind: 'localStorage' | 'sessionStorage',\n options: LocalStorageOptions,\n): ParamsStorage<T> {\n const serialize = options.serialize ?? JSON.stringify\n const deserialize = options.deserialize ?? JSON.parse\n\n const readImpl = (): Partial<T> | undefined => {\n const store = getStore(kind)\n if (!store) return undefined\n try {\n const raw = store.getItem(options.key)\n if (raw === null) return undefined\n const parsed = deserialize(raw)\n if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n return undefined\n }\n return parsed as Partial<T>\n } catch {\n return undefined\n }\n }\n\n return {\n name: kind === 'localStorage' ? 'localStorage' : 'sessionStorage',\n clientOnly: true,\n read: readImpl,\n write: (values) => {\n const store = getStore(kind)\n if (!store) return\n try {\n const existing = readImpl() ?? ({} as Partial<T>)\n const merged: Record<string, unknown> = { ...existing }\n for (const [path, value] of Object.entries(values as Record<string, unknown>)) {\n if (value === undefined) {\n delete merged[path]\n } else {\n merged[path] = value\n }\n }\n if (Object.keys(merged).length === 0) {\n store.removeItem(options.key)\n } else {\n store.setItem(options.key, serialize(merged))\n }\n } catch {\n // Quota exceeded / unavailable — silent fallback\n }\n },\n clear: (paths) => {\n const store = getStore(kind)\n if (!store) return\n try {\n if (paths.length === 0) {\n store.removeItem(options.key)\n return\n }\n const existing = readImpl()\n if (!existing) return\n const next: Record<string, unknown> = { ...(existing as Record<string, unknown>) }\n for (const path of paths) delete next[path]\n if (Object.keys(next).length === 0) {\n store.removeItem(options.key)\n } else {\n store.setItem(options.key, serialize(next))\n }\n } catch {\n // Silent fallback\n }\n },\n subscribe: (callback) => {\n if (!isClient) return () => undefined\n const targetStore = getStore(kind)\n const handler = (event: StorageEvent) => {\n // Ignore unrelated keys; null === storage cleared\n if (event.key !== options.key && event.key !== null) return\n if (targetStore && event.storageArea !== targetStore) return\n const values = readImpl()\n if (values) callback(values)\n }\n window.addEventListener('storage', handler)\n return () => window.removeEventListener('storage', handler)\n },\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACWA,IAAM,WAAW,OAAO,WAAW;AAiBnC,SAAS,SAAS,MAA8D;AAC9E,MAAI,CAAC,SAAU,QAAO;AACtB,MAAI;AACF,WAAO,SAAS,iBAAiB,OAAO,eAAe,OAAO;AAAA,EAChE,QAAQ;AAEN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eACd,MACA,SACkB;AAClB,QAAM,YAAY,QAAQ,aAAa,KAAK;AAC5C,QAAM,cAAc,QAAQ,eAAe,KAAK;AAEhD,QAAM,WAAW,MAA8B;AAC7C,UAAM,QAAQ,SAAS,IAAI;AAC3B,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI;AACF,YAAM,MAAM,MAAM,QAAQ,QAAQ,GAAG;AACrC,UAAI,QAAQ,KAAM,QAAO;AACzB,YAAM,SAAS,YAAY,GAAG;AAC9B,UAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAC1E,eAAO;AAAA,MACT;AACA,aAAO;AAAA,IACT,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AAAA,IACL,MAAM,SAAS,iBAAiB,iBAAiB;AAAA,IACjD,YAAY;AAAA,IACZ,MAAM;AAAA,IACN,OAAO,CAAC,WAAW;AACjB,YAAM,QAAQ,SAAS,IAAI;AAC3B,UAAI,CAAC,MAAO;AACZ,UAAI;AACF,cAAM,WAAW,SAAS,KAAM,CAAC;AACjC,cAAM,SAAkC,EAAE,GAAG,SAAS;AACtD,mBAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,MAAiC,GAAG;AAC7E,cAAI,UAAU,QAAW;AACvB,mBAAO,OAAO,IAAI;AAAA,UACpB,OAAO;AACL,mBAAO,IAAI,IAAI;AAAA,UACjB;AAAA,QACF;AACA,YAAI,OAAO,KAAK,MAAM,EAAE,WAAW,GAAG;AACpC,gBAAM,WAAW,QAAQ,GAAG;AAAA,QAC9B,OAAO;AACL,gBAAM,QAAQ,QAAQ,KAAK,UAAU,MAAM,CAAC;AAAA,QAC9C;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IACA,OAAO,CAAC,UAAU;AAChB,YAAM,QAAQ,SAAS,IAAI;AAC3B,UAAI,CAAC,MAAO;AACZ,UAAI;AACF,YAAI,MAAM,WAAW,GAAG;AACtB,gBAAM,WAAW,QAAQ,GAAG;AAC5B;AAAA,QACF;AACA,cAAM,WAAW,SAAS;AAC1B,YAAI,CAAC,SAAU;AACf,cAAM,OAAgC,EAAE,GAAI,SAAqC;AACjF,mBAAW,QAAQ,MAAO,QAAO,KAAK,IAAI;AAC1C,YAAI,OAAO,KAAK,IAAI,EAAE,WAAW,GAAG;AAClC,gBAAM,WAAW,QAAQ,GAAG;AAAA,QAC9B,OAAO;AACL,gBAAM,QAAQ,QAAQ,KAAK,UAAU,IAAI,CAAC;AAAA,QAC5C;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,IACA,WAAW,CAAC,aAAa;AACvB,UAAI,CAAC,SAAU,QAAO,MAAM;AAC5B,YAAM,cAAc,SAAS,IAAI;AACjC,YAAM,UAAU,CAAC,UAAwB;AAEvC,YAAI,MAAM,QAAQ,QAAQ,OAAO,MAAM,QAAQ,KAAM;AACrD,YAAI,eAAe,MAAM,gBAAgB,YAAa;AACtD,cAAM,SAAS,SAAS;AACxB,YAAI,OAAQ,UAAS,MAAM;AAAA,MAC7B;AACA,aAAO,iBAAiB,WAAW,OAAO;AAC1C,aAAO,MAAM,OAAO,oBAAoB,WAAW,OAAO;AAAA,IAC5D;AAAA,EACF;AACF;;;AD5GO,SAAS,eACd,SACkB;AAClB,SAAO,eAAkB,kBAAkB,OAAO;AACpD;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { P as ParamsStorage } from '../storage-DBLIRR-4.cjs';
|
|
2
|
+
import { LocalStorageOptions } from './local.cjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `sessionStorage`-backed storage. Same shape as `localStorage` but scoped
|
|
6
|
+
* to the current tab/session — values clear when the tab closes.
|
|
7
|
+
*
|
|
8
|
+
* Cross-tab sync works through `storage` events (browsers fire them for
|
|
9
|
+
* sessionStorage updates within the same origin), but the data itself is
|
|
10
|
+
* tab-scoped — opening the URL in a new tab starts fresh.
|
|
11
|
+
*/
|
|
12
|
+
declare function sessionStorage<T = Record<string, unknown>>(options: LocalStorageOptions): ParamsStorage<T>;
|
|
13
|
+
|
|
14
|
+
export { LocalStorageOptions as SessionStorageOptions, sessionStorage };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { P as ParamsStorage } from '../storage-DBLIRR-4.js';
|
|
2
|
+
import { LocalStorageOptions } from './local.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `sessionStorage`-backed storage. Same shape as `localStorage` but scoped
|
|
6
|
+
* to the current tab/session — values clear when the tab closes.
|
|
7
|
+
*
|
|
8
|
+
* Cross-tab sync works through `storage` events (browsers fire them for
|
|
9
|
+
* sessionStorage updates within the same origin), but the data itself is
|
|
10
|
+
* tab-scoped — opening the URL in a new tab starts fresh.
|
|
11
|
+
*/
|
|
12
|
+
declare function sessionStorage<T = Record<string, unknown>>(options: LocalStorageOptions): ParamsStorage<T>;
|
|
13
|
+
|
|
14
|
+
export { LocalStorageOptions as SessionStorageOptions, sessionStorage };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
browserStorage
|
|
3
|
+
} from "../chunk-NHCH2WKC.js";
|
|
4
|
+
|
|
5
|
+
// src/storage/session/index.ts
|
|
6
|
+
function sessionStorage(options) {
|
|
7
|
+
return browserStorage("sessionStorage", options);
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
sessionStorage
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/storage/session/index.ts"],"sourcesContent":["import type { ParamsStorage } from '../../storage'\nimport type { LocalStorageOptions } from '../local'\nimport { browserStorage } from '../local'\n\nexport type { LocalStorageOptions as SessionStorageOptions } from '../local'\n\n/**\n * `sessionStorage`-backed storage. Same shape as `localStorage` but scoped\n * to the current tab/session — values clear when the tab closes.\n *\n * Cross-tab sync works through `storage` events (browsers fire them for\n * sessionStorage updates within the same origin), but the data itself is\n * tab-scoped — opening the URL in a new tab starts fresh.\n */\nexport function sessionStorage<T = Record<string, unknown>>(\n options: LocalStorageOptions,\n): ParamsStorage<T> {\n return browserStorage<T>('sessionStorage', options)\n}\n"],"mappings":";;;;;AAcO,SAAS,eACd,SACkB;AAClB,SAAO,eAAkB,kBAAkB,OAAO;AACpD;","names":[]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/storage/url/index.ts
|
|
21
|
+
var url_exports = {};
|
|
22
|
+
__export(url_exports, {
|
|
23
|
+
urlStorage: () => urlStorage
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(url_exports);
|
|
26
|
+
|
|
27
|
+
// src/schema.ts
|
|
28
|
+
function defaultSerialize(value) {
|
|
29
|
+
if (value === void 0 || value === null) return "";
|
|
30
|
+
if (typeof value === "string") return value;
|
|
31
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
32
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "";
|
|
33
|
+
return JSON.stringify(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// src/storage/url/index.ts
|
|
37
|
+
var isClient = typeof window !== "undefined";
|
|
38
|
+
function urlStorage(options = {}) {
|
|
39
|
+
const adapterStrategy = options.strategy ?? "replace";
|
|
40
|
+
const paramMap = options.paramMap ?? {};
|
|
41
|
+
const reverseMap = buildReverseMap(paramMap);
|
|
42
|
+
const serialize = options.serialize ?? {};
|
|
43
|
+
const deserialize = options.deserialize ?? {};
|
|
44
|
+
const readImpl = () => {
|
|
45
|
+
if (!isClient) return void 0;
|
|
46
|
+
try {
|
|
47
|
+
const params = new URLSearchParams(window.location.search);
|
|
48
|
+
const partial = {};
|
|
49
|
+
let hasAny = false;
|
|
50
|
+
for (const [paramKey, raw] of params.entries()) {
|
|
51
|
+
const formPath = reverseMap[paramKey] ?? paramKey;
|
|
52
|
+
const deserializer = deserialize[formPath];
|
|
53
|
+
try {
|
|
54
|
+
partial[formPath] = deserializer ? deserializer(raw) : raw;
|
|
55
|
+
hasAny = true;
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return hasAny ? partial : void 0;
|
|
60
|
+
} catch {
|
|
61
|
+
return void 0;
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const updateUrl = (params, opts) => {
|
|
65
|
+
if (!isClient) return;
|
|
66
|
+
const search = params.toString();
|
|
67
|
+
const url = search ? `${window.location.pathname}?${search}` : window.location.pathname;
|
|
68
|
+
const strategy = opts?.history ?? adapterStrategy;
|
|
69
|
+
try {
|
|
70
|
+
if (strategy === "push") {
|
|
71
|
+
window.history.pushState(null, "", url);
|
|
72
|
+
} else {
|
|
73
|
+
window.history.replaceState(null, "", url);
|
|
74
|
+
}
|
|
75
|
+
} catch {
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
name: "urlStorage",
|
|
80
|
+
clientOnly: true,
|
|
81
|
+
read: readImpl,
|
|
82
|
+
write: (values, _changed, opts) => {
|
|
83
|
+
if (!isClient) return;
|
|
84
|
+
const params = new URLSearchParams(window.location.search);
|
|
85
|
+
for (const [path, value] of Object.entries(values)) {
|
|
86
|
+
const paramKey = paramMap[path] ?? path;
|
|
87
|
+
if (value === void 0 || value === null) {
|
|
88
|
+
params.delete(paramKey);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const serializer = serialize[path];
|
|
92
|
+
const str = serializer ? serializer(value) : defaultSerialize(value);
|
|
93
|
+
params.set(paramKey, str);
|
|
94
|
+
}
|
|
95
|
+
updateUrl(params, opts);
|
|
96
|
+
},
|
|
97
|
+
clear: (paths, opts) => {
|
|
98
|
+
if (!isClient) return;
|
|
99
|
+
if (paths.length === 0) {
|
|
100
|
+
updateUrl(new URLSearchParams(), opts);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const params = new URLSearchParams(window.location.search);
|
|
104
|
+
for (const path of paths) {
|
|
105
|
+
const paramKey = paramMap[path] ?? path;
|
|
106
|
+
params.delete(paramKey);
|
|
107
|
+
}
|
|
108
|
+
updateUrl(params, opts);
|
|
109
|
+
},
|
|
110
|
+
subscribe: (callback) => {
|
|
111
|
+
if (!isClient) return () => void 0;
|
|
112
|
+
const handler = () => {
|
|
113
|
+
const values = readImpl();
|
|
114
|
+
if (values) callback(values);
|
|
115
|
+
};
|
|
116
|
+
window.addEventListener("popstate", handler);
|
|
117
|
+
return () => window.removeEventListener("popstate", handler);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
function buildReverseMap(map) {
|
|
122
|
+
const reverse = {};
|
|
123
|
+
for (const [formPath, paramKey] of Object.entries(map)) {
|
|
124
|
+
reverse[paramKey] = formPath;
|
|
125
|
+
}
|
|
126
|
+
return reverse;
|
|
127
|
+
}
|
|
128
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
129
|
+
0 && (module.exports = {
|
|
130
|
+
urlStorage
|
|
131
|
+
});
|
|
132
|
+
//# sourceMappingURL=url.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/storage/url/index.ts","../../src/schema.ts"],"sourcesContent":["import { defaultSerialize } from '../../schema'\nimport type { ParamsStorage, WriteOptions } from '../../storage'\n\nexport interface UrlStorageOptions {\n /**\n * History API strategy. `'replace'` keeps the back-stack clean (recommended\n * for filters/search). `'push'` creates a new history entry for every write.\n * Default: `'replace'`.\n */\n readonly strategy?: 'replace' | 'push'\n\n /** form path → URL search-param key. Defaults to identity. */\n readonly paramMap?: Record<string, string>\n\n /** Per-path string serializer. Default: `defaultSerialize` (same as schema). */\n readonly serialize?: Record<string, (value: unknown) => string>\n\n /** Per-path string-to-value deserializer. Default: identity (raw string). */\n readonly deserialize?: Record<string, (str: string) => unknown>\n}\n\nconst isClient = typeof window !== 'undefined'\n\n/**\n * URL search-param storage backend (native History API).\n *\n * - Hydrates from `window.location.search` on store creation.\n * - Reflects writes back to the URL via `replaceState` (default) or `pushState`.\n * - Subscribes to `popstate` so back/forward navigation flows into the store.\n *\n * `omitWhenDefault` is honored at the engine level (the store sends `undefined`\n * for paths at default; the backend deletes them from the URL).\n *\n * Limitations (v0.1):\n * - History API only — `hashchange` not supported.\n * - Cross-tab URL sync not supported (History API is per-tab).\n *\n * v0.4: per-call strategy override via `WriteOptions.history` — store callers\n * can pass `params.set(path, value, { history: 'push' })` to override the\n * adapter-level default for that single write.\n */\nexport function urlStorage<T = Record<string, unknown>>(\n options: UrlStorageOptions = {},\n): ParamsStorage<T> {\n const adapterStrategy = options.strategy ?? 'replace'\n const paramMap = options.paramMap ?? {}\n const reverseMap = buildReverseMap(paramMap)\n const serialize = options.serialize ?? {}\n const deserialize = options.deserialize ?? {}\n\n const readImpl = (): Partial<T> | undefined => {\n if (!isClient) return undefined\n try {\n const params = new URLSearchParams(window.location.search)\n const partial: Record<string, unknown> = {}\n let hasAny = false\n for (const [paramKey, raw] of params.entries()) {\n const formPath = reverseMap[paramKey] ?? paramKey\n const deserializer = deserialize[formPath]\n try {\n partial[formPath] = deserializer ? deserializer(raw) : raw\n hasAny = true\n } catch {\n // Per-path deserialize failure → skip; engine falls back to default\n }\n }\n return hasAny ? (partial as Partial<T>) : undefined\n } catch {\n return undefined\n }\n }\n\n const updateUrl = (params: URLSearchParams, opts?: WriteOptions): void => {\n if (!isClient) return\n const search = params.toString()\n const url = search ? `${window.location.pathname}?${search}` : window.location.pathname\n const strategy = opts?.history ?? adapterStrategy\n try {\n if (strategy === 'push') {\n window.history.pushState(null, '', url)\n } else {\n window.history.replaceState(null, '', url)\n }\n } catch {\n // Some browsers cap History API writes (rare) — silent fallback.\n }\n }\n\n return {\n name: 'urlStorage',\n clientOnly: true,\n read: readImpl,\n write: (values, _changed, opts) => {\n if (!isClient) return\n const params = new URLSearchParams(window.location.search)\n for (const [path, value] of Object.entries(values as Record<string, unknown>)) {\n const paramKey = paramMap[path] ?? path\n if (value === undefined || value === null) {\n params.delete(paramKey)\n continue\n }\n const serializer = serialize[path]\n const str = serializer ? serializer(value) : defaultSerialize(value)\n params.set(paramKey, str)\n }\n updateUrl(params, opts)\n },\n clear: (paths, opts) => {\n if (!isClient) return\n if (paths.length === 0) {\n updateUrl(new URLSearchParams(), opts)\n return\n }\n const params = new URLSearchParams(window.location.search)\n for (const path of paths) {\n const paramKey = paramMap[path] ?? path\n params.delete(paramKey)\n }\n updateUrl(params, opts)\n },\n subscribe: (callback) => {\n if (!isClient) return () => undefined\n const handler = () => {\n const values = readImpl()\n if (values) callback(values)\n }\n window.addEventListener('popstate', handler)\n return () => window.removeEventListener('popstate', handler)\n },\n }\n}\n\nfunction buildReverseMap(map: Record<string, string>): Record<string, string> {\n const reverse: Record<string, string> = {}\n for (const [formPath, paramKey] of Object.entries(map)) {\n reverse[paramKey] = formPath\n }\n return reverse\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec'\n\nimport type { FieldSpec, PlainFieldSpec } from './types'\n\nexport function isStandardSchema(spec: unknown): spec is StandardSchemaV1 {\n return (\n typeof spec === 'object' &&\n spec !== null &&\n '~standard' in spec &&\n typeof (spec as { '~standard'?: unknown })['~standard'] === 'object'\n )\n}\n\nexport function isPlainSpec<T>(spec: FieldSpec<T>): spec is PlainFieldSpec<T> {\n return !isStandardSchema(spec) && typeof spec === 'object' && spec !== null && 'default' in spec\n}\n\n/**\n * Resolve a field's default value.\n *\n * - Plain spec: `spec.default` (always present — required by the type).\n * - Standard Schema (Zod, Valibot, ArkType, …): parse `undefined` and use the\n * schema's `.default()` if it produced a value. Schemas without a default\n * return `undefined`.\n */\nexport function getDefault<T>(spec: FieldSpec<T>): T | undefined {\n if (isStandardSchema(spec)) {\n const result = spec['~standard'].validate(undefined)\n if (result instanceof Promise) return undefined\n if ('value' in result && !result.issues) return result.value as T\n return undefined\n }\n return (spec as PlainFieldSpec<T>).default\n}\n\n/**\n * Parse a raw storage value through the field's spec. Returns the typed value\n * on success, or `undefined` on parse failure (engine falls back to default).\n *\n * The engine records the failure reason in `ParamsStore.storageErrors` for\n * debugging; consumer code doesn't see it.\n */\nexport interface ParseResult<T> {\n ok: boolean\n value?: T\n reason?: string\n}\n\nexport function parseField<T>(spec: FieldSpec<T>, raw: unknown): ParseResult<T> {\n if (isStandardSchema(spec)) {\n const result = spec['~standard'].validate(raw)\n if (result instanceof Promise) {\n return { ok: false, reason: 'async-schema-not-supported-in-v0.1' }\n }\n if ('value' in result && !result.issues) return { ok: true, value: result.value as T }\n return { ok: false, reason: result.issues?.[0]?.message ?? 'parse-failed' }\n }\n\n const plainSpec = spec as PlainFieldSpec<T>\n if (typeof raw === 'string' && plainSpec.parse) {\n try {\n return { ok: true, value: plainSpec.parse(raw) }\n } catch (err) {\n return { ok: false, reason: err instanceof Error ? err.message : String(err) }\n }\n }\n // No custom parse: accept raw value as-is if it's a usable runtime value\n // (string, number, boolean, array, object, etc.). The schema-less plain spec\n // is mostly used for non-string-coerced flows (memory storage with raw values).\n if (raw === undefined) return { ok: false, reason: 'undefined-no-parse' }\n return { ok: true, value: raw as T }\n}\n\n/**\n * Serialize a typed value to its storage string representation. Used by URL\n * storage (every value must be a string) and other string-keyed backends.\n *\n * Plain spec uses its `serialize` if provided, else `String(value)`. Standard\n * schemas don't define an inverse — we fall back to `String(value)` (or\n * `JSON.stringify` for objects) and the consumer can override via per-field\n * `serialize` in the storage backend's options.\n */\nexport function defaultSerialize(value: unknown): string {\n if (value === undefined || value === null) return ''\n if (typeof value === 'string') return value\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') return Number.isFinite(value) ? String(value) : ''\n return JSON.stringify(value)\n}\n\n/**\n * Extract the enum values from a field spec, if any. Returns the array of\n * enum members or `undefined` if the spec doesn't expose them.\n *\n * Supports:\n * - Zod `z.enum(['a', 'b'])` — reads `_def.values: ['a', 'b']`.\n * - Zod `z.nativeEnum(MyEnum)` — reads `_def.values: <enum-object>`.\n * For numeric enums (TypeScript bidirectional `enum Color { Red = 0 }`),\n * `Object.values` returns BOTH the numbers and the reverse-mapped strings;\n * this helper detects the case and filters to numeric values only.\n *\n * Standard Schema doesn't define a uniform enum-introspection API, so libraries\n * other than Zod return `undefined` here — `cycle()` then throws and asks the\n * caller to pass options explicitly.\n */\nexport function extractEnumValues(spec: unknown): readonly unknown[] | undefined {\n // Walk Zod wrapper types (ZodDefault, ZodOptional, ZodNullable, …) to find\n // an underlying enum. `.default(...)` / `.optional()` / `.nullable()` produce\n // a wrapper whose `_def.innerType` points to the wrapped schema. Cap the walk\n // at a small depth — pathological self-referential schemas shouldn't loop.\n // biome-ignore lint/suspicious/noExplicitAny: Zod internal `_def` API\n let current: any = spec\n for (let i = 0; i < 8 && current !== null && typeof current === 'object'; i++) {\n const def = current._def\n if (!def) return undefined\n\n // z.enum(['a', 'b']): values is already a clean string array.\n if (Array.isArray(def.values)) return def.values\n\n // z.nativeEnum(MyEnum): values is the enum object. TS numeric enums are\n // BIDIRECTIONAL — Object.values returns numbers + reverse-mapped strings:\n // enum Color { Red = 0 } → Object.values(Color) = [0, 'Red'].\n // Detect numeric and filter; pure string enums are unidirectional and\n // return only strings.\n if (typeof def.values === 'object' && def.values !== null) {\n const all = Object.values(def.values)\n const hasNumber = all.some((v) => typeof v === 'number')\n return hasNumber ? all.filter((v) => typeof v === 'number') : all\n }\n\n if (def.innerType) {\n current = def.innerType\n continue\n }\n return undefined\n }\n return undefined\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { P as ParamsStorage } from '../storage-DBLIRR-4.cjs';
|
|
2
|
+
|
|
3
|
+
interface UrlStorageOptions {
|
|
4
|
+
/**
|
|
5
|
+
* History API strategy. `'replace'` keeps the back-stack clean (recommended
|
|
6
|
+
* for filters/search). `'push'` creates a new history entry for every write.
|
|
7
|
+
* Default: `'replace'`.
|
|
8
|
+
*/
|
|
9
|
+
readonly strategy?: 'replace' | 'push';
|
|
10
|
+
/** form path → URL search-param key. Defaults to identity. */
|
|
11
|
+
readonly paramMap?: Record<string, string>;
|
|
12
|
+
/** Per-path string serializer. Default: `defaultSerialize` (same as schema). */
|
|
13
|
+
readonly serialize?: Record<string, (value: unknown) => string>;
|
|
14
|
+
/** Per-path string-to-value deserializer. Default: identity (raw string). */
|
|
15
|
+
readonly deserialize?: Record<string, (str: string) => unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* URL search-param storage backend (native History API).
|
|
19
|
+
*
|
|
20
|
+
* - Hydrates from `window.location.search` on store creation.
|
|
21
|
+
* - Reflects writes back to the URL via `replaceState` (default) or `pushState`.
|
|
22
|
+
* - Subscribes to `popstate` so back/forward navigation flows into the store.
|
|
23
|
+
*
|
|
24
|
+
* `omitWhenDefault` is honored at the engine level (the store sends `undefined`
|
|
25
|
+
* for paths at default; the backend deletes them from the URL).
|
|
26
|
+
*
|
|
27
|
+
* Limitations (v0.1):
|
|
28
|
+
* - History API only — `hashchange` not supported.
|
|
29
|
+
* - Cross-tab URL sync not supported (History API is per-tab).
|
|
30
|
+
*
|
|
31
|
+
* v0.4: per-call strategy override via `WriteOptions.history` — store callers
|
|
32
|
+
* can pass `params.set(path, value, { history: 'push' })` to override the
|
|
33
|
+
* adapter-level default for that single write.
|
|
34
|
+
*/
|
|
35
|
+
declare function urlStorage<T = Record<string, unknown>>(options?: UrlStorageOptions): ParamsStorage<T>;
|
|
36
|
+
|
|
37
|
+
export { type UrlStorageOptions, urlStorage };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { P as ParamsStorage } from '../storage-DBLIRR-4.js';
|
|
2
|
+
|
|
3
|
+
interface UrlStorageOptions {
|
|
4
|
+
/**
|
|
5
|
+
* History API strategy. `'replace'` keeps the back-stack clean (recommended
|
|
6
|
+
* for filters/search). `'push'` creates a new history entry for every write.
|
|
7
|
+
* Default: `'replace'`.
|
|
8
|
+
*/
|
|
9
|
+
readonly strategy?: 'replace' | 'push';
|
|
10
|
+
/** form path → URL search-param key. Defaults to identity. */
|
|
11
|
+
readonly paramMap?: Record<string, string>;
|
|
12
|
+
/** Per-path string serializer. Default: `defaultSerialize` (same as schema). */
|
|
13
|
+
readonly serialize?: Record<string, (value: unknown) => string>;
|
|
14
|
+
/** Per-path string-to-value deserializer. Default: identity (raw string). */
|
|
15
|
+
readonly deserialize?: Record<string, (str: string) => unknown>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* URL search-param storage backend (native History API).
|
|
19
|
+
*
|
|
20
|
+
* - Hydrates from `window.location.search` on store creation.
|
|
21
|
+
* - Reflects writes back to the URL via `replaceState` (default) or `pushState`.
|
|
22
|
+
* - Subscribes to `popstate` so back/forward navigation flows into the store.
|
|
23
|
+
*
|
|
24
|
+
* `omitWhenDefault` is honored at the engine level (the store sends `undefined`
|
|
25
|
+
* for paths at default; the backend deletes them from the URL).
|
|
26
|
+
*
|
|
27
|
+
* Limitations (v0.1):
|
|
28
|
+
* - History API only — `hashchange` not supported.
|
|
29
|
+
* - Cross-tab URL sync not supported (History API is per-tab).
|
|
30
|
+
*
|
|
31
|
+
* v0.4: per-call strategy override via `WriteOptions.history` — store callers
|
|
32
|
+
* can pass `params.set(path, value, { history: 'push' })` to override the
|
|
33
|
+
* adapter-level default for that single write.
|
|
34
|
+
*/
|
|
35
|
+
declare function urlStorage<T = Record<string, unknown>>(options?: UrlStorageOptions): ParamsStorage<T>;
|
|
36
|
+
|
|
37
|
+
export { type UrlStorageOptions, urlStorage };
|