@farm.js/core 0.1.0-beta.54 → 0.1.0-beta.56
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-427JQ6OZ.mjs → chunk-5T2XATMK.mjs} +58 -11
- package/dist/chunk-5T2XATMK.mjs.map +1 -0
- package/dist/{chunk-J34JCW6R.mjs → chunk-FOEPWRHM.mjs} +2 -2
- package/dist/{chunk-BDHDDITP.mjs → chunk-FPLRESQA.mjs} +30 -18
- package/dist/chunk-FPLRESQA.mjs.map +1 -0
- package/dist/{chunk-JECGLPIS.mjs → chunk-PYIVNTDI.mjs} +4 -4
- package/dist/{chunk-GPHLXAFT.mjs → chunk-UH5R4J5M.mjs} +2 -2
- package/dist/{chunk-ENPB4CSY.mjs → chunk-XLQVJDK2.mjs} +2 -2
- package/dist/{chunk-ENPB4CSY.mjs.map → chunk-XLQVJDK2.mjs.map} +1 -1
- package/dist/{chunk-GVQ54ERX.mjs → chunk-YPKCFGA7.mjs} +9 -3
- package/dist/chunk-YPKCFGA7.mjs.map +1 -0
- package/dist/{chunk-PGZVE6P7.mjs → chunk-ZOQLQAUG.mjs} +2 -2
- package/dist/client/lifecycle.mjs +2 -2
- package/dist/client-plugins.cjs +1 -1
- package/dist/client-plugins.cjs.map +1 -1
- package/dist/client-plugins.mjs +2 -2
- package/dist/client.cjs +76 -23
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.mts +2 -5
- package/dist/client.d.ts +2 -5
- package/dist/client.mjs +11 -11
- package/dist/index.cjs +85 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +17 -17
- package/dist/internal/build-runtime.cjs +8 -2
- package/dist/internal/build-runtime.cjs.map +1 -1
- package/dist/internal/build-runtime.mjs +3 -3
- package/dist/internal/client-runtime.cjs +1 -1
- package/dist/internal/client-runtime.cjs.map +1 -1
- package/dist/internal/client-runtime.mjs +2 -2
- package/dist/navigation.cjs +74 -20
- package/dist/navigation.cjs.map +1 -1
- package/dist/navigation.mjs +4 -4
- package/dist/plugin.cjs +1 -1
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.mjs +2 -2
- package/dist/query/client.cjs +105 -11
- package/dist/query/client.cjs.map +1 -1
- package/dist/query/client.mjs +18 -11
- package/dist/query/client.mjs.map +1 -1
- package/dist/renderer-client.cjs +56 -12
- package/dist/renderer-client.cjs.map +1 -1
- package/dist/renderer-client.d.mts +1 -1
- package/dist/renderer-client.d.ts +1 -1
- package/dist/renderer-client.mjs +12 -11
- package/dist/renderer-client.mjs.map +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.mjs +2 -2
- package/dist/{spa-router-BD7iUG-C.d.mts → spa-router-BV71GGW0.d.mts} +4 -3
- package/dist/{spa-router-swFKTqwR.d.ts → spa-router-BylM6JXm.d.ts} +4 -3
- package/dist/version.cjs +1 -1
- package/dist/version.cjs.map +1 -1
- package/dist/version.mjs +1 -1
- package/dist/vite.cjs +1 -1
- package/dist/vite.cjs.map +1 -1
- package/dist/vite.mjs +2 -2
- package/package.json +1 -1
- package/dist/chunk-427JQ6OZ.mjs.map +0 -1
- package/dist/chunk-BDHDDITP.mjs.map +0 -1
- package/dist/chunk-GVQ54ERX.mjs.map +0 -1
- /package/dist/{chunk-J34JCW6R.mjs.map → chunk-FOEPWRHM.mjs.map} +0 -0
- /package/dist/{chunk-JECGLPIS.mjs.map → chunk-PYIVNTDI.mjs.map} +0 -0
- /package/dist/{chunk-GPHLXAFT.mjs.map → chunk-UH5R4J5M.mjs.map} +0 -0
- /package/dist/{chunk-PGZVE6P7.mjs.map → chunk-ZOQLQAUG.mjs.map} +0 -0
package/dist/plugin.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import "./chunk-RQ633A5I.mjs";
|
|
7
7
|
import {
|
|
8
8
|
farmPlugin
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-ZOQLQAUG.mjs";
|
|
10
10
|
import "./chunk-XJTDE4L4.mjs";
|
|
11
11
|
import "./chunk-CAUVJN4X.mjs";
|
|
12
12
|
import "./chunk-AMZZCRQ7.mjs";
|
|
@@ -63,7 +63,7 @@ import "./chunk-RBRLLZL5.mjs";
|
|
|
63
63
|
import "./chunk-WZ5QFROO.mjs";
|
|
64
64
|
import "./chunk-7ZMKVETU.mjs";
|
|
65
65
|
import "./chunk-FXG2PS27.mjs";
|
|
66
|
-
import "./chunk-
|
|
66
|
+
import "./chunk-XLQVJDK2.mjs";
|
|
67
67
|
import "./chunk-CGILA3WO.mjs";
|
|
68
68
|
export {
|
|
69
69
|
FarmRuntimeShutdownError,
|
package/dist/query/client.cjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
9
|
var __export = (target, all) => {
|
|
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
18
|
}
|
|
17
19
|
return to;
|
|
18
20
|
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
19
29
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
30
|
|
|
21
31
|
// src/query/client.ts
|
|
@@ -40,6 +50,92 @@ __export(client_exports, {
|
|
|
40
50
|
module.exports = __toCommonJS(client_exports);
|
|
41
51
|
var import_react = require("react");
|
|
42
52
|
|
|
53
|
+
// src/i18n/client-runtime.ts
|
|
54
|
+
var import_intl_messageformat = __toESM(require("intl-messageformat"));
|
|
55
|
+
|
|
56
|
+
// src/i18n/bridge.ts
|
|
57
|
+
var FARM_I18N_SNAPSHOT_RESOLVER = /* @__PURE__ */ Symbol.for("farm.i18n.snapshotResolver");
|
|
58
|
+
function getActiveFarmI18nSnapshot() {
|
|
59
|
+
if (typeof window !== "undefined" && window.__FARM_I18N__) {
|
|
60
|
+
return window.__FARM_I18N__;
|
|
61
|
+
}
|
|
62
|
+
return globalThis[FARM_I18N_SNAPSHOT_RESOLVER]?.();
|
|
63
|
+
}
|
|
64
|
+
__name(getActiveFarmI18nSnapshot, "getActiveFarmI18nSnapshot");
|
|
65
|
+
|
|
66
|
+
// src/i18n/client-runtime.ts
|
|
67
|
+
var compiled = /* @__PURE__ */ new Map();
|
|
68
|
+
var currentSnapshot;
|
|
69
|
+
function getFarmI18nClientState() {
|
|
70
|
+
return currentSnapshot || getActiveFarmI18nSnapshot();
|
|
71
|
+
}
|
|
72
|
+
__name(getFarmI18nClientState, "getFarmI18nClientState");
|
|
73
|
+
var translate = /* @__PURE__ */ __name((key, values, rich = false) => {
|
|
74
|
+
const snapshot = requireSnapshot();
|
|
75
|
+
const message = snapshot.messages[key];
|
|
76
|
+
if (message === void 0) return key;
|
|
77
|
+
const cacheKey = `${snapshot.locale}\0${key}\0${message}`;
|
|
78
|
+
let formatter = compiled.get(cacheKey);
|
|
79
|
+
if (!formatter) {
|
|
80
|
+
formatter = new import_intl_messageformat.default(message, snapshot.locale);
|
|
81
|
+
compiled.set(cacheKey, formatter);
|
|
82
|
+
}
|
|
83
|
+
const result = formatter.format(values);
|
|
84
|
+
if (rich) return Array.isArray(result) && result.length === 1 ? result[0] : result;
|
|
85
|
+
if (Array.isArray(result) && result.some((part) => typeof part !== "string")) {
|
|
86
|
+
throw new Error(`Farm i18n message "${key}" contains rich content. Render it with t.rich().`);
|
|
87
|
+
}
|
|
88
|
+
return Array.isArray(result) ? result.join("") : String(result);
|
|
89
|
+
}, "translate");
|
|
90
|
+
var t = /* @__PURE__ */ __name(((key, values) => translate(key, values)), "t");
|
|
91
|
+
t.rich = (key, values) => translate(key, values, true);
|
|
92
|
+
t.raw = (key) => requireSnapshot().messages[key] ?? key;
|
|
93
|
+
t.has = (key) => key in requireSnapshot().messages;
|
|
94
|
+
function requireSnapshot() {
|
|
95
|
+
const snapshot = getFarmI18nClientState();
|
|
96
|
+
if (!snapshot) {
|
|
97
|
+
throw new Error("Farm i18n is not configured or has not been hydrated.");
|
|
98
|
+
}
|
|
99
|
+
return snapshot;
|
|
100
|
+
}
|
|
101
|
+
__name(requireSnapshot, "requireSnapshot");
|
|
102
|
+
|
|
103
|
+
// src/client/spa-router.ts
|
|
104
|
+
function getInstalledFarmSPARouter() {
|
|
105
|
+
if (typeof window === "undefined") return void 0;
|
|
106
|
+
return window.__FARM_SPA_ROUTER__;
|
|
107
|
+
}
|
|
108
|
+
__name(getInstalledFarmSPARouter, "getInstalledFarmSPARouter");
|
|
109
|
+
|
|
110
|
+
// src/client/history-sync.ts
|
|
111
|
+
var FARM_HISTORY_CHANGE_EVENT = "farm:historychange";
|
|
112
|
+
function notifyHistoryChange(kind) {
|
|
113
|
+
if (typeof window === "undefined") return;
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
if (kind === "url-search" && !getInstalledFarmSPARouter()) {
|
|
116
|
+
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
window.dispatchEvent(
|
|
120
|
+
new CustomEvent(FARM_HISTORY_CHANGE_EVENT, {
|
|
121
|
+
detail: { kind }
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
}, 0);
|
|
125
|
+
}
|
|
126
|
+
__name(notifyHistoryChange, "notifyHistoryChange");
|
|
127
|
+
function subscribeHistoryChange(listener) {
|
|
128
|
+
if (typeof window === "undefined") return () => {
|
|
129
|
+
};
|
|
130
|
+
window.addEventListener("popstate", listener);
|
|
131
|
+
window.addEventListener(FARM_HISTORY_CHANGE_EVENT, listener);
|
|
132
|
+
return () => {
|
|
133
|
+
window.removeEventListener("popstate", listener);
|
|
134
|
+
window.removeEventListener(FARM_HISTORY_CHANGE_EVENT, listener);
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
__name(subscribeHistoryChange, "subscribeHistoryChange");
|
|
138
|
+
|
|
43
139
|
// src/query/sync.ts
|
|
44
140
|
var _Emitter = class _Emitter {
|
|
45
141
|
constructor() {
|
|
@@ -303,20 +399,18 @@ var updateURL = /* @__PURE__ */ __name((updates, options = {}, emitUpdate = true
|
|
|
303
399
|
const currentUrl = window.location.pathname + (window.location.search || "") + window.location.hash;
|
|
304
400
|
if (newUrl !== currentUrl) {
|
|
305
401
|
const update = /* @__PURE__ */ __name(() => {
|
|
402
|
+
const historyState = window.history.state;
|
|
403
|
+
const nextHistoryState = historyState && typeof historyState === "object" && "path" in historyState ? { ...historyState, path: newUrl } : historyState;
|
|
306
404
|
if (history === "replaceState") {
|
|
307
|
-
window.history.replaceState(
|
|
405
|
+
window.history.replaceState(nextHistoryState, "", newUrl);
|
|
308
406
|
} else {
|
|
309
|
-
window.history.pushState(
|
|
407
|
+
window.history.pushState(nextHistoryState, "", newUrl);
|
|
310
408
|
}
|
|
311
409
|
if (emitUpdate) {
|
|
312
410
|
const actualSearchParams = new URLSearchParams(window.location.search);
|
|
313
411
|
emitter.emitUpdate(actualSearchParams);
|
|
314
412
|
}
|
|
315
|
-
if (shallow)
|
|
316
|
-
setTimeout(() => {
|
|
317
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
318
|
-
}, 0);
|
|
319
|
-
}
|
|
413
|
+
if (shallow) notifyHistoryChange("url-search");
|
|
320
414
|
if (scroll) {
|
|
321
415
|
window.scrollTo(0, 0);
|
|
322
416
|
}
|
|
@@ -393,11 +487,11 @@ function useQueryState(key, parser, options) {
|
|
|
393
487
|
stateRef.current = payload.state;
|
|
394
488
|
}
|
|
395
489
|
}, "onKeyUpdate");
|
|
396
|
-
|
|
490
|
+
const unsubscribeHistory = subscribeHistoryChange(onPopState);
|
|
397
491
|
emitter.on("update", onEmitterUpdate);
|
|
398
492
|
emitter.onKey(key, onKeyUpdate);
|
|
399
493
|
return () => {
|
|
400
|
-
|
|
494
|
+
unsubscribeHistory();
|
|
401
495
|
emitter.off("update", onEmitterUpdate);
|
|
402
496
|
emitter.offKey(key, onKeyUpdate);
|
|
403
497
|
};
|
|
@@ -469,10 +563,10 @@ function useQueryStates(parsers, options = {}) {
|
|
|
469
563
|
const onEmitterUpdate = /* @__PURE__ */ __name((searchParams) => {
|
|
470
564
|
applyChange2(searchParams, true);
|
|
471
565
|
}, "onEmitterUpdate");
|
|
472
|
-
|
|
566
|
+
const unsubscribeHistory = subscribeHistoryChange(onPopState);
|
|
473
567
|
emitter.on("update", onEmitterUpdate);
|
|
474
568
|
return () => {
|
|
475
|
-
|
|
569
|
+
unsubscribeHistory();
|
|
476
570
|
emitter.off("update", onEmitterUpdate);
|
|
477
571
|
};
|
|
478
572
|
}, [watchKeys, parsers]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/query/client.ts","../../src/query/sync.ts","../../src/query/params.ts","../../src/query/parsers.ts"],"sourcesContent":["/**\n * Farm.js Client-Side Query State Management\n *\n * Type-safe URL search parameter state management - Like useState, but stored in the URL query string.\n * Client-only hooks for managing query parameters in the browser.\n * Inspired by nuqs implementation patterns.\n */\n\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { emitter } from \"./sync\";\nexport { parseRouteParams, loadRouteParams, type RouteParamsInput } from \"./params\";\n\nimport { asString as asStringClient, asInteger as asIntegerClient, type Parser } from \"./parsers\";\n\nexport {\n asString,\n asInteger,\n asFloat,\n asBoolean,\n asArrayOf,\n asJson,\n asIsoDate,\n asIsoDateTime,\n createParser,\n type Parser,\n type inferParserType,\n} from \"./parsers\";\n\nexport type UrlUpdateType = \"push\" | \"replace\";\nexport type HistoryMethod = \"pushState\" | \"replaceState\";\n\nexport interface Options {\n history?: HistoryMethod;\n shallow?: boolean;\n scroll?: boolean;\n throttleMs?: number;\n}\n\nconst getCurrentSearchParams = (): URLSearchParams => {\n if (typeof window === \"undefined\") return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n};\n\nconst throttleTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst applyChange = (\n searchParams: URLSearchParams,\n updates: Record<string, string | null>,\n keepExisting = true,\n): URLSearchParams => {\n const newParams = keepExisting ? new URLSearchParams(searchParams) : new URLSearchParams();\n\n Object.entries(updates).forEach(([key, value]) => {\n if (value === null || value === undefined || value === \"\") {\n newParams.delete(key);\n } else {\n newParams.set(key, value);\n }\n });\n\n return newParams;\n};\n\nconst updateURL = (\n updates: Record<string, string | null>,\n options: Options = {},\n emitUpdate = true,\n) => {\n if (typeof window === \"undefined\") return;\n\n const { history = \"pushState\", shallow = true, scroll = false, throttleMs } = options;\n const url = new URL(window.location.href);\n\n const newSearchParams = applyChange(new URLSearchParams(url.search), updates);\n const newSearch = newSearchParams.toString();\n const newUrl = url.pathname + (newSearch ? `?${newSearch}` : \"\") + url.hash;\n const currentUrl =\n window.location.pathname + (window.location.search || \"\") + window.location.hash;\n\n if (newUrl !== currentUrl) {\n const update = () => {\n if (history === \"replaceState\") {\n window.history.replaceState(null, \"\", newUrl);\n } else {\n window.history.pushState(null, \"\", newUrl);\n }\n\n if (emitUpdate) {\n const actualSearchParams = new URLSearchParams(window.location.search);\n emitter.emitUpdate(actualSearchParams);\n }\n\n if (shallow) {\n setTimeout(() => {\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n }, 0);\n }\n\n if (scroll) {\n window.scrollTo(0, 0);\n }\n };\n\n if (throttleMs && throttleMs > 0) {\n const throttleKey = Object.keys(updates).sort().join(\",\");\n\n const existingTimeout = throttleTimers.get(throttleKey);\n if (existingTimeout) {\n clearTimeout(existingTimeout);\n }\n\n const timeout = setTimeout(() => {\n update();\n throttleTimers.delete(throttleKey);\n }, throttleMs);\n\n throttleTimers.set(throttleKey, timeout);\n } else {\n update();\n }\n }\n};\n\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n];\nexport function useQueryState<T>(\n key: string,\n parser: Parser<T>,\n options?: Options,\n): [T | null, (value: T | null) => void];\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n] {\n type T = NonNullable<ReturnType<TParser[\"parse\"]>>;\n const [state, setState] = useState<T | null>(() => {\n if (typeof window === \"undefined\") return parser.parse(\"\");\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n return parsed;\n });\n\n const stateRef = useRef(state);\n const isInternalUpdateRef = useRef(false);\n stateRef.current = state;\n\n const setValue = useCallback(\n (value: T | null) => {\n isInternalUpdateRef.current = true;\n\n setState(value);\n stateRef.current = value;\n\n const serialized = value === null ? null : parser.serialize(value);\n\n emitter.emitKey(key, { state: value, query: serialized });\n\n updateURL({ [key]: serialized }, options, true);\n\n setTimeout(() => {\n isInternalUpdateRef.current = false;\n }, 10);\n },\n [key, parser, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onKeyUpdate = (payload: { state: any; query: string | null }) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n if (!Object.is(stateRef.current, payload.state)) {\n setState(payload.state);\n stateRef.current = payload.state;\n }\n };\n\n window.addEventListener(\"popstate\", onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n emitter.onKey(key, onKeyUpdate);\n\n return () => {\n window.removeEventListener(\"popstate\", onPopState);\n emitter.off(\"update\", onEmitterUpdate);\n emitter.offKey(key, onKeyUpdate);\n };\n }, [key, parser]);\n\n return [state, setValue];\n}\n\nexport function useQueryStates<T extends Record<string, Parser<any>>>(\n parsers: T,\n options: Options = {},\n): [\n { [K in keyof T]: ReturnType<T[K][\"parse\"]> },\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => void,\n] {\n const keys = Object.keys(parsers);\n const watchKeys = keys.join(\"&\");\n\n const [state, setState] = useState<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> }>(() => {\n const searchParams =\n typeof window === \"undefined\" ? new URLSearchParams() : getCurrentSearchParams();\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n result[key as keyof T] = parser.parse(value ?? \"\");\n });\n\n return result;\n });\n\n const stateRef = useRef(state);\n stateRef.current = state;\n\n const setValues = useCallback(\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => {\n const newState = { ...stateRef.current, ...updates };\n setState(newState);\n stateRef.current = newState;\n\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n emitter.emitKey(key, { state: value, query: serialized });\n }\n });\n\n const urlUpdates: Record<string, string | null> = {};\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n urlUpdates[key] = serialized;\n }\n });\n\n updateURL(urlUpdates, options, true);\n },\n [parsers, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const applyChange = (searchParams: URLSearchParams, fromEmitter = false) => {\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n let hasChanged = false;\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n const currentValue = stateRef.current[key as keyof T];\n\n if (!Object.is(currentValue, parsed)) {\n hasChanged = true;\n }\n result[key as keyof T] = parsed;\n });\n\n if (hasChanged) {\n setState(result);\n stateRef.current = result;\n }\n };\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n applyChange(searchParams, false);\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n applyChange(searchParams, true);\n };\n\n window.addEventListener(\"popstate\", onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n\n return () => {\n window.removeEventListener(\"popstate\", onPopState);\n emitter.off(\"update\", onEmitterUpdate);\n };\n }, [watchKeys, parsers]);\n\n return [state, setValues];\n}\n\nexport function usePagination(options?: {\n defaultPage?: number;\n defaultLimit?: number;\n pageKey?: string;\n limitKey?: string;\n}) {\n const {\n defaultPage = 1,\n defaultLimit = 10,\n pageKey = \"page\",\n limitKey = \"limit\",\n } = options || {};\n\n const [page, setPage] = useQueryState(pageKey, asIntegerClient.withDefault!(defaultPage));\n const [limit, setLimit] = useQueryState(limitKey, asIntegerClient.withDefault!(defaultLimit));\n\n const currentPage = page ?? defaultPage;\n const currentLimit = limit ?? defaultLimit;\n const offset = ((currentPage as number) - 1) * (currentLimit as number);\n\n return {\n page: currentPage,\n setPage,\n limit: currentLimit,\n setLimit,\n offset,\n resetPagination: () => {\n setPage(defaultPage);\n setLimit(defaultLimit);\n },\n };\n}\n\nexport function useSearchFilters<T extends Record<string, any>>(options?: {\n searchKey?: string;\n defaultFilters?: T;\n filterKeys?: (keyof T)[];\n}) {\n const {\n searchKey = \"search\",\n defaultFilters = {} as T,\n filterKeys = Object.keys(defaultFilters) as (keyof T)[],\n } = options || {};\n\n const [search, setSearch] = useQueryState(searchKey, asStringClient);\n\n const filterParsers = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = asStringClient;\n return acc;\n },\n {} as Record<string, Parser<string>>,\n );\n\n const [filters, setFilters] = useQueryStates(filterParsers);\n\n const clearFilters = () => {\n setSearch(null);\n const clearedFilters = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = null;\n return acc;\n },\n {} as Record<string, null>,\n );\n setFilters(clearedFilters);\n };\n\n return {\n search,\n setSearch,\n filters: filters as T,\n setFilters,\n clearFilters,\n };\n}\n","/**\n * Cross-hook synchronization system for query state\n * Inspired by nuqs implementation\n */\n\ntype Listener = (searchParams: URLSearchParams) => void;\ntype KeyListener = (payload: { state: any; query: string | null }) => void;\n\nclass Emitter {\n private listeners: Map<string, Set<Listener>> = new Map();\n private keyListeners: Map<string, Set<KeyListener>> = new Map();\n\n /**\n * Emit a global update event (all hooks should sync)\n */\n emitUpdate(searchParams: URLSearchParams): void {\n const listeners = this.listeners.get(\"update\") || new Set();\n listeners.forEach((listener) => listener(searchParams));\n }\n\n /**\n * Emit a key-specific update (for cross-hook sync)\n */\n emitKey(key: string, payload: { state: any; query: string | null }): void {\n const listeners = this.keyListeners.get(key) || new Set();\n listeners.forEach((listener) => listener(payload));\n }\n\n /**\n * Subscribe to global updates\n */\n on(event: \"update\", listener: Listener): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n }\n\n /**\n * Subscribe to key-specific updates\n */\n onKey(key: string, listener: KeyListener): void {\n if (!this.keyListeners.has(key)) {\n this.keyListeners.set(key, new Set());\n }\n this.keyListeners.get(key)!.add(listener);\n }\n\n /**\n * Unsubscribe from global updates\n */\n off(event: \"update\", listener: Listener): void {\n const listeners = this.listeners.get(event);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n\n /**\n * Unsubscribe from key-specific updates\n */\n offKey(key: string, listener: KeyListener): void {\n const listeners = this.keyListeners.get(key);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n}\n\nexport const emitter = new Emitter();\n","import type { Parser } from \"./parsers\";\n\nexport type RouteParamsInput =\n | Record<string, string | undefined>\n | URLSearchParams\n | Promise<Record<string, string | undefined> | URLSearchParams>;\n\nfunction normalizeParams(input: Record<string, string | undefined> | URLSearchParams) {\n if (input instanceof URLSearchParams) {\n const params: Record<string, string | undefined> = {};\n input.forEach((value, key) => {\n params[key] = value;\n });\n return params;\n }\n return input;\n}\n\nexport async function loadRouteParams<T extends Record<string, Parser<any>>>(\n input: RouteParamsInput,\n parsers: T,\n options: { strict?: boolean } = {},\n): Promise<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> }> {\n const resolved = await Promise.resolve(input);\n const params = normalizeParams(resolved);\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n for (const [key, parser] of Object.entries(parsers)) {\n const raw = params[key] ?? \"\";\n const parsed = parser.parse(raw);\n if (parsed === null && options.strict) {\n throw new Error(`Failed to parse route param \"${key}\" with value \"${raw}\"`);\n }\n result[key as keyof T] = parsed as ReturnType<T[typeof key][\"parse\"]>;\n }\n\n return result;\n}\n\nexport function parseRouteParams<T extends Record<string, Parser<any>>>(\n input: Record<string, string | undefined> | URLSearchParams,\n parsers: T,\n options: { strict?: boolean } = {},\n): { [K in keyof T]: ReturnType<T[K][\"parse\"]> } {\n const params = normalizeParams(input);\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n for (const [key, parser] of Object.entries(parsers)) {\n const raw = params[key] ?? \"\";\n const parsed = parser.parse(raw);\n if (parsed === null && options.strict) {\n throw new Error(`Failed to parse route param \"${key}\" with value \"${raw}\"`);\n }\n result[key as keyof T] = parsed as ReturnType<T[typeof key][\"parse\"]>;\n }\n\n return result;\n}\n","/**\n * Farm.js Query State Parsers\n *\n * Shared parsers that can be used in both client and server contexts\n */\n\nexport interface Parser<T> {\n parse: (value: string) => T | null;\n serialize: (value: T) => string;\n withDefault?: (defaultValue: T) => Parser<T>;\n}\n\n// String parser\nexport const asString: Parser<string> = {\n parse: (value: string) => (value && value.trim()) || null,\n serialize: (value: string) => value,\n withDefault: (defaultValue: string) => ({\n parse: (value: string) => (value && value.trim()) || defaultValue,\n serialize: (value: string) => value || defaultValue,\n }),\n};\n\n// Integer parser\nexport const asInteger: Parser<number> = {\n parse: (value: string) => {\n const parsed = Number.parseInt(value, 10);\n return isNaN(parsed) ? null : parsed;\n },\n serialize: (value: number) => value.toString(),\n withDefault: (defaultValue: number) => ({\n parse: (value: string) => {\n const parsed = Number.parseInt(value, 10);\n return isNaN(parsed) ? defaultValue : parsed;\n },\n serialize: (value: number) => value.toString(),\n }),\n};\n\n// Float parser\nexport const asFloat: Parser<number> = {\n parse: (value: string) => {\n const parsed = Number.parseFloat(value);\n return isNaN(parsed) ? null : parsed;\n },\n serialize: (value: number) => value.toString(),\n withDefault: (defaultValue: number) => ({\n parse: (value: string) => {\n const parsed = Number.parseFloat(value);\n return isNaN(parsed) ? defaultValue : parsed;\n },\n serialize: (value: number) => value.toString(),\n }),\n};\n\n// Boolean parser\nexport const asBoolean: Parser<boolean> = {\n parse: (value: string) => {\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n return null;\n },\n serialize: (value: boolean) => value.toString(),\n withDefault: (defaultValue: boolean) => ({\n parse: (value: string) => {\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n return defaultValue;\n },\n serialize: (value: boolean) => value.toString(),\n }),\n};\n\n// Array parser\nexport function asArrayOf<T>(itemParser: Parser<T>): Parser<T[]> {\n return {\n parse: (value: string) => {\n if (!value) return null;\n return value\n .split(\",\")\n .filter(Boolean)\n .map((item) => itemParser.parse(item.trim()))\n .filter((item) => item !== null) as T[];\n },\n serialize: (value: T[]) => value.map((item) => itemParser.serialize(item)).join(\",\"),\n withDefault: (defaultValue: T[]) => ({\n parse: (value: string) => {\n if (!value) return defaultValue;\n return value\n .split(\",\")\n .filter(Boolean)\n .map((item) => itemParser.parse(item.trim()))\n .filter((item) => item !== null) as T[];\n },\n serialize: (value: T[]) => value.map((item) => itemParser.serialize(item)).join(\",\"),\n }),\n };\n}\n\n// JSON parser\nexport function asJson<T>(): Parser<T> {\n return {\n parse: (value: string) => {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n },\n serialize: (value: T) => JSON.stringify(value),\n withDefault: (defaultValue: T) => ({\n parse: (value: string) => {\n try {\n return JSON.parse(value);\n } catch {\n return defaultValue;\n }\n },\n serialize: (value: T) => JSON.stringify(value),\n }),\n };\n}\n\n// ISO Date parser\nexport const asIsoDate: Parser<Date> = {\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? null : date;\n },\n serialize: (value: Date) => value.toISOString(),\n withDefault: (defaultValue: Date) => ({\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? defaultValue : date;\n },\n serialize: (value: Date) => value.toISOString(),\n }),\n};\n\n// ISO DateTime parser\nexport const asIsoDateTime: Parser<Date> = {\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? null : date;\n },\n serialize: (value: Date) => value.toISOString(),\n withDefault: (defaultValue: Date) => ({\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? defaultValue : date;\n },\n serialize: (value: Date) => value.toISOString(),\n }),\n};\n\n// Custom parser creator\nexport function createParser<T>(config: {\n parse: (value: string) => T | null;\n serialize: (value: T) => string;\n}): Parser<T> {\n return {\n parse: config.parse,\n serialize: config.serialize,\n withDefault: (defaultValue: T) => ({\n parse: (value: string) => config.parse(value) ?? defaultValue,\n serialize: config.serialize,\n }),\n };\n}\n\n// Type inference helper\nexport type inferParserType<T> = T extends Parser<infer U> ? U : never;\n"],"mappings":"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","names":["applyChange"]}
|
|
1
|
+
{"version":3,"sources":["../../src/query/client.ts","../../src/i18n/client-runtime.ts","../../src/i18n/bridge.ts","../../src/client/spa-router.ts","../../src/client/history-sync.ts","../../src/query/sync.ts","../../src/query/params.ts","../../src/query/parsers.ts"],"sourcesContent":["/**\n * Farm.js Client-Side Query State Management\n *\n * Type-safe URL search parameter state management - Like useState, but stored in the URL query string.\n * Client-only hooks for managing query parameters in the browser.\n * Inspired by nuqs implementation patterns.\n */\n\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { notifyHistoryChange, subscribeHistoryChange } from \"../client/history-sync\";\nimport { emitter } from \"./sync\";\nexport { parseRouteParams, loadRouteParams, type RouteParamsInput } from \"./params\";\n\nimport { asString as asStringClient, asInteger as asIntegerClient, type Parser } from \"./parsers\";\n\nexport {\n asString,\n asInteger,\n asFloat,\n asBoolean,\n asArrayOf,\n asJson,\n asIsoDate,\n asIsoDateTime,\n createParser,\n type Parser,\n type inferParserType,\n} from \"./parsers\";\n\nexport type UrlUpdateType = \"push\" | \"replace\";\nexport type HistoryMethod = \"pushState\" | \"replaceState\";\n\nexport interface Options {\n history?: HistoryMethod;\n shallow?: boolean;\n scroll?: boolean;\n throttleMs?: number;\n}\n\nconst getCurrentSearchParams = (): URLSearchParams => {\n if (typeof window === \"undefined\") return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n};\n\nconst throttleTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst applyChange = (\n searchParams: URLSearchParams,\n updates: Record<string, string | null>,\n keepExisting = true,\n): URLSearchParams => {\n const newParams = keepExisting ? new URLSearchParams(searchParams) : new URLSearchParams();\n\n Object.entries(updates).forEach(([key, value]) => {\n if (value === null || value === undefined || value === \"\") {\n newParams.delete(key);\n } else {\n newParams.set(key, value);\n }\n });\n\n return newParams;\n};\n\nconst updateURL = (\n updates: Record<string, string | null>,\n options: Options = {},\n emitUpdate = true,\n) => {\n if (typeof window === \"undefined\") return;\n\n const { history = \"pushState\", shallow = true, scroll = false, throttleMs } = options;\n const url = new URL(window.location.href);\n\n const newSearchParams = applyChange(new URLSearchParams(url.search), updates);\n const newSearch = newSearchParams.toString();\n const newUrl = url.pathname + (newSearch ? `?${newSearch}` : \"\") + url.hash;\n const currentUrl =\n window.location.pathname + (window.location.search || \"\") + window.location.hash;\n\n if (newUrl !== currentUrl) {\n const update = () => {\n // Preserve Farm's history state (page state, interception markers) and\n // keep its recorded path in sync with the new query string so pops\n // report the entry's real location.\n const historyState = window.history.state;\n const nextHistoryState =\n historyState && typeof historyState === \"object\" && \"path\" in historyState\n ? { ...historyState, path: newUrl }\n : historyState;\n\n if (history === \"replaceState\") {\n window.history.replaceState(nextHistoryState, \"\", newUrl);\n } else {\n window.history.pushState(nextHistoryState, \"\", newUrl);\n }\n\n if (emitUpdate) {\n const actualSearchParams = new URLSearchParams(window.location.search);\n emitter.emitUpdate(actualSearchParams);\n }\n\n if (shallow) notifyHistoryChange(\"url-search\");\n\n if (scroll) {\n window.scrollTo(0, 0);\n }\n };\n\n if (throttleMs && throttleMs > 0) {\n const throttleKey = Object.keys(updates).sort().join(\",\");\n\n const existingTimeout = throttleTimers.get(throttleKey);\n if (existingTimeout) {\n clearTimeout(existingTimeout);\n }\n\n const timeout = setTimeout(() => {\n update();\n throttleTimers.delete(throttleKey);\n }, throttleMs);\n\n throttleTimers.set(throttleKey, timeout);\n } else {\n update();\n }\n }\n};\n\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n];\nexport function useQueryState<T>(\n key: string,\n parser: Parser<T>,\n options?: Options,\n): [T | null, (value: T | null) => void];\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n] {\n type T = NonNullable<ReturnType<TParser[\"parse\"]>>;\n const [state, setState] = useState<T | null>(() => {\n if (typeof window === \"undefined\") return parser.parse(\"\");\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n return parsed;\n });\n\n const stateRef = useRef(state);\n const isInternalUpdateRef = useRef(false);\n stateRef.current = state;\n\n const setValue = useCallback(\n (value: T | null) => {\n isInternalUpdateRef.current = true;\n\n setState(value);\n stateRef.current = value;\n\n const serialized = value === null ? null : parser.serialize(value);\n\n emitter.emitKey(key, { state: value, query: serialized });\n\n updateURL({ [key]: serialized }, options, true);\n\n setTimeout(() => {\n isInternalUpdateRef.current = false;\n }, 10);\n },\n [key, parser, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onKeyUpdate = (payload: { state: any; query: string | null }) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n if (!Object.is(stateRef.current, payload.state)) {\n setState(payload.state);\n stateRef.current = payload.state;\n }\n };\n\n // Page-state writes with an href can change the query string without a\n // popstate, so listen through the shared history channel (real\n // back/forward events included). Self-updates no-op via the Object.is\n // comparison above.\n const unsubscribeHistory = subscribeHistoryChange(onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n emitter.onKey(key, onKeyUpdate);\n\n return () => {\n unsubscribeHistory();\n emitter.off(\"update\", onEmitterUpdate);\n emitter.offKey(key, onKeyUpdate);\n };\n }, [key, parser]);\n\n return [state, setValue];\n}\n\nexport function useQueryStates<T extends Record<string, Parser<any>>>(\n parsers: T,\n options: Options = {},\n): [\n { [K in keyof T]: ReturnType<T[K][\"parse\"]> },\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => void,\n] {\n const keys = Object.keys(parsers);\n const watchKeys = keys.join(\"&\");\n\n const [state, setState] = useState<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> }>(() => {\n const searchParams =\n typeof window === \"undefined\" ? new URLSearchParams() : getCurrentSearchParams();\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n result[key as keyof T] = parser.parse(value ?? \"\");\n });\n\n return result;\n });\n\n const stateRef = useRef(state);\n stateRef.current = state;\n\n const setValues = useCallback(\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => {\n const newState = { ...stateRef.current, ...updates };\n setState(newState);\n stateRef.current = newState;\n\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n emitter.emitKey(key, { state: value, query: serialized });\n }\n });\n\n const urlUpdates: Record<string, string | null> = {};\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n urlUpdates[key] = serialized;\n }\n });\n\n updateURL(urlUpdates, options, true);\n },\n [parsers, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const applyChange = (searchParams: URLSearchParams, fromEmitter = false) => {\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n let hasChanged = false;\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n const currentValue = stateRef.current[key as keyof T];\n\n if (!Object.is(currentValue, parsed)) {\n hasChanged = true;\n }\n result[key as keyof T] = parsed;\n });\n\n if (hasChanged) {\n setState(result);\n stateRef.current = result;\n }\n };\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n applyChange(searchParams, false);\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n applyChange(searchParams, true);\n };\n\n const unsubscribeHistory = subscribeHistoryChange(onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n\n return () => {\n unsubscribeHistory();\n emitter.off(\"update\", onEmitterUpdate);\n };\n }, [watchKeys, parsers]);\n\n return [state, setValues];\n}\n\nexport function usePagination(options?: {\n defaultPage?: number;\n defaultLimit?: number;\n pageKey?: string;\n limitKey?: string;\n}) {\n const {\n defaultPage = 1,\n defaultLimit = 10,\n pageKey = \"page\",\n limitKey = \"limit\",\n } = options || {};\n\n const [page, setPage] = useQueryState(pageKey, asIntegerClient.withDefault!(defaultPage));\n const [limit, setLimit] = useQueryState(limitKey, asIntegerClient.withDefault!(defaultLimit));\n\n const currentPage = page ?? defaultPage;\n const currentLimit = limit ?? defaultLimit;\n const offset = ((currentPage as number) - 1) * (currentLimit as number);\n\n return {\n page: currentPage,\n setPage,\n limit: currentLimit,\n setLimit,\n offset,\n resetPagination: () => {\n setPage(defaultPage);\n setLimit(defaultLimit);\n },\n };\n}\n\nexport function useSearchFilters<T extends Record<string, any>>(options?: {\n searchKey?: string;\n defaultFilters?: T;\n filterKeys?: (keyof T)[];\n}) {\n const {\n searchKey = \"search\",\n defaultFilters = {} as T,\n filterKeys = Object.keys(defaultFilters) as (keyof T)[],\n } = options || {};\n\n const [search, setSearch] = useQueryState(searchKey, asStringClient);\n\n const filterParsers = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = asStringClient;\n return acc;\n },\n {} as Record<string, Parser<string>>,\n );\n\n const [filters, setFilters] = useQueryStates(filterParsers);\n\n const clearFilters = () => {\n setSearch(null);\n const clearedFilters = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = null;\n return acc;\n },\n {} as Record<string, null>,\n );\n setFilters(clearedFilters);\n };\n\n return {\n search,\n setSearch,\n filters: filters as T,\n setFilters,\n clearFilters,\n };\n}\n","import IntlMessageFormat from \"intl-messageformat\";\nimport { getActiveFarmI18nSnapshot } from \"./bridge\";\nimport { localizeFarmHref, resolveFarmLocalePath } from \"./routing\";\nimport type {\n FarmI18nClientSnapshot,\n FarmI18nLocale,\n FarmI18nMessageArgs,\n FarmI18nMessageKey,\n FarmTranslator,\n} from \"./types\";\n\ntype Listener = () => void;\n\nconst listeners = new Set<Listener>();\nconst compiled = new Map<string, IntlMessageFormat>();\nlet currentSnapshot: FarmI18nClientSnapshot | undefined;\n\nexport function getFarmI18nClientState(): FarmI18nClientSnapshot | undefined {\n return currentSnapshot || getActiveFarmI18nSnapshot();\n}\n\nexport function subscribeFarmI18n(listener: Listener): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\nexport function _hydrateFarmI18n(snapshot: FarmI18nClientSnapshot | undefined): void {\n if (!snapshot) return;\n const changed = currentSnapshot?.locale !== snapshot.locale;\n currentSnapshot = snapshot;\n if (typeof window !== \"undefined\") window.__FARM_I18N__ = snapshot;\n if (typeof document !== \"undefined\") {\n document.documentElement.lang = snapshot.locale;\n document.documentElement.dir = snapshot.direction;\n }\n if (changed) compiled.clear();\n for (const listener of listeners) listener();\n}\n\nconst translate = (key: string, values?: Record<string, unknown>, rich = false): unknown => {\n const snapshot = requireSnapshot();\n const message = snapshot.messages[key];\n if (message === undefined) return key;\n const cacheKey = `${snapshot.locale}\\u0000${key}\\u0000${message}`;\n let formatter = compiled.get(cacheKey);\n if (!formatter) {\n formatter = new IntlMessageFormat(message, snapshot.locale);\n compiled.set(cacheKey, formatter);\n }\n const result = formatter.format(values as any);\n if (rich) return Array.isArray(result) && result.length === 1 ? result[0] : result;\n if (Array.isArray(result) && result.some((part) => typeof part !== \"string\")) {\n throw new Error(`Farm i18n message \"${key}\" contains rich content. Render it with t.rich().`);\n }\n return Array.isArray(result) ? result.join(\"\") : String(result);\n};\n\nexport const t = ((key: string, values?: Record<string, unknown>) =>\n translate(key, values)) as FarmTranslator;\nt.rich = (key: string, values?: Record<string, unknown>) => translate(key, values, true);\nt.raw = (key: string) => requireSnapshot().messages[key] ?? key;\nt.has = (key: string) => key in requireSnapshot().messages;\n\nexport function localizeActiveFarmHref(href: string, locale?: FarmI18nLocale): string {\n const snapshot = getFarmI18nClientState();\n if (!snapshot) return href;\n return localizeFarmHref(href, locale || snapshot.locale, snapshot);\n}\n\nexport function isFarmLocaleChangeHref(href: string): boolean {\n const snapshot = getFarmI18nClientState();\n if (!snapshot || typeof window === \"undefined\") return false;\n if (snapshot.routing === \"none\") return false;\n const target = new URL(href, window.location.origin);\n const match = resolveFarmLocalePath(target.pathname, snapshot);\n const targetLocale = match.locale || snapshot.defaultLocale;\n return targetLocale !== snapshot.locale;\n}\n\nexport function createFarmClientTranslator(): FarmTranslator {\n return (<TKey extends FarmI18nMessageKey>(key: TKey, ...args: FarmI18nMessageArgs<TKey>) =>\n t(key, ...args)) as FarmTranslator;\n}\n\nfunction requireSnapshot(): FarmI18nClientSnapshot {\n const snapshot = getFarmI18nClientState();\n if (!snapshot) {\n throw new Error(\"Farm i18n is not configured or has not been hydrated.\");\n }\n return snapshot;\n}\n","import type { FarmI18nClientSnapshot } from \"./types\";\n\ntype SnapshotResolver = () => FarmI18nClientSnapshot | undefined;\n\nconst FARM_I18N_SNAPSHOT_RESOLVER = Symbol.for(\"farm.i18n.snapshotResolver\");\ntype GlobalWithI18nResolver = typeof globalThis & {\n [FARM_I18N_SNAPSHOT_RESOLVER]?: SnapshotResolver;\n};\n\nexport function _setFarmI18nSnapshotResolver(resolver: SnapshotResolver | undefined): void {\n (globalThis as GlobalWithI18nResolver)[FARM_I18N_SNAPSHOT_RESOLVER] = resolver;\n}\n\nexport function getActiveFarmI18nSnapshot(): FarmI18nClientSnapshot | undefined {\n if (typeof window !== \"undefined\" && window.__FARM_I18N__) {\n return window.__FARM_I18N__;\n }\n return (globalThis as GlobalWithI18nResolver)[FARM_I18N_SNAPSHOT_RESOLVER]?.();\n}\n","\"use client\";\n\nimport { readDeferredDataResponse } from \"../deferred\";\nimport { notifyHistoryChange } from \"./history-sync\";\nimport {\n createFarmDeploymentMismatchError,\n createFarmDeploymentRequestHeaders,\n isFarmDeploymentMismatchResponse,\n} from \"../deployment\";\nimport type { FarmClientNavigationSession, FarmClientPluginManager } from \"./plugin\";\nimport { _hydrateFarmI18n, isFarmLocaleChangeHref } from \"../i18n/client-runtime\";\nimport type { FarmI18nClientSnapshot } from \"../i18n/types\";\nimport type { FarmIslandStrategy } from \"../island\";\nimport type { FarmRouteRenderPlan } from \"../navigation/render-plan\";\n\n/**\n * Farm.js SPA Router\n *\n * Provides client-side navigation without full page reloads.\n * Features:\n * - Prefetch on viewport intersection\n * - Prefetch on hover\n * - Page caching\n * - History API integration\n * - Scroll restoration\n */\n\ninterface PageData {\n props: Record<string, any>;\n modulePath: string;\n loadingModulePath?: string | null;\n canonicalPath?: string;\n isClientComponent?: boolean;\n shouldHydrate?: boolean;\n islandStrategy?: FarmIslandStrategy | null;\n renderPlan?: FarmRouteRenderPlan;\n fragment?: {\n html: string;\n layoutPatterns: string[];\n };\n metadata?: {\n title?: string;\n description?: string;\n };\n layoutModules?: string[];\n routeSlots?: RouteSlotPageData[];\n interception?: {\n from?: string;\n slots: string[];\n };\n i18n?: FarmI18nClientSnapshot;\n}\n\ninterface RouteSlotPageData {\n name: string;\n ownerPattern: string;\n containerId: string;\n interception: boolean;\n fallback: boolean;\n modulePath: string;\n isClientComponent: boolean;\n shouldHydrate: boolean;\n props: Record<string, any>;\n}\n\nexport interface RouterOptions {\n prefetchTimeout?: number;\n cacheMaxAge?: number;\n scrollRestoration?: boolean;\n shouldUseDocumentNavigation?: (pathname: string) => boolean;\n deploymentId?: string;\n}\n\ninterface CacheEntry {\n data: PageData;\n timestamp: number;\n}\n\nexport interface FarmNavigationBlockerContext {\n from: string;\n to: string;\n action: \"push\" | \"replace\" | \"pop\";\n}\n\nexport type FarmNavigationBlocker = (\n context: FarmNavigationBlockerContext,\n) => boolean | void | Promise<boolean | void>;\n\nexport type FarmViewTransitionMode = boolean | \"auto\";\n\nexport interface FarmNavigateOptions {\n replace?: boolean;\n scroll?: boolean;\n state?: unknown;\n viewTransition?: FarmViewTransitionMode;\n /** Re-fetch the current route even when its URL has not changed. */\n refresh?: boolean;\n}\n\nexport interface FarmNavigationLocation {\n href: string;\n pathname: string;\n search: string;\n hash: string;\n}\n\nexport interface FarmNavigationState {\n state: \"idle\" | \"loading\";\n pending: boolean;\n from: string | null;\n to: FarmNavigationLocation | null;\n action: FarmNavigationBlockerContext[\"action\"] | null;\n startedAt: number | null;\n}\n\nexport type FarmNavigationListener = (state: FarmNavigationState) => void;\n\nconst FARM_PAGE_STATE_KEY = \"__farmPageState\";\nconst FARM_INTERCEPT_FROM_KEY = \"__farmInterceptFrom\";\nconst IDLE_NAVIGATION_STATE: FarmNavigationState = {\n state: \"idle\",\n pending: false,\n from: null,\n to: null,\n action: null,\n startedAt: null,\n};\n\nfunction getActiveLayoutChainHeader(): string | undefined {\n if (typeof document === \"undefined\") return undefined;\n const patterns = Array.from(\n document.querySelectorAll<HTMLElement>('[data-farm-layout-boundary=\"true\"]'),\n )\n .map((element) => element.dataset.farmLayoutPattern)\n .filter((pattern): pattern is string => Boolean(pattern));\n return patterns.length > 0 ? JSON.stringify(patterns) : undefined;\n}\n\n// Global router instance\nlet routerInstance: SPARouter | null = null;\n\nexport class SPARouter {\n private cache: Map<string, CacheEntry> = new Map();\n private prefetchingUrls: Set<string> = new Set();\n private observers: Map<Element, IntersectionObserver> = new Map();\n private blockers: Set<FarmNavigationBlocker> = new Set();\n private navigationListeners: Set<FarmNavigationListener> = new Set();\n private navigationState: FarmNavigationState = IDLE_NAVIGATION_STATE;\n private scrollElements: Map<string, HTMLElement> = new Map();\n private options: Required<Omit<RouterOptions, \"deploymentId\">> &\n Pick<RouterOptions, \"deploymentId\">;\n private onNavigate?: (data: PageData) => Promise<void>;\n private clientPlugins?: Pick<\n FarmClientPluginManager,\n | \"beginNavigation\"\n | \"markNavigationLoaded\"\n | \"resolveNavigation\"\n | \"scheduleNavigationRendered\"\n | \"failNavigation\"\n >;\n\n private readonly onPopState = (event: PopStateEvent) => {\n void this.handlePopState(event);\n };\n\n private readonly onBeforeUnload = (event: BeforeUnloadEvent) => {\n if (this.blockers.size > 0) {\n event.preventDefault();\n event.returnValue = \"\";\n }\n this.saveScrollPosition(window.location.pathname);\n };\n\n constructor(options: RouterOptions = {}) {\n this.options = {\n prefetchTimeout: options.prefetchTimeout ?? 100,\n cacheMaxAge: options.cacheMaxAge ?? 30000, // 30 seconds\n scrollRestoration: options.scrollRestoration ?? true,\n shouldUseDocumentNavigation: options.shouldUseDocumentNavigation ?? (() => false),\n deploymentId: options.deploymentId ?? readBrowserDeploymentId(),\n };\n\n if (typeof window !== \"undefined\") {\n // Listen for popstate (back/forward navigation)\n window.addEventListener(\"popstate\", this.onPopState);\n\n // Save scroll position before unload\n window.addEventListener(\"beforeunload\", this.onBeforeUnload);\n }\n }\n\n /** Remove global listeners. Intended for tests and teardown. */\n destroy(): void {\n if (typeof window === \"undefined\") return;\n window.removeEventListener(\"popstate\", this.onPopState);\n window.removeEventListener(\"beforeunload\", this.onBeforeUnload);\n }\n\n /**\n * Set the navigation handler that updates React\n */\n setNavigationHandler(handler: (data: PageData) => Promise<void>) {\n this.onNavigate = handler;\n }\n\n /**\n * Connect Farm's browser plugin lifecycle to SPA navigation.\n */\n setClientPluginManager(manager: FarmClientPluginManager) {\n this.clientPlugins = manager;\n }\n\n /**\n * Navigate to a new URL\n */\n async navigate(href: string, options: FarmNavigateOptions = {}): Promise<void> {\n const {\n replace = false,\n scroll = true,\n state,\n viewTransition = false,\n refresh = false,\n } = options;\n\n // Parse the URL\n const url = new URL(href, window.location.origin);\n const pathname = url.pathname;\n const search = url.search;\n const fullPath = pathname + search;\n\n if (\n isFarmLocaleChangeHref(url.toString()) ||\n this.options.shouldUseDocumentNavigation(pathname)\n ) {\n if (replace) window.location.replace(url.toString());\n else window.location.assign(url.toString());\n return;\n }\n\n // Same page navigation - just update hash/scroll\n if (!refresh && pathname === window.location.pathname && search === window.location.search) {\n if (url.hash) {\n window.location.hash = url.hash;\n }\n return;\n }\n\n const from = window.location.pathname + window.location.search;\n if (\n await this.shouldBlockNavigation({\n from,\n to: fullPath,\n action: replace ? \"replace\" : \"push\",\n })\n ) {\n return;\n }\n\n // Save current scroll position\n if (this.options.scrollRestoration) {\n this.saveScrollPosition(window.location.pathname);\n }\n\n this.startNavigation({\n from,\n to: createNavigationLocation(url),\n action: replace ? \"replace\" : \"push\",\n });\n\n let clientNavigation: FarmClientNavigationSession | undefined;\n try {\n clientNavigation = await this.clientPlugins?.beginNavigation({\n from,\n to: url,\n action: replace ? \"replace\" : \"push\",\n });\n\n // Fetch page data (from cache or server)\n const pageData = await this.fetchPageData(fullPath, true, from, refresh);\n if (clientNavigation) {\n await this.clientPlugins?.markNavigationLoaded(clientNavigation, pageData);\n }\n\n await this.runViewTransition(viewTransition, () =>\n this.commitNavigation({\n fullPath,\n pageData,\n pathname,\n replace,\n scroll,\n state,\n url,\n }),\n );\n\n if (clientNavigation) {\n await this.clientPlugins?.resolveNavigation(clientNavigation);\n void this.clientPlugins?.scheduleNavigationRendered(clientNavigation);\n }\n this.finishNavigation();\n } catch (error) {\n if (clientNavigation) {\n await this.clientPlugins?.failNavigation(clientNavigation, error);\n }\n this.finishNavigation();\n console.error(\"[Farm.js] Navigation error:\", error);\n // Fall back to full page navigation\n window.location.href = href;\n }\n }\n\n /**\n * Re-render the active route from the server while preserving the document,\n * shared layouts, client runtime, and browser history entry.\n */\n refresh(options: Omit<FarmNavigateOptions, \"replace\" | \"refresh\"> = {}): Promise<void> {\n return this.navigate(window.location.href, {\n ...options,\n replace: true,\n refresh: true,\n scroll: options.scroll ?? false,\n });\n }\n\n /**\n * Prefetch a URL\n */\n async prefetch(href: string): Promise<void> {\n if (isFarmLocaleChangeHref(href)) return;\n const url = new URL(href, window.location.origin);\n const fullPath = url.pathname + url.search;\n const interceptFrom = window.location.pathname + window.location.search;\n\n // Skip if already prefetching or cached\n if (this.prefetchingUrls.has(fullPath)) return;\n if (this.isCached(fullPath, interceptFrom)) return;\n\n this.prefetchingUrls.add(fullPath);\n\n try {\n await this.fetchPageData(fullPath, false, interceptFrom);\n } catch (error) {\n console.warn(\"[Farm.js] Prefetch failed:\", href, error);\n } finally {\n this.prefetchingUrls.delete(fullPath);\n }\n }\n\n /**\n * Observe an element for viewport intersection (prefetch when visible)\n */\n observeForPrefetch(element: HTMLAnchorElement): void {\n if (typeof IntersectionObserver === \"undefined\") return;\n\n const href = element.getAttribute(\"href\");\n if (!href || this.isExternalUrl(href)) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n // Delay prefetch slightly to avoid prefetching during scroll\n setTimeout(() => {\n this.prefetch(href);\n }, this.options.prefetchTimeout);\n\n // Stop observing after first intersection\n observer.unobserve(element);\n this.observers.delete(element);\n }\n }\n },\n { rootMargin: \"200px\" }, // Start prefetching when 200px from viewport\n );\n\n observer.observe(element);\n this.observers.set(element, observer);\n }\n\n /**\n * Stop observing an element\n */\n unobserveForPrefetch(element: HTMLAnchorElement): void {\n const observer = this.observers.get(element);\n if (observer) {\n observer.unobserve(element);\n this.observers.delete(element);\n }\n }\n\n addBlocker(blocker: FarmNavigationBlocker): () => void {\n this.blockers.add(blocker);\n return () => {\n this.blockers.delete(blocker);\n };\n }\n\n getNavigationState(): FarmNavigationState {\n return this.navigationState;\n }\n\n subscribeNavigation(listener: FarmNavigationListener): () => void {\n this.navigationListeners.add(listener);\n listener(this.navigationState);\n return () => {\n this.navigationListeners.delete(listener);\n };\n }\n\n registerScrollElement(key: string, element: HTMLElement): () => void {\n this.scrollElements.set(key, element);\n this.restoreScrollElement(window.location.pathname, key, element);\n return () => {\n if (this.scrollElements.get(key) === element) {\n this.scrollElements.delete(key);\n }\n };\n }\n\n pushState(state: unknown, href?: string): void {\n this.writePageState(\"push\", state, href);\n }\n\n replaceState(state: unknown, href?: string): void {\n this.writePageState(\"replace\", state, href);\n }\n\n private async commitNavigation(options: {\n fullPath: string;\n pageData: PageData;\n pathname: string;\n replace: boolean;\n scroll: boolean;\n state: unknown;\n url: URL;\n }): Promise<void> {\n const historyPath = options.pageData.canonicalPath || options.fullPath;\n const historyState = createHistoryState(\n historyPath,\n options.state,\n undefined,\n options.pageData.interception?.from ?? null,\n );\n if (options.replace) {\n window.history.replaceState(historyState, \"\", historyPath);\n } else {\n window.history.pushState(historyState, \"\", historyPath);\n }\n\n if (options.pageData.metadata?.title) {\n document.title = options.pageData.metadata.title;\n }\n\n if (options.pageData.metadata?.description) {\n let metaDesc = document.querySelector('meta[name=\"description\"]');\n if (!metaDesc) {\n metaDesc = document.createElement(\"meta\");\n metaDesc.setAttribute(\"name\", \"description\");\n document.head.appendChild(metaDesc);\n }\n metaDesc.setAttribute(\"content\", options.pageData.metadata.description);\n }\n\n _hydrateFarmI18n(options.pageData.i18n);\n\n if (this.onNavigate) {\n await this.onNavigate(options.pageData);\n }\n\n if (options.scroll) {\n if (options.url.hash) {\n const element = document.querySelector(options.url.hash);\n if (element) {\n element.scrollIntoView();\n }\n } else {\n window.scrollTo(0, 0);\n }\n } else if (this.options.scrollRestoration) {\n this.restoreScrollPosition(options.pathname);\n }\n }\n\n private async runViewTransition(\n mode: FarmViewTransitionMode,\n callback: () => Promise<void>,\n ): Promise<void> {\n if (!mode || typeof document === \"undefined\") {\n await callback();\n return;\n }\n\n const startViewTransition = (document as any).startViewTransition;\n if (typeof startViewTransition !== \"function\") {\n await callback();\n return;\n }\n\n const transition = startViewTransition.call(document, () => callback());\n const updateDone = transition?.updateCallbackDone || transition?.ready;\n if (updateDone && typeof updateDone.then === \"function\") {\n await updateDone;\n }\n transition?.finished?.catch?.(() => undefined);\n }\n\n /**\n * Fetch page data from cache or server\n */\n private async fetchPageData(\n path: string,\n recover = true,\n interceptFrom?: string,\n fresh = false,\n ): Promise<PageData> {\n const cacheKey = getPageDataCacheKey(path, interceptFrom);\n // Check cache first\n const cached = fresh ? undefined : this.cache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < this.options.cacheMaxAge) {\n return cached.data;\n }\n\n // Fetch from server. The active layout chain lets the server omit shared\n // shell HTML from the fragment without inspecting component source.\n const activeLayoutChain = getActiveLayoutChainHeader();\n const response = await fetch(`/__farm/page-data?path=${encodeURIComponent(path)}`, {\n headers: createFarmDeploymentRequestHeaders(this.options.deploymentId, {\n Accept: \"application/x-farm-deferred+json, application/json\",\n \"X-Farm-SPA\": \"1\",\n ...(activeLayoutChain ? { \"X-Farm-Layout-Chain\": activeLayoutChain } : {}),\n ...(interceptFrom ? { \"X-Farm-Intercept-From\": interceptFrom } : {}),\n }),\n });\n\n if (isFarmDeploymentMismatchResponse(response, this.options.deploymentId)) {\n const error = createFarmDeploymentMismatchError(\n response,\n this.options.deploymentId || \"unknown\",\n );\n dispatchDeploymentMismatch(error);\n if (recover) {\n window.location.assign(path);\n }\n throw error;\n }\n\n if (!response.ok) {\n throw new Error(`Failed to fetch page data: ${response.status}`);\n }\n\n const data = await readDeferredDataResponse<PageData>(response);\n\n // Cache the result\n this.cache.set(cacheKey, {\n data,\n timestamp: Date.now(),\n });\n\n return data;\n }\n\n /**\n * Handle browser back/forward navigation\n */\n private async handlePopState(event: PopStateEvent): Promise<void> {\n if (document.documentElement.dataset.farmDocsRuntime === \"true\") return;\n\n const path = window.location.pathname + window.location.search;\n\n if (\n await this.shouldBlockNavigation({\n from: event.state?.path || path,\n to: path,\n action: \"pop\",\n })\n ) {\n return;\n }\n\n this.startNavigation({\n from: event.state?.path || path,\n to: createNavigationLocation(new URL(window.location.href)),\n action: \"pop\",\n });\n\n let clientNavigation: FarmClientNavigationSession | undefined;\n try {\n clientNavigation = await this.clientPlugins?.beginNavigation({\n from: null,\n to: window.location.href,\n action: \"pop\",\n });\n const interceptFrom =\n typeof event.state?.[FARM_INTERCEPT_FROM_KEY] === \"string\"\n ? event.state[FARM_INTERCEPT_FROM_KEY]\n : undefined;\n const pageData = await this.fetchPageData(path, true, interceptFrom);\n if (clientNavigation) {\n await this.clientPlugins?.markNavigationLoaded(clientNavigation, pageData);\n }\n\n // Update document title\n if (pageData.metadata?.title) {\n document.title = pageData.metadata.title;\n }\n\n _hydrateFarmI18n(pageData.i18n);\n\n // Call the navigation handler to update React\n if (this.onNavigate) {\n await this.onNavigate(pageData);\n }\n\n // Restore scroll position\n if (this.options.scrollRestoration) {\n this.restoreScrollPosition(window.location.pathname);\n }\n\n if (clientNavigation) {\n await this.clientPlugins?.resolveNavigation(clientNavigation);\n void this.clientPlugins?.scheduleNavigationRendered(clientNavigation);\n }\n this.finishNavigation();\n } catch (error) {\n if (clientNavigation) {\n await this.clientPlugins?.failNavigation(clientNavigation, error);\n }\n this.finishNavigation();\n console.error(\"[Farm.js] Popstate navigation error:\", error);\n // Reload the page as fallback\n window.location.reload();\n }\n }\n\n /**\n * Check if URL is cached\n */\n private isCached(path: string, interceptFrom?: string): boolean {\n const cached = this.cache.get(getPageDataCacheKey(path, interceptFrom));\n return cached !== undefined && Date.now() - cached.timestamp < this.options.cacheMaxAge;\n }\n\n /**\n * Check if URL is external\n */\n private isExternalUrl(href: string): boolean {\n return href.startsWith(\"http://\") || href.startsWith(\"https://\") || href.startsWith(\"//\");\n }\n\n private async shouldBlockNavigation(context: FarmNavigationBlockerContext): Promise<boolean> {\n for (const blocker of this.blockers) {\n if (await blocker(context)) {\n return true;\n }\n }\n return false;\n }\n\n private startNavigation(options: {\n from: string;\n to: FarmNavigationLocation;\n action: FarmNavigationBlockerContext[\"action\"];\n }): void {\n this.setNavigationState({\n state: \"loading\",\n pending: true,\n from: options.from,\n to: options.to,\n action: options.action,\n startedAt: Date.now(),\n });\n }\n\n private finishNavigation(): void {\n this.setNavigationState(IDLE_NAVIGATION_STATE);\n }\n\n private setNavigationState(state: FarmNavigationState): void {\n this.navigationState = state;\n for (const listener of this.navigationListeners) {\n listener(state);\n }\n }\n\n private writePageState(action: \"push\" | \"replace\", state: unknown, href?: string): void {\n if (typeof window === \"undefined\") return;\n\n const url = href ? new URL(href, window.location.origin).toString() : window.location.href;\n const nextState = createHistoryState(\n new URL(url).pathname + new URL(url).search,\n state,\n window.history.state,\n );\n\n if (action === \"replace\") {\n window.history.replaceState(nextState, \"\", url);\n } else {\n window.history.pushState(nextState, \"\", url);\n }\n\n notifyHistoryChange(\"page-state\");\n }\n\n /**\n * Save scroll position for a path\n */\n private saveScrollPosition(path: string): void {\n try {\n sessionStorage.setItem(\n `farm-scroll-${path}`,\n JSON.stringify({ x: window.scrollX, y: window.scrollY }),\n );\n for (const [key, element] of this.scrollElements) {\n sessionStorage.setItem(\n getScrollElementStorageKey(path, key),\n JSON.stringify({ x: element.scrollLeft, y: element.scrollTop }),\n );\n }\n } catch {\n // Ignore storage errors\n }\n }\n\n /**\n * Restore scroll position for a path\n */\n private restoreScrollPosition(path: string): void {\n try {\n const saved = sessionStorage.getItem(`farm-scroll-${path}`);\n if (saved) {\n const { x, y } = JSON.parse(saved);\n setTimeout(() => window.scrollTo(x, y), 0);\n }\n for (const [key, element] of this.scrollElements) {\n this.restoreScrollElement(path, key, element);\n }\n } catch {\n // Ignore storage errors\n }\n }\n\n private restoreScrollElement(path: string, key: string, element: HTMLElement): void {\n try {\n const saved = sessionStorage.getItem(getScrollElementStorageKey(path, key));\n if (!saved) return;\n const { x, y } = JSON.parse(saved);\n setTimeout(() => {\n element.scrollLeft = x;\n element.scrollTop = y;\n }, 0);\n } catch {\n // Ignore storage errors\n }\n }\n\n /**\n * Clear the page cache\n */\n clearCache(): void {\n this.cache.clear();\n }\n}\n\nfunction readBrowserDeploymentId(): string | undefined {\n if (typeof window === \"undefined\") return undefined;\n\n const runtimeId = (window as Window & { __FARM_DEPLOYMENT_ID__?: unknown })\n .__FARM_DEPLOYMENT_ID__;\n if (typeof runtimeId === \"string\" && runtimeId) return runtimeId;\n\n return document.querySelector<HTMLMetaElement>('meta[name=\"farm-deployment-id\"]')?.content;\n}\n\nfunction dispatchDeploymentMismatch(error: Error): void {\n if (typeof window === \"undefined\" || typeof CustomEvent === \"undefined\") return;\n window.dispatchEvent(new CustomEvent(\"farm:deployment-mismatch\", { detail: error }));\n}\n\n/**\n * Get or create the global router instance\n */\nexport function getInstalledFarmSPARouter(): SPARouter | undefined {\n if (typeof window === \"undefined\") return undefined;\n return (window as Window & { __FARM_SPA_ROUTER__?: SPARouter }).__FARM_SPA_ROUTER__;\n}\n\nexport function getRouter(): SPARouter {\n if (typeof window === \"undefined\") {\n // Return a no-op router for SSR\n return new SPARouter();\n }\n\n const installedRouter = getInstalledFarmSPARouter();\n if (installedRouter) {\n routerInstance = installedRouter;\n return installedRouter;\n }\n\n if (!routerInstance) {\n routerInstance = new SPARouter();\n }\n\n return routerInstance;\n}\n\n/**\n * Navigate to a URL using the SPA router\n */\nexport function navigateTo(href: string, options?: FarmNavigateOptions): Promise<void> {\n return getRouter().navigate(href, options);\n}\n\n/**\n * Prefetch a URL\n */\nexport function prefetch(href: string): Promise<void> {\n return getRouter().prefetch(href);\n}\n\nexport function pushState(state: unknown, href?: string): void {\n getRouter().pushState(state, href);\n}\n\nexport function replaceState(state: unknown, href?: string): void {\n getRouter().replaceState(state, href);\n}\n\nexport function readPageState<TState = unknown>(): TState | null {\n if (typeof window === \"undefined\") return null;\n const state = window.history.state;\n if (!state || typeof state !== \"object\") return null;\n return ((state as Record<string, unknown>)[FARM_PAGE_STATE_KEY] as TState | undefined) ?? null;\n}\n\nfunction createHistoryState(\n path: string,\n pageState: unknown,\n currentState?: unknown,\n interceptFrom?: string | null,\n) {\n const base =\n currentState && typeof currentState === \"object\" ? { ...(currentState as object) } : {};\n const nextState: Record<string, unknown> = {\n ...base,\n path,\n [FARM_PAGE_STATE_KEY]: pageState,\n };\n if (interceptFrom === null) {\n delete nextState[FARM_INTERCEPT_FROM_KEY];\n } else if (interceptFrom) {\n nextState[FARM_INTERCEPT_FROM_KEY] = interceptFrom;\n }\n return nextState;\n}\n\nfunction getPageDataCacheKey(path: string, interceptFrom?: string): string {\n return interceptFrom ? `${path}\\nintercept:${interceptFrom}` : path;\n}\n\nfunction createNavigationLocation(url: URL): FarmNavigationLocation {\n return {\n href: url.href,\n pathname: url.pathname,\n search: url.search,\n hash: url.hash,\n };\n}\n\nfunction getScrollElementStorageKey(path: string, key: string): string {\n return `farm-scroll-${path}:${key}`;\n}\n","import { getInstalledFarmSPARouter } from \"./spa-router\";\n\n/** Fired after programmatic history writes that must not start a navigation. */\nexport const FARM_HISTORY_CHANGE_EVENT = \"farm:historychange\";\n\nexport type FarmHistoryChangeKind = \"url-search\" | \"page-state\";\n\nexport interface FarmHistoryChangeDetail {\n kind: FarmHistoryChangeKind;\n}\n\n/**\n * Notify history observers after pushState/replaceState.\n *\n * Farm's SPA router treats `popstate` as real back/forward navigation, so a\n * dedicated event is used instead. `url-search` keeps the nuqs-style synthetic\n * `popstate` when no Farm router is installed, so standalone `useQueryState`\n * consumers still resync; `page-state` is Farm-internal and never falls back.\n */\nexport function notifyHistoryChange(kind: FarmHistoryChangeKind): void {\n if (typeof window === \"undefined\") return;\n\n setTimeout(() => {\n if (kind === \"url-search\" && !getInstalledFarmSPARouter()) {\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n return;\n }\n\n window.dispatchEvent(\n new CustomEvent<FarmHistoryChangeDetail>(FARM_HISTORY_CHANGE_EVENT, {\n detail: { kind },\n }),\n );\n }, 0);\n}\n\nexport function subscribeHistoryChange(listener: () => void): () => void {\n if (typeof window === \"undefined\") return () => {};\n\n window.addEventListener(\"popstate\", listener);\n window.addEventListener(FARM_HISTORY_CHANGE_EVENT, listener);\n\n return () => {\n window.removeEventListener(\"popstate\", listener);\n window.removeEventListener(FARM_HISTORY_CHANGE_EVENT, listener);\n };\n}\n","/**\n * Cross-hook synchronization system for query state\n * Inspired by nuqs implementation\n */\n\ntype Listener = (searchParams: URLSearchParams) => void;\ntype KeyListener = (payload: { state: any; query: string | null }) => void;\n\nclass Emitter {\n private listeners: Map<string, Set<Listener>> = new Map();\n private keyListeners: Map<string, Set<KeyListener>> = new Map();\n\n /**\n * Emit a global update event (all hooks should sync)\n */\n emitUpdate(searchParams: URLSearchParams): void {\n const listeners = this.listeners.get(\"update\") || new Set();\n listeners.forEach((listener) => listener(searchParams));\n }\n\n /**\n * Emit a key-specific update (for cross-hook sync)\n */\n emitKey(key: string, payload: { state: any; query: string | null }): void {\n const listeners = this.keyListeners.get(key) || new Set();\n listeners.forEach((listener) => listener(payload));\n }\n\n /**\n * Subscribe to global updates\n */\n on(event: \"update\", listener: Listener): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n }\n\n /**\n * Subscribe to key-specific updates\n */\n onKey(key: string, listener: KeyListener): void {\n if (!this.keyListeners.has(key)) {\n this.keyListeners.set(key, new Set());\n }\n this.keyListeners.get(key)!.add(listener);\n }\n\n /**\n * Unsubscribe from global updates\n */\n off(event: \"update\", listener: Listener): void {\n const listeners = this.listeners.get(event);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n\n /**\n * Unsubscribe from key-specific updates\n */\n offKey(key: string, listener: KeyListener): void {\n const listeners = this.keyListeners.get(key);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n}\n\nexport const emitter = new Emitter();\n","import type { Parser } from \"./parsers\";\n\nexport type RouteParamsInput =\n | Record<string, string | undefined>\n | URLSearchParams\n | Promise<Record<string, string | undefined> | URLSearchParams>;\n\nfunction normalizeParams(input: Record<string, string | undefined> | URLSearchParams) {\n if (input instanceof URLSearchParams) {\n const params: Record<string, string | undefined> = {};\n input.forEach((value, key) => {\n params[key] = value;\n });\n return params;\n }\n return input;\n}\n\nexport async function loadRouteParams<T extends Record<string, Parser<any>>>(\n input: RouteParamsInput,\n parsers: T,\n options: { strict?: boolean } = {},\n): Promise<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> }> {\n const resolved = await Promise.resolve(input);\n const params = normalizeParams(resolved);\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n for (const [key, parser] of Object.entries(parsers)) {\n const raw = params[key] ?? \"\";\n const parsed = parser.parse(raw);\n if (parsed === null && options.strict) {\n throw new Error(`Failed to parse route param \"${key}\" with value \"${raw}\"`);\n }\n result[key as keyof T] = parsed as ReturnType<T[typeof key][\"parse\"]>;\n }\n\n return result;\n}\n\nexport function parseRouteParams<T extends Record<string, Parser<any>>>(\n input: Record<string, string | undefined> | URLSearchParams,\n parsers: T,\n options: { strict?: boolean } = {},\n): { [K in keyof T]: ReturnType<T[K][\"parse\"]> } {\n const params = normalizeParams(input);\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n for (const [key, parser] of Object.entries(parsers)) {\n const raw = params[key] ?? \"\";\n const parsed = parser.parse(raw);\n if (parsed === null && options.strict) {\n throw new Error(`Failed to parse route param \"${key}\" with value \"${raw}\"`);\n }\n result[key as keyof T] = parsed as ReturnType<T[typeof key][\"parse\"]>;\n }\n\n return result;\n}\n","/**\n * Farm.js Query State Parsers\n *\n * Shared parsers that can be used in both client and server contexts\n */\n\nexport interface Parser<T> {\n parse: (value: string) => T | null;\n serialize: (value: T) => string;\n withDefault?: (defaultValue: T) => Parser<T>;\n}\n\n// String parser\nexport const asString: Parser<string> = {\n parse: (value: string) => (value && value.trim()) || null,\n serialize: (value: string) => value,\n withDefault: (defaultValue: string) => ({\n parse: (value: string) => (value && value.trim()) || defaultValue,\n serialize: (value: string) => value || defaultValue,\n }),\n};\n\n// Integer parser\nexport const asInteger: Parser<number> = {\n parse: (value: string) => {\n const parsed = Number.parseInt(value, 10);\n return isNaN(parsed) ? null : parsed;\n },\n serialize: (value: number) => value.toString(),\n withDefault: (defaultValue: number) => ({\n parse: (value: string) => {\n const parsed = Number.parseInt(value, 10);\n return isNaN(parsed) ? defaultValue : parsed;\n },\n serialize: (value: number) => value.toString(),\n }),\n};\n\n// Float parser\nexport const asFloat: Parser<number> = {\n parse: (value: string) => {\n const parsed = Number.parseFloat(value);\n return isNaN(parsed) ? null : parsed;\n },\n serialize: (value: number) => value.toString(),\n withDefault: (defaultValue: number) => ({\n parse: (value: string) => {\n const parsed = Number.parseFloat(value);\n return isNaN(parsed) ? defaultValue : parsed;\n },\n serialize: (value: number) => value.toString(),\n }),\n};\n\n// Boolean parser\nexport const asBoolean: Parser<boolean> = {\n parse: (value: string) => {\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n return null;\n },\n serialize: (value: boolean) => value.toString(),\n withDefault: (defaultValue: boolean) => ({\n parse: (value: string) => {\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n return defaultValue;\n },\n serialize: (value: boolean) => value.toString(),\n }),\n};\n\n// Array parser\nexport function asArrayOf<T>(itemParser: Parser<T>): Parser<T[]> {\n return {\n parse: (value: string) => {\n if (!value) return null;\n return value\n .split(\",\")\n .filter(Boolean)\n .map((item) => itemParser.parse(item.trim()))\n .filter((item) => item !== null) as T[];\n },\n serialize: (value: T[]) => value.map((item) => itemParser.serialize(item)).join(\",\"),\n withDefault: (defaultValue: T[]) => ({\n parse: (value: string) => {\n if (!value) return defaultValue;\n return value\n .split(\",\")\n .filter(Boolean)\n .map((item) => itemParser.parse(item.trim()))\n .filter((item) => item !== null) as T[];\n },\n serialize: (value: T[]) => value.map((item) => itemParser.serialize(item)).join(\",\"),\n }),\n };\n}\n\n// JSON parser\nexport function asJson<T>(): Parser<T> {\n return {\n parse: (value: string) => {\n try {\n return JSON.parse(value);\n } catch {\n return null;\n }\n },\n serialize: (value: T) => JSON.stringify(value),\n withDefault: (defaultValue: T) => ({\n parse: (value: string) => {\n try {\n return JSON.parse(value);\n } catch {\n return defaultValue;\n }\n },\n serialize: (value: T) => JSON.stringify(value),\n }),\n };\n}\n\n// ISO Date parser\nexport const asIsoDate: Parser<Date> = {\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? null : date;\n },\n serialize: (value: Date) => value.toISOString(),\n withDefault: (defaultValue: Date) => ({\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? defaultValue : date;\n },\n serialize: (value: Date) => value.toISOString(),\n }),\n};\n\n// ISO DateTime parser\nexport const asIsoDateTime: Parser<Date> = {\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? null : date;\n },\n serialize: (value: Date) => value.toISOString(),\n withDefault: (defaultValue: Date) => ({\n parse: (value: string) => {\n const date = new Date(value);\n return isNaN(date.getTime()) ? defaultValue : date;\n },\n serialize: (value: Date) => value.toISOString(),\n }),\n};\n\n// Custom parser creator\nexport function createParser<T>(config: {\n parse: (value: string) => T | null;\n serialize: (value: T) => string;\n}): Parser<T> {\n return {\n parse: config.parse,\n serialize: config.serialize,\n withDefault: (defaultValue: T) => ({\n parse: (value: string) => config.parse(value) ?? defaultValue,\n serialize: config.serialize,\n }),\n };\n}\n\n// Type inference helper\nexport type inferParserType<T> = T extends Parser<infer U> ? U : never;\n"],"mappings":"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","names":["IntlMessageFormat","applyChange"]}
|
package/dist/query/client.mjs
CHANGED
|
@@ -13,6 +13,15 @@ import {
|
|
|
13
13
|
asString,
|
|
14
14
|
createParser
|
|
15
15
|
} from "../chunk-E2MPIBRE.mjs";
|
|
16
|
+
import {
|
|
17
|
+
notifyHistoryChange,
|
|
18
|
+
subscribeHistoryChange
|
|
19
|
+
} from "../chunk-5T2XATMK.mjs";
|
|
20
|
+
import "../chunk-ITN462YM.mjs";
|
|
21
|
+
import "../chunk-3BXGOVKE.mjs";
|
|
22
|
+
import "../chunk-GS6K5OCM.mjs";
|
|
23
|
+
import "../chunk-44ZCFSKE.mjs";
|
|
24
|
+
import "../chunk-WZ5QFROO.mjs";
|
|
16
25
|
import {
|
|
17
26
|
__name
|
|
18
27
|
} from "../chunk-CGILA3WO.mjs";
|
|
@@ -108,20 +117,18 @@ var updateURL = /* @__PURE__ */ __name((updates, options = {}, emitUpdate = true
|
|
|
108
117
|
const currentUrl = window.location.pathname + (window.location.search || "") + window.location.hash;
|
|
109
118
|
if (newUrl !== currentUrl) {
|
|
110
119
|
const update = /* @__PURE__ */ __name(() => {
|
|
120
|
+
const historyState = window.history.state;
|
|
121
|
+
const nextHistoryState = historyState && typeof historyState === "object" && "path" in historyState ? { ...historyState, path: newUrl } : historyState;
|
|
111
122
|
if (history === "replaceState") {
|
|
112
|
-
window.history.replaceState(
|
|
123
|
+
window.history.replaceState(nextHistoryState, "", newUrl);
|
|
113
124
|
} else {
|
|
114
|
-
window.history.pushState(
|
|
125
|
+
window.history.pushState(nextHistoryState, "", newUrl);
|
|
115
126
|
}
|
|
116
127
|
if (emitUpdate) {
|
|
117
128
|
const actualSearchParams = new URLSearchParams(window.location.search);
|
|
118
129
|
emitter.emitUpdate(actualSearchParams);
|
|
119
130
|
}
|
|
120
|
-
if (shallow)
|
|
121
|
-
setTimeout(() => {
|
|
122
|
-
window.dispatchEvent(new PopStateEvent("popstate"));
|
|
123
|
-
}, 0);
|
|
124
|
-
}
|
|
131
|
+
if (shallow) notifyHistoryChange("url-search");
|
|
125
132
|
if (scroll) {
|
|
126
133
|
window.scrollTo(0, 0);
|
|
127
134
|
}
|
|
@@ -198,11 +205,11 @@ function useQueryState(key, parser, options) {
|
|
|
198
205
|
stateRef.current = payload.state;
|
|
199
206
|
}
|
|
200
207
|
}, "onKeyUpdate");
|
|
201
|
-
|
|
208
|
+
const unsubscribeHistory = subscribeHistoryChange(onPopState);
|
|
202
209
|
emitter.on("update", onEmitterUpdate);
|
|
203
210
|
emitter.onKey(key, onKeyUpdate);
|
|
204
211
|
return () => {
|
|
205
|
-
|
|
212
|
+
unsubscribeHistory();
|
|
206
213
|
emitter.off("update", onEmitterUpdate);
|
|
207
214
|
emitter.offKey(key, onKeyUpdate);
|
|
208
215
|
};
|
|
@@ -274,10 +281,10 @@ function useQueryStates(parsers, options = {}) {
|
|
|
274
281
|
const onEmitterUpdate = /* @__PURE__ */ __name((searchParams) => {
|
|
275
282
|
applyChange2(searchParams, true);
|
|
276
283
|
}, "onEmitterUpdate");
|
|
277
|
-
|
|
284
|
+
const unsubscribeHistory = subscribeHistoryChange(onPopState);
|
|
278
285
|
emitter.on("update", onEmitterUpdate);
|
|
279
286
|
return () => {
|
|
280
|
-
|
|
287
|
+
unsubscribeHistory();
|
|
281
288
|
emitter.off("update", onEmitterUpdate);
|
|
282
289
|
};
|
|
283
290
|
}, [watchKeys, parsers]);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/query/client.ts","../../src/query/sync.ts"],"sourcesContent":["/**\n * Farm.js Client-Side Query State Management\n *\n * Type-safe URL search parameter state management - Like useState, but stored in the URL query string.\n * Client-only hooks for managing query parameters in the browser.\n * Inspired by nuqs implementation patterns.\n */\n\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { emitter } from \"./sync\";\nexport { parseRouteParams, loadRouteParams, type RouteParamsInput } from \"./params\";\n\nimport { asString as asStringClient, asInteger as asIntegerClient, type Parser } from \"./parsers\";\n\nexport {\n asString,\n asInteger,\n asFloat,\n asBoolean,\n asArrayOf,\n asJson,\n asIsoDate,\n asIsoDateTime,\n createParser,\n type Parser,\n type inferParserType,\n} from \"./parsers\";\n\nexport type UrlUpdateType = \"push\" | \"replace\";\nexport type HistoryMethod = \"pushState\" | \"replaceState\";\n\nexport interface Options {\n history?: HistoryMethod;\n shallow?: boolean;\n scroll?: boolean;\n throttleMs?: number;\n}\n\nconst getCurrentSearchParams = (): URLSearchParams => {\n if (typeof window === \"undefined\") return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n};\n\nconst throttleTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst applyChange = (\n searchParams: URLSearchParams,\n updates: Record<string, string | null>,\n keepExisting = true,\n): URLSearchParams => {\n const newParams = keepExisting ? new URLSearchParams(searchParams) : new URLSearchParams();\n\n Object.entries(updates).forEach(([key, value]) => {\n if (value === null || value === undefined || value === \"\") {\n newParams.delete(key);\n } else {\n newParams.set(key, value);\n }\n });\n\n return newParams;\n};\n\nconst updateURL = (\n updates: Record<string, string | null>,\n options: Options = {},\n emitUpdate = true,\n) => {\n if (typeof window === \"undefined\") return;\n\n const { history = \"pushState\", shallow = true, scroll = false, throttleMs } = options;\n const url = new URL(window.location.href);\n\n const newSearchParams = applyChange(new URLSearchParams(url.search), updates);\n const newSearch = newSearchParams.toString();\n const newUrl = url.pathname + (newSearch ? `?${newSearch}` : \"\") + url.hash;\n const currentUrl =\n window.location.pathname + (window.location.search || \"\") + window.location.hash;\n\n if (newUrl !== currentUrl) {\n const update = () => {\n if (history === \"replaceState\") {\n window.history.replaceState(null, \"\", newUrl);\n } else {\n window.history.pushState(null, \"\", newUrl);\n }\n\n if (emitUpdate) {\n const actualSearchParams = new URLSearchParams(window.location.search);\n emitter.emitUpdate(actualSearchParams);\n }\n\n if (shallow) {\n setTimeout(() => {\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n }, 0);\n }\n\n if (scroll) {\n window.scrollTo(0, 0);\n }\n };\n\n if (throttleMs && throttleMs > 0) {\n const throttleKey = Object.keys(updates).sort().join(\",\");\n\n const existingTimeout = throttleTimers.get(throttleKey);\n if (existingTimeout) {\n clearTimeout(existingTimeout);\n }\n\n const timeout = setTimeout(() => {\n update();\n throttleTimers.delete(throttleKey);\n }, throttleMs);\n\n throttleTimers.set(throttleKey, timeout);\n } else {\n update();\n }\n }\n};\n\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n];\nexport function useQueryState<T>(\n key: string,\n parser: Parser<T>,\n options?: Options,\n): [T | null, (value: T | null) => void];\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n] {\n type T = NonNullable<ReturnType<TParser[\"parse\"]>>;\n const [state, setState] = useState<T | null>(() => {\n if (typeof window === \"undefined\") return parser.parse(\"\");\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n return parsed;\n });\n\n const stateRef = useRef(state);\n const isInternalUpdateRef = useRef(false);\n stateRef.current = state;\n\n const setValue = useCallback(\n (value: T | null) => {\n isInternalUpdateRef.current = true;\n\n setState(value);\n stateRef.current = value;\n\n const serialized = value === null ? null : parser.serialize(value);\n\n emitter.emitKey(key, { state: value, query: serialized });\n\n updateURL({ [key]: serialized }, options, true);\n\n setTimeout(() => {\n isInternalUpdateRef.current = false;\n }, 10);\n },\n [key, parser, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onKeyUpdate = (payload: { state: any; query: string | null }) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n if (!Object.is(stateRef.current, payload.state)) {\n setState(payload.state);\n stateRef.current = payload.state;\n }\n };\n\n window.addEventListener(\"popstate\", onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n emitter.onKey(key, onKeyUpdate);\n\n return () => {\n window.removeEventListener(\"popstate\", onPopState);\n emitter.off(\"update\", onEmitterUpdate);\n emitter.offKey(key, onKeyUpdate);\n };\n }, [key, parser]);\n\n return [state, setValue];\n}\n\nexport function useQueryStates<T extends Record<string, Parser<any>>>(\n parsers: T,\n options: Options = {},\n): [\n { [K in keyof T]: ReturnType<T[K][\"parse\"]> },\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => void,\n] {\n const keys = Object.keys(parsers);\n const watchKeys = keys.join(\"&\");\n\n const [state, setState] = useState<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> }>(() => {\n const searchParams =\n typeof window === \"undefined\" ? new URLSearchParams() : getCurrentSearchParams();\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n result[key as keyof T] = parser.parse(value ?? \"\");\n });\n\n return result;\n });\n\n const stateRef = useRef(state);\n stateRef.current = state;\n\n const setValues = useCallback(\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => {\n const newState = { ...stateRef.current, ...updates };\n setState(newState);\n stateRef.current = newState;\n\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n emitter.emitKey(key, { state: value, query: serialized });\n }\n });\n\n const urlUpdates: Record<string, string | null> = {};\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n urlUpdates[key] = serialized;\n }\n });\n\n updateURL(urlUpdates, options, true);\n },\n [parsers, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const applyChange = (searchParams: URLSearchParams, fromEmitter = false) => {\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n let hasChanged = false;\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n const currentValue = stateRef.current[key as keyof T];\n\n if (!Object.is(currentValue, parsed)) {\n hasChanged = true;\n }\n result[key as keyof T] = parsed;\n });\n\n if (hasChanged) {\n setState(result);\n stateRef.current = result;\n }\n };\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n applyChange(searchParams, false);\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n applyChange(searchParams, true);\n };\n\n window.addEventListener(\"popstate\", onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n\n return () => {\n window.removeEventListener(\"popstate\", onPopState);\n emitter.off(\"update\", onEmitterUpdate);\n };\n }, [watchKeys, parsers]);\n\n return [state, setValues];\n}\n\nexport function usePagination(options?: {\n defaultPage?: number;\n defaultLimit?: number;\n pageKey?: string;\n limitKey?: string;\n}) {\n const {\n defaultPage = 1,\n defaultLimit = 10,\n pageKey = \"page\",\n limitKey = \"limit\",\n } = options || {};\n\n const [page, setPage] = useQueryState(pageKey, asIntegerClient.withDefault!(defaultPage));\n const [limit, setLimit] = useQueryState(limitKey, asIntegerClient.withDefault!(defaultLimit));\n\n const currentPage = page ?? defaultPage;\n const currentLimit = limit ?? defaultLimit;\n const offset = ((currentPage as number) - 1) * (currentLimit as number);\n\n return {\n page: currentPage,\n setPage,\n limit: currentLimit,\n setLimit,\n offset,\n resetPagination: () => {\n setPage(defaultPage);\n setLimit(defaultLimit);\n },\n };\n}\n\nexport function useSearchFilters<T extends Record<string, any>>(options?: {\n searchKey?: string;\n defaultFilters?: T;\n filterKeys?: (keyof T)[];\n}) {\n const {\n searchKey = \"search\",\n defaultFilters = {} as T,\n filterKeys = Object.keys(defaultFilters) as (keyof T)[],\n } = options || {};\n\n const [search, setSearch] = useQueryState(searchKey, asStringClient);\n\n const filterParsers = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = asStringClient;\n return acc;\n },\n {} as Record<string, Parser<string>>,\n );\n\n const [filters, setFilters] = useQueryStates(filterParsers);\n\n const clearFilters = () => {\n setSearch(null);\n const clearedFilters = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = null;\n return acc;\n },\n {} as Record<string, null>,\n );\n setFilters(clearedFilters);\n };\n\n return {\n search,\n setSearch,\n filters: filters as T,\n setFilters,\n clearFilters,\n };\n}\n","/**\n * Cross-hook synchronization system for query state\n * Inspired by nuqs implementation\n */\n\ntype Listener = (searchParams: URLSearchParams) => void;\ntype KeyListener = (payload: { state: any; query: string | null }) => void;\n\nclass Emitter {\n private listeners: Map<string, Set<Listener>> = new Map();\n private keyListeners: Map<string, Set<KeyListener>> = new Map();\n\n /**\n * Emit a global update event (all hooks should sync)\n */\n emitUpdate(searchParams: URLSearchParams): void {\n const listeners = this.listeners.get(\"update\") || new Set();\n listeners.forEach((listener) => listener(searchParams));\n }\n\n /**\n * Emit a key-specific update (for cross-hook sync)\n */\n emitKey(key: string, payload: { state: any; query: string | null }): void {\n const listeners = this.keyListeners.get(key) || new Set();\n listeners.forEach((listener) => listener(payload));\n }\n\n /**\n * Subscribe to global updates\n */\n on(event: \"update\", listener: Listener): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n }\n\n /**\n * Subscribe to key-specific updates\n */\n onKey(key: string, listener: KeyListener): void {\n if (!this.keyListeners.has(key)) {\n this.keyListeners.set(key, new Set());\n }\n this.keyListeners.get(key)!.add(listener);\n }\n\n /**\n * Unsubscribe from global updates\n */\n off(event: \"update\", listener: Listener): void {\n const listeners = this.listeners.get(event);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n\n /**\n * Unsubscribe from key-specific updates\n */\n offKey(key: string, listener: KeyListener): void {\n const listeners = this.keyListeners.get(key);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n}\n\nexport const emitter = new Emitter();\n"],"mappings":"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","names":["applyChange"]}
|
|
1
|
+
{"version":3,"sources":["../../src/query/client.ts","../../src/query/sync.ts"],"sourcesContent":["/**\n * Farm.js Client-Side Query State Management\n *\n * Type-safe URL search parameter state management - Like useState, but stored in the URL query string.\n * Client-only hooks for managing query parameters in the browser.\n * Inspired by nuqs implementation patterns.\n */\n\nimport { useState, useEffect, useCallback, useRef } from \"react\";\nimport { notifyHistoryChange, subscribeHistoryChange } from \"../client/history-sync\";\nimport { emitter } from \"./sync\";\nexport { parseRouteParams, loadRouteParams, type RouteParamsInput } from \"./params\";\n\nimport { asString as asStringClient, asInteger as asIntegerClient, type Parser } from \"./parsers\";\n\nexport {\n asString,\n asInteger,\n asFloat,\n asBoolean,\n asArrayOf,\n asJson,\n asIsoDate,\n asIsoDateTime,\n createParser,\n type Parser,\n type inferParserType,\n} from \"./parsers\";\n\nexport type UrlUpdateType = \"push\" | \"replace\";\nexport type HistoryMethod = \"pushState\" | \"replaceState\";\n\nexport interface Options {\n history?: HistoryMethod;\n shallow?: boolean;\n scroll?: boolean;\n throttleMs?: number;\n}\n\nconst getCurrentSearchParams = (): URLSearchParams => {\n if (typeof window === \"undefined\") return new URLSearchParams();\n return new URLSearchParams(window.location.search);\n};\n\nconst throttleTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\nconst applyChange = (\n searchParams: URLSearchParams,\n updates: Record<string, string | null>,\n keepExisting = true,\n): URLSearchParams => {\n const newParams = keepExisting ? new URLSearchParams(searchParams) : new URLSearchParams();\n\n Object.entries(updates).forEach(([key, value]) => {\n if (value === null || value === undefined || value === \"\") {\n newParams.delete(key);\n } else {\n newParams.set(key, value);\n }\n });\n\n return newParams;\n};\n\nconst updateURL = (\n updates: Record<string, string | null>,\n options: Options = {},\n emitUpdate = true,\n) => {\n if (typeof window === \"undefined\") return;\n\n const { history = \"pushState\", shallow = true, scroll = false, throttleMs } = options;\n const url = new URL(window.location.href);\n\n const newSearchParams = applyChange(new URLSearchParams(url.search), updates);\n const newSearch = newSearchParams.toString();\n const newUrl = url.pathname + (newSearch ? `?${newSearch}` : \"\") + url.hash;\n const currentUrl =\n window.location.pathname + (window.location.search || \"\") + window.location.hash;\n\n if (newUrl !== currentUrl) {\n const update = () => {\n // Preserve Farm's history state (page state, interception markers) and\n // keep its recorded path in sync with the new query string so pops\n // report the entry's real location.\n const historyState = window.history.state;\n const nextHistoryState =\n historyState && typeof historyState === \"object\" && \"path\" in historyState\n ? { ...historyState, path: newUrl }\n : historyState;\n\n if (history === \"replaceState\") {\n window.history.replaceState(nextHistoryState, \"\", newUrl);\n } else {\n window.history.pushState(nextHistoryState, \"\", newUrl);\n }\n\n if (emitUpdate) {\n const actualSearchParams = new URLSearchParams(window.location.search);\n emitter.emitUpdate(actualSearchParams);\n }\n\n if (shallow) notifyHistoryChange(\"url-search\");\n\n if (scroll) {\n window.scrollTo(0, 0);\n }\n };\n\n if (throttleMs && throttleMs > 0) {\n const throttleKey = Object.keys(updates).sort().join(\",\");\n\n const existingTimeout = throttleTimers.get(throttleKey);\n if (existingTimeout) {\n clearTimeout(existingTimeout);\n }\n\n const timeout = setTimeout(() => {\n update();\n throttleTimers.delete(throttleKey);\n }, throttleMs);\n\n throttleTimers.set(throttleKey, timeout);\n } else {\n update();\n }\n }\n};\n\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n];\nexport function useQueryState<T>(\n key: string,\n parser: Parser<T>,\n options?: Options,\n): [T | null, (value: T | null) => void];\nexport function useQueryState<TParser extends Parser<any>>(\n key: string,\n parser: TParser,\n options?: Options,\n): [\n NonNullable<ReturnType<TParser[\"parse\"]>> | null,\n (value: NonNullable<ReturnType<TParser[\"parse\"]>> | null) => void,\n] {\n type T = NonNullable<ReturnType<TParser[\"parse\"]>>;\n const [state, setState] = useState<T | null>(() => {\n if (typeof window === \"undefined\") return parser.parse(\"\");\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n return parsed;\n });\n\n const stateRef = useRef(state);\n const isInternalUpdateRef = useRef(false);\n stateRef.current = state;\n\n const setValue = useCallback(\n (value: T | null) => {\n isInternalUpdateRef.current = true;\n\n setState(value);\n stateRef.current = value;\n\n const serialized = value === null ? null : parser.serialize(value);\n\n emitter.emitKey(key, { state: value, query: serialized });\n\n updateURL({ [key]: serialized }, options, true);\n\n setTimeout(() => {\n isInternalUpdateRef.current = false;\n }, 10);\n },\n [key, parser, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n if (!Object.is(stateRef.current, parsed)) {\n setState(parsed);\n stateRef.current = parsed;\n }\n };\n\n const onKeyUpdate = (payload: { state: any; query: string | null }) => {\n if (isInternalUpdateRef.current) {\n return;\n }\n\n if (!Object.is(stateRef.current, payload.state)) {\n setState(payload.state);\n stateRef.current = payload.state;\n }\n };\n\n // Page-state writes with an href can change the query string without a\n // popstate, so listen through the shared history channel (real\n // back/forward events included). Self-updates no-op via the Object.is\n // comparison above.\n const unsubscribeHistory = subscribeHistoryChange(onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n emitter.onKey(key, onKeyUpdate);\n\n return () => {\n unsubscribeHistory();\n emitter.off(\"update\", onEmitterUpdate);\n emitter.offKey(key, onKeyUpdate);\n };\n }, [key, parser]);\n\n return [state, setValue];\n}\n\nexport function useQueryStates<T extends Record<string, Parser<any>>>(\n parsers: T,\n options: Options = {},\n): [\n { [K in keyof T]: ReturnType<T[K][\"parse\"]> },\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => void,\n] {\n const keys = Object.keys(parsers);\n const watchKeys = keys.join(\"&\");\n\n const [state, setState] = useState<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> }>(() => {\n const searchParams =\n typeof window === \"undefined\" ? new URLSearchParams() : getCurrentSearchParams();\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n result[key as keyof T] = parser.parse(value ?? \"\");\n });\n\n return result;\n });\n\n const stateRef = useRef(state);\n stateRef.current = state;\n\n const setValues = useCallback(\n (updates: Partial<{ [K in keyof T]: ReturnType<T[K][\"parse\"]> | null }>) => {\n const newState = { ...stateRef.current, ...updates };\n setState(newState);\n stateRef.current = newState;\n\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n emitter.emitKey(key, { state: value, query: serialized });\n }\n });\n\n const urlUpdates: Record<string, string | null> = {};\n Object.entries(updates).forEach(([key, value]) => {\n const parser = parsers[key];\n if (parser) {\n const serialized = value === null ? null : parser.serialize(value);\n urlUpdates[key] = serialized;\n }\n });\n\n updateURL(urlUpdates, options, true);\n },\n [parsers, options],\n );\n\n useEffect(() => {\n if (typeof window === \"undefined\") return;\n\n const applyChange = (searchParams: URLSearchParams, fromEmitter = false) => {\n const result = {} as { [K in keyof T]: ReturnType<T[K][\"parse\"]> };\n let hasChanged = false;\n\n Object.entries(parsers).forEach(([key, parser]) => {\n const value = searchParams.get(key);\n const parsed = parser.parse(value ?? \"\");\n const currentValue = stateRef.current[key as keyof T];\n\n if (!Object.is(currentValue, parsed)) {\n hasChanged = true;\n }\n result[key as keyof T] = parsed;\n });\n\n if (hasChanged) {\n setState(result);\n stateRef.current = result;\n }\n };\n\n const onPopState = () => {\n const searchParams = getCurrentSearchParams();\n applyChange(searchParams, false);\n };\n\n const onEmitterUpdate = (searchParams: URLSearchParams) => {\n applyChange(searchParams, true);\n };\n\n const unsubscribeHistory = subscribeHistoryChange(onPopState);\n emitter.on(\"update\", onEmitterUpdate);\n\n return () => {\n unsubscribeHistory();\n emitter.off(\"update\", onEmitterUpdate);\n };\n }, [watchKeys, parsers]);\n\n return [state, setValues];\n}\n\nexport function usePagination(options?: {\n defaultPage?: number;\n defaultLimit?: number;\n pageKey?: string;\n limitKey?: string;\n}) {\n const {\n defaultPage = 1,\n defaultLimit = 10,\n pageKey = \"page\",\n limitKey = \"limit\",\n } = options || {};\n\n const [page, setPage] = useQueryState(pageKey, asIntegerClient.withDefault!(defaultPage));\n const [limit, setLimit] = useQueryState(limitKey, asIntegerClient.withDefault!(defaultLimit));\n\n const currentPage = page ?? defaultPage;\n const currentLimit = limit ?? defaultLimit;\n const offset = ((currentPage as number) - 1) * (currentLimit as number);\n\n return {\n page: currentPage,\n setPage,\n limit: currentLimit,\n setLimit,\n offset,\n resetPagination: () => {\n setPage(defaultPage);\n setLimit(defaultLimit);\n },\n };\n}\n\nexport function useSearchFilters<T extends Record<string, any>>(options?: {\n searchKey?: string;\n defaultFilters?: T;\n filterKeys?: (keyof T)[];\n}) {\n const {\n searchKey = \"search\",\n defaultFilters = {} as T,\n filterKeys = Object.keys(defaultFilters) as (keyof T)[],\n } = options || {};\n\n const [search, setSearch] = useQueryState(searchKey, asStringClient);\n\n const filterParsers = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = asStringClient;\n return acc;\n },\n {} as Record<string, Parser<string>>,\n );\n\n const [filters, setFilters] = useQueryStates(filterParsers);\n\n const clearFilters = () => {\n setSearch(null);\n const clearedFilters = filterKeys.reduce(\n (acc, key) => {\n acc[key as string] = null;\n return acc;\n },\n {} as Record<string, null>,\n );\n setFilters(clearedFilters);\n };\n\n return {\n search,\n setSearch,\n filters: filters as T,\n setFilters,\n clearFilters,\n };\n}\n","/**\n * Cross-hook synchronization system for query state\n * Inspired by nuqs implementation\n */\n\ntype Listener = (searchParams: URLSearchParams) => void;\ntype KeyListener = (payload: { state: any; query: string | null }) => void;\n\nclass Emitter {\n private listeners: Map<string, Set<Listener>> = new Map();\n private keyListeners: Map<string, Set<KeyListener>> = new Map();\n\n /**\n * Emit a global update event (all hooks should sync)\n */\n emitUpdate(searchParams: URLSearchParams): void {\n const listeners = this.listeners.get(\"update\") || new Set();\n listeners.forEach((listener) => listener(searchParams));\n }\n\n /**\n * Emit a key-specific update (for cross-hook sync)\n */\n emitKey(key: string, payload: { state: any; query: string | null }): void {\n const listeners = this.keyListeners.get(key) || new Set();\n listeners.forEach((listener) => listener(payload));\n }\n\n /**\n * Subscribe to global updates\n */\n on(event: \"update\", listener: Listener): void {\n if (!this.listeners.has(event)) {\n this.listeners.set(event, new Set());\n }\n this.listeners.get(event)!.add(listener);\n }\n\n /**\n * Subscribe to key-specific updates\n */\n onKey(key: string, listener: KeyListener): void {\n if (!this.keyListeners.has(key)) {\n this.keyListeners.set(key, new Set());\n }\n this.keyListeners.get(key)!.add(listener);\n }\n\n /**\n * Unsubscribe from global updates\n */\n off(event: \"update\", listener: Listener): void {\n const listeners = this.listeners.get(event);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n\n /**\n * Unsubscribe from key-specific updates\n */\n offKey(key: string, listener: KeyListener): void {\n const listeners = this.keyListeners.get(key);\n if (listeners) {\n listeners.delete(listener);\n }\n }\n}\n\nexport const emitter = new Emitter();\n"],"mappings":"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","names":["applyChange"]}
|