paramsync 0.0.0-stage → 1.0.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/index.cjs ADDED
@@ -0,0 +1,392 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var navigation = require('next/navigation');
5
+
6
+ // src/parsers.ts
7
+ function createParser(options) {
8
+ const parser = {
9
+ parse: (value) => {
10
+ const result = options.parse(value);
11
+ if ((result === null || result === void 0) && options.defaultValue !== void 0) {
12
+ return options.defaultValue;
13
+ }
14
+ return result;
15
+ },
16
+ serialize: options.serialize,
17
+ defaultValue: options.defaultValue,
18
+ withDefault(defaultValue) {
19
+ return {
20
+ defaultValue,
21
+ parse: (value) => {
22
+ if (value === null || value === void 0) {
23
+ return defaultValue;
24
+ }
25
+ const parsed = options.parse(value);
26
+ if (parsed === null || parsed === void 0) {
27
+ return defaultValue;
28
+ }
29
+ return parsed;
30
+ },
31
+ serialize: (value) => {
32
+ return options.serialize(value);
33
+ },
34
+ withDefault(nextDefault) {
35
+ return parser.withDefault(nextDefault);
36
+ }
37
+ };
38
+ }
39
+ };
40
+ return parser;
41
+ }
42
+ var parseAsString = createParser({
43
+ parse: (value) => value,
44
+ serialize: (value) => value !== null && value !== void 0 ? String(value) : ""
45
+ });
46
+ var parseAsInteger = createParser({
47
+ parse: (value) => {
48
+ if (value === null || value === "") return null;
49
+ const num = Number(value);
50
+ return Number.isInteger(num) ? num : null;
51
+ },
52
+ serialize: (value) => value !== null && value !== void 0 ? Math.round(value).toString() : ""
53
+ });
54
+ var parseAsFloat = createParser({
55
+ parse: (value) => {
56
+ if (value === null || value === "") return null;
57
+ const num = Number(value);
58
+ return Number.isFinite(num) ? num : null;
59
+ },
60
+ serialize: (value) => value !== null && value !== void 0 ? String(value) : ""
61
+ });
62
+ var parseAsBoolean = createParser({
63
+ parse: (value) => {
64
+ if (value === null || value === "") return null;
65
+ const normalized = value.trim().toLowerCase();
66
+ if (normalized === "true" || normalized === "1") return true;
67
+ if (normalized === "false" || normalized === "0") return false;
68
+ return null;
69
+ },
70
+ serialize: (value) => value === true ? "true" : value === false ? "false" : ""
71
+ });
72
+ function parseAsArray(itemParser, separator = ",") {
73
+ return createParser({
74
+ defaultValue: [],
75
+ parse: (value) => {
76
+ if (value === null || value === void 0 || value.trim() === "") {
77
+ return [];
78
+ }
79
+ const rawItems = value.split(separator);
80
+ const results = [];
81
+ for (const raw of rawItems) {
82
+ const parsed = itemParser.parse(raw);
83
+ if (parsed !== null && parsed !== void 0) {
84
+ results.push(parsed);
85
+ }
86
+ }
87
+ return results;
88
+ },
89
+ serialize: (value) => {
90
+ if (!Array.isArray(value) || value.length === 0) {
91
+ return "";
92
+ }
93
+ return value.map((item) => itemParser.serialize(item)).join(separator);
94
+ }
95
+ });
96
+ }
97
+ function parseAsNumberRange(defaultMin, defaultMax, separator = "-") {
98
+ const defaultRange = { min: defaultMin, max: defaultMax };
99
+ return createParser({
100
+ defaultValue: defaultRange,
101
+ parse: (value) => {
102
+ if (!value || value.trim() === "") {
103
+ return defaultRange;
104
+ }
105
+ const parts = value.split(separator);
106
+ if (parts.length !== 2) {
107
+ return defaultRange;
108
+ }
109
+ const min = Number(parts[0]);
110
+ const max = Number(parts[1]);
111
+ if (!Number.isFinite(min) || !Number.isFinite(max)) {
112
+ return defaultRange;
113
+ }
114
+ return { min, max };
115
+ },
116
+ serialize: (value) => {
117
+ if (!value || typeof value.min !== "number" || typeof value.max !== "number" || value.min === defaultMin && value.max === defaultMax) {
118
+ return "";
119
+ }
120
+ return `${value.min}${separator}${value.max}`;
121
+ }
122
+ });
123
+ }
124
+ function parseAsSort(defaultField, defaultOrder = "asc", separator = ".") {
125
+ const defaultOption = {
126
+ field: defaultField,
127
+ order: defaultOrder
128
+ };
129
+ return createParser({
130
+ defaultValue: defaultOption,
131
+ parse: (value) => {
132
+ if (!value || value.trim() === "") {
133
+ return defaultOption;
134
+ }
135
+ const [field, rawOrder] = value.split(separator);
136
+ if (!field) {
137
+ return defaultOption;
138
+ }
139
+ const order = rawOrder === "desc" || rawOrder === "asc" ? rawOrder : defaultOrder;
140
+ return {
141
+ field,
142
+ order
143
+ };
144
+ },
145
+ serialize: (value) => {
146
+ if (!value || !value.field) {
147
+ return "";
148
+ }
149
+ if (value.field === defaultField && value.order === defaultOrder) {
150
+ return "";
151
+ }
152
+ return `${value.field}${separator}${value.order}`;
153
+ }
154
+ });
155
+ }
156
+ function areValuesEqual(a, b) {
157
+ if (a === b) return true;
158
+ if (a === null || b === null || a === void 0 || b === void 0) return false;
159
+ if (typeof a !== typeof b) return false;
160
+ if (Array.isArray(a) && Array.isArray(b)) {
161
+ if (a.length !== b.length) return false;
162
+ return a.every((item, idx) => areValuesEqual(item, b[idx]));
163
+ }
164
+ if (typeof a === "object" && typeof b === "object") {
165
+ const keysA = Object.keys(a);
166
+ const keysB = Object.keys(b);
167
+ if (keysA.length !== keysB.length) return false;
168
+ return keysA.every(
169
+ (key) => areValuesEqual(
170
+ a[key],
171
+ b[key]
172
+ )
173
+ );
174
+ }
175
+ return false;
176
+ }
177
+ function isCleanValue(value, defaultValue) {
178
+ if (value === null || value === void 0) return true;
179
+ if (value === "") return true;
180
+ if (Array.isArray(value) && value.length === 0) return true;
181
+ if (defaultValue !== void 0 && areValuesEqual(value, defaultValue)) return true;
182
+ return false;
183
+ }
184
+ function useQueryState(key, parser, options) {
185
+ const router = navigation.useRouter();
186
+ const pathname = navigation.usePathname();
187
+ const searchParams = navigation.useSearchParams();
188
+ const [isPending, startTransition] = react.useTransition();
189
+ const effectiveDefault = options?.defaultValue ?? parser.defaultValue;
190
+ const debounceMs = options?.debounceMs ?? 0;
191
+ const historyMode = options?.history ?? "replace";
192
+ const shouldScroll = options?.scroll ?? false;
193
+ const rawQueryValue = searchParams ? searchParams.get(key) : null;
194
+ const parsedUrlValue = parser.parse(rawQueryValue);
195
+ const initialValue = parsedUrlValue !== null && parsedUrlValue !== void 0 ? parsedUrlValue : effectiveDefault;
196
+ const [internalValue, setInternalValue] = react.useState(initialValue);
197
+ const latestValueRef = react.useRef(internalValue);
198
+ latestValueRef.current = internalValue;
199
+ const timerRef = react.useRef(null);
200
+ const lastKnownUrlParamRef = react.useRef(rawQueryValue);
201
+ const lastCommittedParamRef = react.useRef(rawQueryValue);
202
+ react.useEffect(() => {
203
+ const currentParam = searchParams ? searchParams.get(key) : null;
204
+ if (currentParam !== lastKnownUrlParamRef.current) {
205
+ lastKnownUrlParamRef.current = currentParam;
206
+ if (currentParam !== lastCommittedParamRef.current) {
207
+ lastCommittedParamRef.current = currentParam;
208
+ const parsed = parser.parse(currentParam);
209
+ const nextVal = parsed !== null && parsed !== void 0 ? parsed : effectiveDefault;
210
+ if (timerRef.current) {
211
+ clearTimeout(timerRef.current);
212
+ timerRef.current = null;
213
+ }
214
+ setInternalValue(nextVal);
215
+ latestValueRef.current = nextVal;
216
+ }
217
+ }
218
+ }, [searchParams, key, parser, effectiveDefault]);
219
+ react.useEffect(() => {
220
+ return () => {
221
+ if (timerRef.current) {
222
+ clearTimeout(timerRef.current);
223
+ }
224
+ };
225
+ }, []);
226
+ const commitToUrl = react.useCallback(
227
+ (targetValue) => {
228
+ const currentParams = new URLSearchParams(
229
+ searchParams ? searchParams.toString() : ""
230
+ );
231
+ const serialized = parser.serialize(targetValue);
232
+ if (isCleanValue(targetValue, effectiveDefault) || serialized === "") {
233
+ currentParams.delete(key);
234
+ } else {
235
+ currentParams.set(key, serialized);
236
+ }
237
+ const queryString = currentParams.toString();
238
+ const nextUrl = queryString ? `${pathname}?${queryString}` : pathname;
239
+ lastCommittedParamRef.current = currentParams.get(key);
240
+ startTransition(() => {
241
+ if (historyMode === "push") {
242
+ router.push(nextUrl, { scroll: shouldScroll });
243
+ } else {
244
+ router.replace(nextUrl, { scroll: shouldScroll });
245
+ }
246
+ });
247
+ },
248
+ [
249
+ searchParams,
250
+ parser,
251
+ effectiveDefault,
252
+ key,
253
+ pathname,
254
+ historyMode,
255
+ shouldScroll,
256
+ router
257
+ ]
258
+ );
259
+ const setValue = react.useCallback(
260
+ (valueOrUpdater) => {
261
+ const nextValue = typeof valueOrUpdater === "function" ? valueOrUpdater(latestValueRef.current) : valueOrUpdater;
262
+ setInternalValue(nextValue);
263
+ latestValueRef.current = nextValue;
264
+ if (timerRef.current) {
265
+ clearTimeout(timerRef.current);
266
+ timerRef.current = null;
267
+ }
268
+ if (debounceMs > 0) {
269
+ timerRef.current = setTimeout(() => {
270
+ commitToUrl(nextValue);
271
+ timerRef.current = null;
272
+ }, debounceMs);
273
+ } else {
274
+ commitToUrl(nextValue);
275
+ }
276
+ },
277
+ [debounceMs, commitToUrl]
278
+ );
279
+ const reset = react.useCallback(() => {
280
+ if (timerRef.current) {
281
+ clearTimeout(timerRef.current);
282
+ timerRef.current = null;
283
+ }
284
+ const def = effectiveDefault;
285
+ setInternalValue(def);
286
+ latestValueRef.current = def;
287
+ commitToUrl(def);
288
+ }, [effectiveDefault, commitToUrl]);
289
+ return [internalValue, setValue, reset, isPending];
290
+ }
291
+ function useBatchQueryParams(options) {
292
+ const router = navigation.useRouter();
293
+ const pathname = navigation.usePathname();
294
+ const searchParams = navigation.useSearchParams();
295
+ const [isPending, startTransition] = react.useTransition();
296
+ const defaultHistory = options?.history ?? "replace";
297
+ const defaultScroll = options?.scroll ?? false;
298
+ const setBatchParams = react.useCallback(
299
+ (updates, navOptions) => {
300
+ const currentParams = new URLSearchParams(
301
+ searchParams ? searchParams.toString() : ""
302
+ );
303
+ for (const [key, rawEntry] of Object.entries(updates)) {
304
+ let value = rawEntry;
305
+ let parser;
306
+ let defaultValue = void 0;
307
+ if (rawEntry !== null && typeof rawEntry === "object" && "value" in rawEntry && !Array.isArray(rawEntry)) {
308
+ const descriptor = rawEntry;
309
+ value = descriptor.value;
310
+ parser = descriptor.parser;
311
+ defaultValue = descriptor.defaultValue ?? parser?.defaultValue;
312
+ }
313
+ if (isCleanValue(value, defaultValue)) {
314
+ currentParams.delete(key);
315
+ } else if (parser) {
316
+ const serialized = parser.serialize(value);
317
+ if (serialized === "") {
318
+ currentParams.delete(key);
319
+ } else {
320
+ currentParams.set(key, serialized);
321
+ }
322
+ } else if (Array.isArray(value)) {
323
+ if (value.length === 0) {
324
+ currentParams.delete(key);
325
+ } else {
326
+ currentParams.set(key, value.map(String).join(","));
327
+ }
328
+ } else {
329
+ currentParams.set(key, String(value));
330
+ }
331
+ }
332
+ const queryString = currentParams.toString();
333
+ const nextUrl = queryString ? `${pathname}?${queryString}` : pathname;
334
+ const historyMode = navOptions?.history ?? defaultHistory;
335
+ const shouldScroll = navOptions?.scroll ?? defaultScroll;
336
+ startTransition(() => {
337
+ if (historyMode === "push") {
338
+ router.push(nextUrl, { scroll: shouldScroll });
339
+ } else {
340
+ router.replace(nextUrl, { scroll: shouldScroll });
341
+ }
342
+ });
343
+ },
344
+ [searchParams, pathname, defaultHistory, defaultScroll, router]
345
+ );
346
+ const resetAllParams = react.useCallback(
347
+ (preserveKeys = [], navOptions) => {
348
+ const currentParams = new URLSearchParams(
349
+ searchParams ? searchParams.toString() : ""
350
+ );
351
+ const preserveSet = new Set(preserveKeys);
352
+ const allKeys = Array.from(currentParams.keys());
353
+ for (const key of allKeys) {
354
+ if (!preserveSet.has(key)) {
355
+ currentParams.delete(key);
356
+ }
357
+ }
358
+ const queryString = currentParams.toString();
359
+ const nextUrl = queryString ? `${pathname}?${queryString}` : pathname;
360
+ const historyMode = navOptions?.history ?? defaultHistory;
361
+ const shouldScroll = navOptions?.scroll ?? defaultScroll;
362
+ startTransition(() => {
363
+ if (historyMode === "push") {
364
+ router.push(nextUrl, { scroll: shouldScroll });
365
+ } else {
366
+ router.replace(nextUrl, { scroll: shouldScroll });
367
+ }
368
+ });
369
+ },
370
+ [searchParams, pathname, defaultHistory, defaultScroll, router]
371
+ );
372
+ return {
373
+ setBatchParams,
374
+ resetAllParams,
375
+ isPending
376
+ };
377
+ }
378
+
379
+ exports.areValuesEqual = areValuesEqual;
380
+ exports.createParser = createParser;
381
+ exports.isCleanValue = isCleanValue;
382
+ exports.parseAsArray = parseAsArray;
383
+ exports.parseAsBoolean = parseAsBoolean;
384
+ exports.parseAsFloat = parseAsFloat;
385
+ exports.parseAsInteger = parseAsInteger;
386
+ exports.parseAsNumberRange = parseAsNumberRange;
387
+ exports.parseAsSort = parseAsSort;
388
+ exports.parseAsString = parseAsString;
389
+ exports.useBatchQueryParams = useBatchQueryParams;
390
+ exports.useQueryState = useQueryState;
391
+ //# sourceMappingURL=index.cjs.map
392
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/parsers.ts","../src/useQueryState.ts","../src/useBatchQueryParams.ts"],"names":["useRouter","usePathname","useSearchParams","useTransition","useState","useRef","useEffect","useCallback"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Core Parser Interfaces and Built-in Parsers for `paramsync`.\n * Handles bi-directional conversion between URL query string parameters and typed React state.\n */\n\nexport interface Parser<T> {\n /**\n * Parse a raw URL query string value into a typed representation.\n * Receives `null` when the key is not present in the URL.\n */\n parse: (value: string | null) => T;\n\n /**\n * Convert a typed state value into a URL query parameter string.\n */\n serialize: (value: T) => string;\n\n /**\n * Optional default value associated with this parser.\n */\n defaultValue?: T;\n\n /**\n * Create a derived parser with an explicit non-null default value.\n */\n withDefault: (defaultValue: NonNullable<T>) => ParserWithDefault<NonNullable<T>>;\n}\n\nexport interface ParserWithDefault<T> extends Parser<T> {\n readonly defaultValue: T;\n}\n\n/**\n * Factory to create custom parsers with seamless chaining and default value fallback.\n */\nexport function createParser<T>(options: {\n parse: (value: string | null) => T;\n serialize: (value: T) => string;\n defaultValue?: T;\n}): Parser<T> {\n const parser: Parser<T> = {\n parse: (value: string | null): T => {\n const result = options.parse(value);\n if ((result === null || result === undefined) && options.defaultValue !== undefined) {\n return options.defaultValue;\n }\n return result;\n },\n serialize: options.serialize,\n defaultValue: options.defaultValue,\n withDefault(defaultValue: NonNullable<T>): ParserWithDefault<NonNullable<T>> {\n return {\n defaultValue,\n parse: (value: string | null): NonNullable<T> => {\n if (value === null || value === undefined) {\n return defaultValue;\n }\n const parsed = options.parse(value);\n if (parsed === null || parsed === undefined) {\n return defaultValue;\n }\n return parsed as NonNullable<T>;\n },\n serialize: (value: NonNullable<T>): string => {\n return options.serialize(value as unknown as T);\n },\n withDefault(nextDefault: NonNullable<T>): ParserWithDefault<NonNullable<T>> {\n return parser.withDefault(nextDefault);\n },\n };\n },\n };\n\n return parser;\n}\n\n/* ========================================================================== */\n/* Primitive Parsers */\n/* ========================================================================== */\n\n/**\n * Parser for string parameters.\n * Parses raw string or returns `null` when missing.\n */\nexport const parseAsString: Parser<string | null> = createParser<string | null>({\n parse: (value) => value,\n serialize: (value) => (value !== null && value !== undefined ? String(value) : ''),\n});\n\n/**\n * Parser for integer numbers.\n * Validates strict integer representations (e.g. `42`, `-5`). Returns `null` on NaN or invalid strings.\n */\nexport const parseAsInteger: Parser<number | null> = createParser<number | null>({\n parse: (value) => {\n if (value === null || value === '') return null;\n const num = Number(value);\n return Number.isInteger(num) ? num : null;\n },\n serialize: (value) => (value !== null && value !== undefined ? Math.round(value).toString() : ''),\n});\n\n/**\n * Parser for floating-point numbers.\n * Supports decimal numbers (e.g. `3.14`, `-0.5`). Returns `null` on NaN or infinite numbers.\n */\nexport const parseAsFloat: Parser<number | null> = createParser<number | null>({\n parse: (value) => {\n if (value === null || value === '') return null;\n const num = Number(value);\n return Number.isFinite(num) ? num : null;\n },\n serialize: (value) => (value !== null && value !== undefined ? String(value) : ''),\n});\n\n/**\n * Parser for boolean parameters.\n * Treats 'true' and '1' as `true`, 'false' and '0' as `false`. Returns `null` on unrecognized values.\n */\nexport const parseAsBoolean: Parser<boolean | null> = createParser<boolean | null>({\n parse: (value) => {\n if (value === null || value === '') return null;\n const normalized = value.trim().toLowerCase();\n if (normalized === 'true' || normalized === '1') return true;\n if (normalized === 'false' || normalized === '0') return false;\n return null;\n },\n serialize: (value) => (value === true ? 'true' : value === false ? 'false' : ''),\n});\n\n/* ========================================================================== */\n/* Complex Parsers */\n/* ========================================================================== */\n\n/**\n * Parser for arrays of values (e.g. `?tags=shoes,watches`).\n * Perfect for multi-select checkboxes, tags, and category chips.\n *\n * @param itemParser Parser instance for individual items\n * @param separator Custom delimiter (defaults to comma `,`)\n */\nexport function parseAsArray<T>(\n itemParser: Parser<T>,\n separator: string = ','\n): ParserWithDefault<NonNullable<T>[]> {\n type ItemType = NonNullable<T>;\n return createParser<ItemType[]>({\n defaultValue: [],\n parse: (value: string | null): ItemType[] => {\n if (value === null || value === undefined || value.trim() === '') {\n return [];\n }\n const rawItems = value.split(separator);\n const results: ItemType[] = [];\n\n for (const raw of rawItems) {\n const parsed = itemParser.parse(raw);\n if (parsed !== null && parsed !== undefined) {\n results.push(parsed as ItemType);\n }\n }\n\n return results;\n },\n serialize: (value: ItemType[]): string => {\n if (!Array.isArray(value) || value.length === 0) {\n return '';\n }\n return value.map((item) => itemParser.serialize(item as unknown as T)).join(separator);\n },\n }) as ParserWithDefault<ItemType[]>;\n}\n\nexport interface NumberRange {\n min: number;\n max: number;\n}\n\n/**\n * Parser for numeric range sliders and price bounds (e.g. `?price=100-500`).\n *\n * @param defaultMin Minimum range bound\n * @param defaultMax Maximum range bound\n * @param separator Delimiter between min and max (defaults to `-`)\n */\nexport function parseAsNumberRange(\n defaultMin: number,\n defaultMax: number,\n separator: string = '-'\n): ParserWithDefault<NumberRange> {\n const defaultRange: NumberRange = { min: defaultMin, max: defaultMax };\n\n return createParser<NumberRange>({\n defaultValue: defaultRange,\n parse: (value: string | null): NumberRange => {\n if (!value || value.trim() === '') {\n return defaultRange;\n }\n\n const parts = value.split(separator);\n if (parts.length !== 2) {\n return defaultRange;\n }\n\n const min = Number(parts[0]);\n const max = Number(parts[1]);\n\n if (!Number.isFinite(min) || !Number.isFinite(max)) {\n return defaultRange;\n }\n\n return { min, max };\n },\n serialize: (value: NumberRange): string => {\n if (\n !value ||\n typeof value.min !== 'number' ||\n typeof value.max !== 'number' ||\n (value.min === defaultMin && value.max === defaultMax)\n ) {\n return '';\n }\n return `${value.min}${separator}${value.max}`;\n },\n }) as ParserWithDefault<NumberRange>;\n}\n\nexport type SortOrder = 'asc' | 'desc';\n\nexport interface SortOption<TField extends string = string> {\n field: TField;\n order: SortOrder;\n}\n\n/**\n * Parser for sorting configurations in data tables and feeds (e.g. `?sort=price.desc`).\n *\n * @param defaultField Initial sort field\n * @param defaultOrder Initial sort order (`'asc'` or `'desc'`)\n * @param separator Delimiter between field and order (defaults to `.`)\n */\nexport function parseAsSort<TField extends string = string>(\n defaultField: TField,\n defaultOrder: SortOrder = 'asc',\n separator: string = '.'\n): ParserWithDefault<SortOption<TField>> {\n const defaultOption: SortOption<TField> = {\n field: defaultField,\n order: defaultOrder,\n };\n\n return createParser<SortOption<TField>>({\n defaultValue: defaultOption,\n parse: (value: string | null): SortOption<TField> => {\n if (!value || value.trim() === '') {\n return defaultOption;\n }\n\n const [field, rawOrder] = value.split(separator);\n if (!field) {\n return defaultOption;\n }\n\n const order: SortOrder =\n rawOrder === 'desc' || rawOrder === 'asc' ? rawOrder : defaultOrder;\n\n return {\n field: field as TField,\n order,\n };\n },\n serialize: (value: SortOption<TField>): string => {\n if (!value || !value.field) {\n return '';\n }\n if (value.field === defaultField && value.order === defaultOrder) {\n return '';\n }\n return `${value.field}${separator}${value.order}`;\n },\n }) as ParserWithDefault<SortOption<TField>>;\n}\n\n/* ========================================================================== */\n/* Clean URL Helpers */\n/* ========================================================================== */\n\n/**\n * Deep equality comparator for state values against defaults.\n */\nexport function areValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (a === null || b === null || a === undefined || b === undefined) return false;\n if (typeof a !== typeof b) return false;\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false;\n return a.every((item, idx) => areValuesEqual(item, b[idx]));\n }\n\n if (typeof a === 'object' && typeof b === 'object') {\n const keysA = Object.keys(a as object);\n const keysB = Object.keys(b as object);\n if (keysA.length !== keysB.length) return false;\n return keysA.every((key) =>\n areValuesEqual(\n (a as Record<string, unknown>)[key],\n (b as Record<string, unknown>)[key]\n )\n );\n }\n\n return false;\n}\n\n/**\n * Determines whether a value should be stripped from the URL query string under the Clean URL Policy.\n */\nexport function isCleanValue(value: unknown, defaultValue?: unknown): boolean {\n if (value === null || value === undefined) return true;\n if (value === '') return true;\n if (Array.isArray(value) && value.length === 0) return true;\n if (defaultValue !== undefined && areValuesEqual(value, defaultValue)) return true;\n return false;\n}\n","'use client';\n\nimport { useState, useEffect, useRef, useCallback, useTransition } from 'react';\nimport { useRouter, usePathname, useSearchParams } from 'next/navigation';\nimport {\n Parser,\n ParserWithDefault,\n isCleanValue,\n} from './parsers';\n\nexport interface UseQueryStateOptions<T> {\n /**\n * Fallback value used when query parameter is omitted or cannot be parsed.\n */\n defaultValue?: T;\n\n /**\n * Debounce delay in milliseconds before updating the browser URL.\n * Internal state updates immediately to eliminate UI typing lag.\n */\n debounceMs?: number;\n\n /**\n * Navigation method: 'replace' updates URL in-place; 'push' creates a new browser history entry.\n * @default 'replace'\n */\n history?: 'replace' | 'push';\n\n /**\n * Whether Next.js should scroll to the top of the page after URL update.\n * @default false\n */\n scroll?: boolean;\n}\n\nexport type StateUpdater<T> = T | ((prev: T) => T);\n\n/**\n * Hook to manage React state synchronized bidirectionally with URL Search Parameters in Next.js App Router.\n *\n * @param key The query string parameter name (e.g. `'q'`, `'page'`, `'sort'`).\n * @param parser The parser instance (e.g. `parseAsString`, `parseAsInteger`, etc.).\n * @param options Configuration options for defaults, debouncing, and history.\n * @returns A tuple of `[value, setValue, reset, isPending] as const`.\n */\nexport function useQueryState<T>(\n key: string,\n parser: ParserWithDefault<T>,\n options?: UseQueryStateOptions<T>\n): readonly [\n value: T,\n setValue: (valueOrUpdater: StateUpdater<T>) => void,\n reset: () => void,\n isPending: boolean\n];\n\nexport function useQueryState<T>(\n key: string,\n parser: Parser<T>,\n options: UseQueryStateOptions<T> & { defaultValue: T }\n): readonly [\n value: T,\n setValue: (valueOrUpdater: StateUpdater<T>) => void,\n reset: () => void,\n isPending: boolean\n];\n\nexport function useQueryState<T>(\n key: string,\n parser: Parser<T>,\n options?: UseQueryStateOptions<T>\n): readonly [\n value: T,\n setValue: (valueOrUpdater: StateUpdater<T>) => void,\n reset: () => void,\n isPending: boolean\n];\n\nexport function useQueryState<T>(\n key: string,\n parser: Parser<T>,\n options?: UseQueryStateOptions<T>\n): readonly [\n value: T,\n setValue: (valueOrUpdater: StateUpdater<T>) => void,\n reset: () => void,\n isPending: boolean\n] {\n const router = useRouter();\n const pathname = usePathname();\n const searchParams = useSearchParams();\n const [isPending, startTransition] = useTransition();\n\n const effectiveDefault = options?.defaultValue ?? parser.defaultValue;\n const debounceMs = options?.debounceMs ?? 0;\n const historyMode = options?.history ?? 'replace';\n const shouldScroll = options?.scroll ?? false;\n\n // Read initial value from current URL search params\n const rawQueryValue = searchParams ? searchParams.get(key) : null;\n const parsedUrlValue = parser.parse(rawQueryValue);\n const initialValue =\n parsedUrlValue !== null && parsedUrlValue !== undefined\n ? parsedUrlValue\n : (effectiveDefault as T);\n\n // Local state guarantees instantaneous typing and interaction feedback\n const [internalValue, setInternalValue] = useState<T>(initialValue);\n\n // Keep tracking refs for latest values, debouncing, and external URL sync\n const latestValueRef = useRef<T>(internalValue);\n latestValueRef.current = internalValue;\n\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const lastKnownUrlParamRef = useRef<string | null>(rawQueryValue);\n const lastCommittedParamRef = useRef<string | null>(rawQueryValue);\n\n // Synchronize internal state when URL search params change externally (e.g. back/forward navigation)\n useEffect(() => {\n const currentParam = searchParams ? searchParams.get(key) : null;\n\n if (currentParam !== lastKnownUrlParamRef.current) {\n lastKnownUrlParamRef.current = currentParam;\n\n if (currentParam !== lastCommittedParamRef.current) {\n lastCommittedParamRef.current = currentParam;\n const parsed = parser.parse(currentParam);\n const nextVal =\n parsed !== null && parsed !== undefined\n ? parsed\n : (effectiveDefault as T);\n\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n\n setInternalValue(nextVal);\n latestValueRef.current = nextVal;\n }\n }\n }, [searchParams, key, parser, effectiveDefault]);\n\n // Clean up timers on unmount\n useEffect(() => {\n return () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n }\n };\n }, []);\n\n // Function to commit changes to the URL\n const commitToUrl = useCallback(\n (targetValue: T) => {\n const currentParams = new URLSearchParams(\n searchParams ? searchParams.toString() : ''\n );\n const serialized = parser.serialize(targetValue);\n\n // Clean URL Policy: remove if serialized to empty, or matches default/empty state\n if (isCleanValue(targetValue, effectiveDefault) || serialized === '') {\n currentParams.delete(key);\n } else {\n currentParams.set(key, serialized);\n }\n\n const queryString = currentParams.toString();\n const nextUrl = queryString ? `${pathname}?${queryString}` : pathname;\n lastCommittedParamRef.current = currentParams.get(key);\n\n startTransition(() => {\n if (historyMode === 'push') {\n router.push(nextUrl, { scroll: shouldScroll });\n } else {\n router.replace(nextUrl, { scroll: shouldScroll });\n }\n });\n },\n [\n searchParams,\n parser,\n effectiveDefault,\n key,\n pathname,\n historyMode,\n shouldScroll,\n router,\n ]\n );\n\n // Dispatcher with optional debounce\n const setValue = useCallback(\n (valueOrUpdater: StateUpdater<T>) => {\n const nextValue =\n typeof valueOrUpdater === 'function'\n ? (valueOrUpdater as (prev: T) => T)(latestValueRef.current)\n : valueOrUpdater;\n\n // 1. Immediately update internal state for 0ms UI lag\n setInternalValue(nextValue);\n latestValueRef.current = nextValue;\n\n // 2. Clear existing timer if any\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n\n // 3. Debounce URL navigation or commit synchronously\n if (debounceMs > 0) {\n timerRef.current = setTimeout(() => {\n commitToUrl(nextValue);\n timerRef.current = null;\n }, debounceMs);\n } else {\n commitToUrl(nextValue);\n }\n },\n [debounceMs, commitToUrl]\n );\n\n // Reset helper to restore default value and clean URL\n const reset = useCallback(() => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n const def = effectiveDefault as T;\n setInternalValue(def);\n latestValueRef.current = def;\n commitToUrl(def);\n }, [effectiveDefault, commitToUrl]);\n\n return [internalValue, setValue, reset, isPending] as const;\n}\n","'use client';\n\nimport { useCallback, useTransition } from 'react';\nimport { useRouter, usePathname, useSearchParams } from 'next/navigation';\nimport { Parser, isCleanValue } from './parsers';\n\nexport interface BatchNavigationOptions {\n /**\n * Navigation method: 'replace' updates in-place; 'push' creates a new browser history entry.\n * @default 'replace'\n */\n history?: 'replace' | 'push';\n\n /**\n * Whether to scroll to top of page on update.\n * @default false\n */\n scroll?: boolean;\n}\n\nexport interface BatchParamDescriptor<T = any> {\n value: T;\n parser?: Parser<any>;\n defaultValue?: T;\n}\n\nexport type BatchParamValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | unknown[]\n | BatchParamDescriptor<any>;\n\nexport type BatchParamsRecord = Record<string, BatchParamValue>;\n\nexport interface UseBatchQueryParamsOptions extends BatchNavigationOptions {}\n\n/**\n * Hook to execute batch updates to URL query parameters in a single transition.\n * Ideal for \"Apply Filters\" buttons, filter drawers/modals, and wiping multiple parameters at once.\n */\nexport function useBatchQueryParams(options?: UseBatchQueryParamsOptions) {\n const router = useRouter();\n const pathname = usePathname();\n const searchParams = useSearchParams();\n const [isPending, startTransition] = useTransition();\n\n const defaultHistory = options?.history ?? 'replace';\n const defaultScroll = options?.scroll ?? false;\n\n /**\n * Apply multiple parameter updates simultaneously in a single navigation transition.\n *\n * @param updates Object containing key/value pairs to set or delete.\n * @param navOptions Optional navigation overrides for this batch.\n */\n const setBatchParams = useCallback(\n (updates: BatchParamsRecord, navOptions?: BatchNavigationOptions) => {\n const currentParams = new URLSearchParams(\n searchParams ? searchParams.toString() : ''\n );\n\n for (const [key, rawEntry] of Object.entries(updates)) {\n let value: unknown = rawEntry;\n let parser: Parser<unknown> | undefined;\n let defaultValue: unknown = undefined;\n\n // Check if passed entry is a descriptor with parser/defaultValue\n if (\n rawEntry !== null &&\n typeof rawEntry === 'object' &&\n 'value' in rawEntry &&\n !Array.isArray(rawEntry)\n ) {\n const descriptor = rawEntry as BatchParamDescriptor<unknown>;\n value = descriptor.value;\n parser = descriptor.parser;\n defaultValue = descriptor.defaultValue ?? parser?.defaultValue;\n }\n\n // Apply Clean URL Policy\n if (isCleanValue(value, defaultValue)) {\n currentParams.delete(key);\n } else if (parser) {\n const serialized = parser.serialize(value);\n if (serialized === '') {\n currentParams.delete(key);\n } else {\n currentParams.set(key, serialized);\n }\n } else if (Array.isArray(value)) {\n if (value.length === 0) {\n currentParams.delete(key);\n } else {\n currentParams.set(key, value.map(String).join(','));\n }\n } else {\n currentParams.set(key, String(value));\n }\n }\n\n const queryString = currentParams.toString();\n const nextUrl = queryString ? `${pathname}?${queryString}` : pathname;\n const historyMode = navOptions?.history ?? defaultHistory;\n const shouldScroll = navOptions?.scroll ?? defaultScroll;\n\n startTransition(() => {\n if (historyMode === 'push') {\n router.push(nextUrl, { scroll: shouldScroll });\n } else {\n router.replace(nextUrl, { scroll: shouldScroll });\n }\n });\n },\n [searchParams, pathname, defaultHistory, defaultScroll, router]\n );\n\n /**\n * Wipe all non-essential query parameters from the URL in one call.\n *\n * @param preserveKeys List of query param keys to keep intact (e.g. `['tab', 'utm_source']`).\n * @param navOptions Optional navigation overrides.\n */\n const resetAllParams = useCallback(\n (preserveKeys: string[] = [], navOptions?: BatchNavigationOptions) => {\n const currentParams = new URLSearchParams(\n searchParams ? searchParams.toString() : ''\n );\n const preserveSet = new Set(preserveKeys);\n\n const allKeys = Array.from(currentParams.keys());\n for (const key of allKeys) {\n if (!preserveSet.has(key)) {\n currentParams.delete(key);\n }\n }\n\n const queryString = currentParams.toString();\n const nextUrl = queryString ? `${pathname}?${queryString}` : pathname;\n const historyMode = navOptions?.history ?? defaultHistory;\n const shouldScroll = navOptions?.scroll ?? defaultScroll;\n\n startTransition(() => {\n if (historyMode === 'push') {\n router.push(nextUrl, { scroll: shouldScroll });\n } else {\n router.replace(nextUrl, { scroll: shouldScroll });\n }\n });\n },\n [searchParams, pathname, defaultHistory, defaultScroll, router]\n );\n\n return {\n setBatchParams,\n resetAllParams,\n isPending,\n };\n}\n"]}
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Core Parser Interfaces and Built-in Parsers for `paramsync`.
3
+ * Handles bi-directional conversion between URL query string parameters and typed React state.
4
+ */
5
+ interface Parser<T> {
6
+ /**
7
+ * Parse a raw URL query string value into a typed representation.
8
+ * Receives `null` when the key is not present in the URL.
9
+ */
10
+ parse: (value: string | null) => T;
11
+ /**
12
+ * Convert a typed state value into a URL query parameter string.
13
+ */
14
+ serialize: (value: T) => string;
15
+ /**
16
+ * Optional default value associated with this parser.
17
+ */
18
+ defaultValue?: T;
19
+ /**
20
+ * Create a derived parser with an explicit non-null default value.
21
+ */
22
+ withDefault: (defaultValue: NonNullable<T>) => ParserWithDefault<NonNullable<T>>;
23
+ }
24
+ interface ParserWithDefault<T> extends Parser<T> {
25
+ readonly defaultValue: T;
26
+ }
27
+ /**
28
+ * Factory to create custom parsers with seamless chaining and default value fallback.
29
+ */
30
+ declare function createParser<T>(options: {
31
+ parse: (value: string | null) => T;
32
+ serialize: (value: T) => string;
33
+ defaultValue?: T;
34
+ }): Parser<T>;
35
+ /**
36
+ * Parser for string parameters.
37
+ * Parses raw string or returns `null` when missing.
38
+ */
39
+ declare const parseAsString: Parser<string | null>;
40
+ /**
41
+ * Parser for integer numbers.
42
+ * Validates strict integer representations (e.g. `42`, `-5`). Returns `null` on NaN or invalid strings.
43
+ */
44
+ declare const parseAsInteger: Parser<number | null>;
45
+ /**
46
+ * Parser for floating-point numbers.
47
+ * Supports decimal numbers (e.g. `3.14`, `-0.5`). Returns `null` on NaN or infinite numbers.
48
+ */
49
+ declare const parseAsFloat: Parser<number | null>;
50
+ /**
51
+ * Parser for boolean parameters.
52
+ * Treats 'true' and '1' as `true`, 'false' and '0' as `false`. Returns `null` on unrecognized values.
53
+ */
54
+ declare const parseAsBoolean: Parser<boolean | null>;
55
+ /**
56
+ * Parser for arrays of values (e.g. `?tags=shoes,watches`).
57
+ * Perfect for multi-select checkboxes, tags, and category chips.
58
+ *
59
+ * @param itemParser Parser instance for individual items
60
+ * @param separator Custom delimiter (defaults to comma `,`)
61
+ */
62
+ declare function parseAsArray<T>(itemParser: Parser<T>, separator?: string): ParserWithDefault<NonNullable<T>[]>;
63
+ interface NumberRange {
64
+ min: number;
65
+ max: number;
66
+ }
67
+ /**
68
+ * Parser for numeric range sliders and price bounds (e.g. `?price=100-500`).
69
+ *
70
+ * @param defaultMin Minimum range bound
71
+ * @param defaultMax Maximum range bound
72
+ * @param separator Delimiter between min and max (defaults to `-`)
73
+ */
74
+ declare function parseAsNumberRange(defaultMin: number, defaultMax: number, separator?: string): ParserWithDefault<NumberRange>;
75
+ type SortOrder = 'asc' | 'desc';
76
+ interface SortOption<TField extends string = string> {
77
+ field: TField;
78
+ order: SortOrder;
79
+ }
80
+ /**
81
+ * Parser for sorting configurations in data tables and feeds (e.g. `?sort=price.desc`).
82
+ *
83
+ * @param defaultField Initial sort field
84
+ * @param defaultOrder Initial sort order (`'asc'` or `'desc'`)
85
+ * @param separator Delimiter between field and order (defaults to `.`)
86
+ */
87
+ declare function parseAsSort<TField extends string = string>(defaultField: TField, defaultOrder?: SortOrder, separator?: string): ParserWithDefault<SortOption<TField>>;
88
+ /**
89
+ * Deep equality comparator for state values against defaults.
90
+ */
91
+ declare function areValuesEqual(a: unknown, b: unknown): boolean;
92
+ /**
93
+ * Determines whether a value should be stripped from the URL query string under the Clean URL Policy.
94
+ */
95
+ declare function isCleanValue(value: unknown, defaultValue?: unknown): boolean;
96
+
97
+ interface UseQueryStateOptions<T> {
98
+ /**
99
+ * Fallback value used when query parameter is omitted or cannot be parsed.
100
+ */
101
+ defaultValue?: T;
102
+ /**
103
+ * Debounce delay in milliseconds before updating the browser URL.
104
+ * Internal state updates immediately to eliminate UI typing lag.
105
+ */
106
+ debounceMs?: number;
107
+ /**
108
+ * Navigation method: 'replace' updates URL in-place; 'push' creates a new browser history entry.
109
+ * @default 'replace'
110
+ */
111
+ history?: 'replace' | 'push';
112
+ /**
113
+ * Whether Next.js should scroll to the top of the page after URL update.
114
+ * @default false
115
+ */
116
+ scroll?: boolean;
117
+ }
118
+ type StateUpdater<T> = T | ((prev: T) => T);
119
+ /**
120
+ * Hook to manage React state synchronized bidirectionally with URL Search Parameters in Next.js App Router.
121
+ *
122
+ * @param key The query string parameter name (e.g. `'q'`, `'page'`, `'sort'`).
123
+ * @param parser The parser instance (e.g. `parseAsString`, `parseAsInteger`, etc.).
124
+ * @param options Configuration options for defaults, debouncing, and history.
125
+ * @returns A tuple of `[value, setValue, reset, isPending] as const`.
126
+ */
127
+ declare function useQueryState<T>(key: string, parser: ParserWithDefault<T>, options?: UseQueryStateOptions<T>): readonly [
128
+ value: T,
129
+ setValue: (valueOrUpdater: StateUpdater<T>) => void,
130
+ reset: () => void,
131
+ isPending: boolean
132
+ ];
133
+ declare function useQueryState<T>(key: string, parser: Parser<T>, options: UseQueryStateOptions<T> & {
134
+ defaultValue: T;
135
+ }): readonly [
136
+ value: T,
137
+ setValue: (valueOrUpdater: StateUpdater<T>) => void,
138
+ reset: () => void,
139
+ isPending: boolean
140
+ ];
141
+ declare function useQueryState<T>(key: string, parser: Parser<T>, options?: UseQueryStateOptions<T>): readonly [
142
+ value: T,
143
+ setValue: (valueOrUpdater: StateUpdater<T>) => void,
144
+ reset: () => void,
145
+ isPending: boolean
146
+ ];
147
+
148
+ interface BatchNavigationOptions {
149
+ /**
150
+ * Navigation method: 'replace' updates in-place; 'push' creates a new browser history entry.
151
+ * @default 'replace'
152
+ */
153
+ history?: 'replace' | 'push';
154
+ /**
155
+ * Whether to scroll to top of page on update.
156
+ * @default false
157
+ */
158
+ scroll?: boolean;
159
+ }
160
+ interface BatchParamDescriptor<T = any> {
161
+ value: T;
162
+ parser?: Parser<any>;
163
+ defaultValue?: T;
164
+ }
165
+ type BatchParamValue = string | number | boolean | null | undefined | unknown[] | BatchParamDescriptor<any>;
166
+ type BatchParamsRecord = Record<string, BatchParamValue>;
167
+ interface UseBatchQueryParamsOptions extends BatchNavigationOptions {
168
+ }
169
+ /**
170
+ * Hook to execute batch updates to URL query parameters in a single transition.
171
+ * Ideal for "Apply Filters" buttons, filter drawers/modals, and wiping multiple parameters at once.
172
+ */
173
+ declare function useBatchQueryParams(options?: UseBatchQueryParamsOptions): {
174
+ setBatchParams: (updates: BatchParamsRecord, navOptions?: BatchNavigationOptions) => void;
175
+ resetAllParams: (preserveKeys?: string[], navOptions?: BatchNavigationOptions) => void;
176
+ isPending: boolean;
177
+ };
178
+
179
+ export { areValuesEqual, createParser, isCleanValue, parseAsArray, parseAsBoolean, parseAsFloat, parseAsInteger, parseAsNumberRange, parseAsSort, parseAsString, useBatchQueryParams, useQueryState };
180
+ export type { BatchNavigationOptions, BatchParamDescriptor, BatchParamValue, BatchParamsRecord, NumberRange, Parser, ParserWithDefault, SortOption, SortOrder, StateUpdater, UseBatchQueryParamsOptions, UseQueryStateOptions };