@farm.js/core 0.1.0-beta.54 → 0.1.0-beta.55

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.
Files changed (66) hide show
  1. package/dist/{chunk-PGZVE6P7.mjs → chunk-2ISN27CV.mjs} +2 -2
  2. package/dist/{chunk-ENPB4CSY.mjs → chunk-BLTATPSB.mjs} +2 -2
  3. package/dist/{chunk-ENPB4CSY.mjs.map → chunk-BLTATPSB.mjs.map} +1 -1
  4. package/dist/{chunk-J34JCW6R.mjs → chunk-MSQDIAQW.mjs} +2 -2
  5. package/dist/{chunk-GPHLXAFT.mjs → chunk-OGFWHBDO.mjs} +2 -2
  6. package/dist/{chunk-JECGLPIS.mjs → chunk-PYIVNTDI.mjs} +4 -4
  7. package/dist/{chunk-GVQ54ERX.mjs → chunk-RAZ7L3UY.mjs} +2 -2
  8. package/dist/{chunk-427JQ6OZ.mjs → chunk-YBHLSG7E.mjs} +54 -11
  9. package/dist/chunk-YBHLSG7E.mjs.map +1 -0
  10. package/dist/{chunk-BDHDDITP.mjs → chunk-ZP5IWDTE.mjs} +9 -8
  11. package/dist/chunk-ZP5IWDTE.mjs.map +1 -0
  12. package/dist/client/lifecycle.mjs +2 -2
  13. package/dist/client-plugins.cjs +1 -1
  14. package/dist/client-plugins.cjs.map +1 -1
  15. package/dist/client-plugins.mjs +2 -2
  16. package/dist/client.cjs +41 -12
  17. package/dist/client.cjs.map +1 -1
  18. package/dist/client.d.mts +2 -2
  19. package/dist/client.d.ts +2 -2
  20. package/dist/client.mjs +11 -11
  21. package/dist/index.cjs +41 -12
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.mts +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.mjs +17 -17
  26. package/dist/internal/build-runtime.cjs +1 -1
  27. package/dist/internal/build-runtime.cjs.map +1 -1
  28. package/dist/internal/build-runtime.mjs +3 -3
  29. package/dist/internal/client-runtime.cjs +1 -1
  30. package/dist/internal/client-runtime.cjs.map +1 -1
  31. package/dist/internal/client-runtime.mjs +2 -2
  32. package/dist/navigation.cjs +40 -11
  33. package/dist/navigation.cjs.map +1 -1
  34. package/dist/navigation.mjs +4 -4
  35. package/dist/plugin.cjs +1 -1
  36. package/dist/plugin.cjs.map +1 -1
  37. package/dist/plugin.mjs +2 -2
  38. package/dist/query/client.cjs +86 -7
  39. package/dist/query/client.cjs.map +1 -1
  40. package/dist/query/client.mjs +13 -7
  41. package/dist/query/client.mjs.map +1 -1
  42. package/dist/renderer-client.cjs +29 -9
  43. package/dist/renderer-client.cjs.map +1 -1
  44. package/dist/renderer-client.d.mts +1 -1
  45. package/dist/renderer-client.d.ts +1 -1
  46. package/dist/renderer-client.mjs +12 -9
  47. package/dist/renderer-client.mjs.map +1 -1
  48. package/dist/server.cjs +1 -1
  49. package/dist/server.cjs.map +1 -1
  50. package/dist/server.mjs +2 -2
  51. package/dist/{spa-router-BD7iUG-C.d.mts → spa-router-BV71GGW0.d.mts} +4 -3
  52. package/dist/{spa-router-swFKTqwR.d.ts → spa-router-BylM6JXm.d.ts} +4 -3
  53. package/dist/version.cjs +1 -1
  54. package/dist/version.cjs.map +1 -1
  55. package/dist/version.mjs +1 -1
  56. package/dist/vite.cjs +1 -1
  57. package/dist/vite.cjs.map +1 -1
  58. package/dist/vite.mjs +2 -2
  59. package/package.json +1 -1
  60. package/dist/chunk-427JQ6OZ.mjs.map +0 -1
  61. package/dist/chunk-BDHDDITP.mjs.map +0 -1
  62. /package/dist/{chunk-PGZVE6P7.mjs.map → chunk-2ISN27CV.mjs.map} +0 -0
  63. /package/dist/{chunk-J34JCW6R.mjs.map → chunk-MSQDIAQW.mjs.map} +0 -0
  64. /package/dist/{chunk-GPHLXAFT.mjs.map → chunk-OGFWHBDO.mjs.map} +0 -0
  65. /package/dist/{chunk-JECGLPIS.mjs.map → chunk-PYIVNTDI.mjs.map} +0 -0
  66. /package/dist/{chunk-GVQ54ERX.mjs.map → chunk-RAZ7L3UY.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-PGZVE6P7.mjs";
9
+ } from "./chunk-2ISN27CV.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-ENPB4CSY.mjs";
66
+ import "./chunk-BLTATPSB.mjs";
67
67
  import "./chunk-CGILA3WO.mjs";
68
68
  export {
69
69
  FarmRuntimeShutdownError,
@@ -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,77 @@ __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/url-search-sync.ts
111
+ var FARM_URL_SEARCH_CHANGE_EVENT = "farm:urlsearchchange";
112
+ function notifyUrlSearchObservers(shallow) {
113
+ if (!shallow || typeof window === "undefined") return;
114
+ setTimeout(() => {
115
+ if (getInstalledFarmSPARouter()) {
116
+ window.dispatchEvent(new CustomEvent(FARM_URL_SEARCH_CHANGE_EVENT));
117
+ return;
118
+ }
119
+ window.dispatchEvent(new PopStateEvent("popstate"));
120
+ }, 0);
121
+ }
122
+ __name(notifyUrlSearchObservers, "notifyUrlSearchObservers");
123
+
43
124
  // src/query/sync.ts
44
125
  var _Emitter = class _Emitter {
45
126
  constructor() {
@@ -303,20 +384,18 @@ var updateURL = /* @__PURE__ */ __name((updates, options = {}, emitUpdate = true
303
384
  const currentUrl = window.location.pathname + (window.location.search || "") + window.location.hash;
304
385
  if (newUrl !== currentUrl) {
305
386
  const update = /* @__PURE__ */ __name(() => {
387
+ const historyState = window.history.state;
388
+ const nextHistoryState = historyState && typeof historyState === "object" && "path" in historyState ? { ...historyState, path: newUrl } : historyState;
306
389
  if (history === "replaceState") {
307
- window.history.replaceState(null, "", newUrl);
390
+ window.history.replaceState(nextHistoryState, "", newUrl);
308
391
  } else {
309
- window.history.pushState(null, "", newUrl);
392
+ window.history.pushState(nextHistoryState, "", newUrl);
310
393
  }
311
394
  if (emitUpdate) {
312
395
  const actualSearchParams = new URLSearchParams(window.location.search);
313
396
  emitter.emitUpdate(actualSearchParams);
314
397
  }
315
- if (shallow) {
316
- setTimeout(() => {
317
- window.dispatchEvent(new PopStateEvent("popstate"));
318
- }, 0);
319
- }
398
+ notifyUrlSearchObservers(shallow);
320
399
  if (scroll) {
321
400
  window.scrollTo(0, 0);
322
401
  }
@@ -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/url-search-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 { notifyUrlSearchObservers } from \"../client/url-search-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 notifyUrlSearchObservers(shallow);\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","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 {\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 window.dispatchEvent(new PopStateEvent(\"popstate\", { state: nextState }));\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 query-string updates when Farm SPA router is active. */\nexport const FARM_URL_SEARCH_CHANGE_EVENT = \"farm:urlsearchchange\";\n\n/**\n * Notify URL observers after history.pushState/replaceState updates the query string.\n *\n * nuqs uses `shallow: true` for client-only URL updates and dispatches synthetic\n * `popstate` so other hooks resync. Farm's SPA router also listens to `popstate`\n * for full document navigation, so when `__FARM_SPA_ROUTER__` is installed we emit a\n * dedicated event instead and keep `popstate` for real back/forward navigation.\n */\nexport function notifyUrlSearchObservers(shallow: boolean): void {\n if (!shallow || typeof window === \"undefined\") return;\n\n setTimeout(() => {\n if (getInstalledFarmSPARouter()) {\n window.dispatchEvent(new CustomEvent(FARM_URL_SEARCH_CHANGE_EVENT));\n return;\n }\n\n window.dispatchEvent(new PopStateEvent(\"popstate\"));\n }, 0);\n}\n\nexport function subscribeUrlSearchObservers(listener: () => void): () => void {\n if (typeof window === \"undefined\") return () => {};\n\n window.addEventListener(\"popstate\", listener);\n window.addEventListener(FARM_URL_SEARCH_CHANGE_EVENT, listener);\n\n return () => {\n window.removeEventListener(\"popstate\", listener);\n window.removeEventListener(FARM_URL_SEARCH_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"]}
@@ -13,6 +13,14 @@ import {
13
13
  asString,
14
14
  createParser
15
15
  } from "../chunk-E2MPIBRE.mjs";
16
+ import {
17
+ notifyUrlSearchObservers
18
+ } from "../chunk-YBHLSG7E.mjs";
19
+ import "../chunk-ITN462YM.mjs";
20
+ import "../chunk-3BXGOVKE.mjs";
21
+ import "../chunk-GS6K5OCM.mjs";
22
+ import "../chunk-44ZCFSKE.mjs";
23
+ import "../chunk-WZ5QFROO.mjs";
16
24
  import {
17
25
  __name
18
26
  } from "../chunk-CGILA3WO.mjs";
@@ -108,20 +116,18 @@ var updateURL = /* @__PURE__ */ __name((updates, options = {}, emitUpdate = true
108
116
  const currentUrl = window.location.pathname + (window.location.search || "") + window.location.hash;
109
117
  if (newUrl !== currentUrl) {
110
118
  const update = /* @__PURE__ */ __name(() => {
119
+ const historyState = window.history.state;
120
+ const nextHistoryState = historyState && typeof historyState === "object" && "path" in historyState ? { ...historyState, path: newUrl } : historyState;
111
121
  if (history === "replaceState") {
112
- window.history.replaceState(null, "", newUrl);
122
+ window.history.replaceState(nextHistoryState, "", newUrl);
113
123
  } else {
114
- window.history.pushState(null, "", newUrl);
124
+ window.history.pushState(nextHistoryState, "", newUrl);
115
125
  }
116
126
  if (emitUpdate) {
117
127
  const actualSearchParams = new URLSearchParams(window.location.search);
118
128
  emitter.emitUpdate(actualSearchParams);
119
129
  }
120
- if (shallow) {
121
- setTimeout(() => {
122
- window.dispatchEvent(new PopStateEvent("popstate"));
123
- }, 0);
124
- }
130
+ notifyUrlSearchObservers(shallow);
125
131
  if (scroll) {
126
132
  window.scrollTo(0, 0);
127
133
  }
@@ -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":";;;;;;;;;;;;;;;;;;;;AAQA,SAAS,UAAU,WAAW,aAAa,cAAc;;;ACAzD,IAAM,WAAN,MAAM,SAAQ;AAAA,EAAd;AACE,SAAQ,YAAwC,oBAAI,IAAI;AACxD,SAAQ,eAA8C,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9D,WAAW,cAAqC;AAC9C,UAAM,YAAY,KAAK,UAAU,IAAI,QAAQ,KAAK,oBAAI,IAAI;AAC1D,cAAU,QAAQ,CAAC,aAAa,SAAS,YAAY,CAAC;AAAA,EACxD;AAAA;AAAA;AAAA;AAAA,EAKA,QAAQ,KAAa,SAAqD;AACxE,UAAM,YAAY,KAAK,aAAa,IAAI,GAAG,KAAK,oBAAI,IAAI;AACxD,cAAU,QAAQ,CAAC,aAAa,SAAS,OAAO,CAAC;AAAA,EACnD;AAAA;AAAA;AAAA;AAAA,EAKA,GAAG,OAAiB,UAA0B;AAC5C,QAAI,CAAC,KAAK,UAAU,IAAI,KAAK,GAAG;AAC9B,WAAK,UAAU,IAAI,OAAO,oBAAI,IAAI,CAAC;AAAA,IACrC;AACA,SAAK,UAAU,IAAI,KAAK,EAAG,IAAI,QAAQ;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,KAAa,UAA6B;AAC9C,QAAI,CAAC,KAAK,aAAa,IAAI,GAAG,GAAG;AAC/B,WAAK,aAAa,IAAI,KAAK,oBAAI,IAAI,CAAC;AAAA,IACtC;AACA,SAAK,aAAa,IAAI,GAAG,EAAG,IAAI,QAAQ;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,OAAiB,UAA0B;AAC7C,UAAM,YAAY,KAAK,UAAU,IAAI,KAAK;AAC1C,QAAI,WAAW;AACb,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,KAAa,UAA6B;AAC/C,UAAM,YAAY,KAAK,aAAa,IAAI,GAAG;AAC3C,QAAI,WAAW;AACb,gBAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AACF;AA3Dc;AAAd,IAAM,UAAN;AA6DO,IAAM,UAAU,IAAI,QAAQ;;;AD/BnC,IAAM,yBAAyB,6BAAuB;AACpD,MAAI,OAAO,WAAW,YAAa,QAAO,IAAI,gBAAgB;AAC9D,SAAO,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACnD,GAH+B;AAK/B,IAAM,iBAAiB,oBAAI,IAA2C;AAEtE,IAAM,cAAc,wBAClB,cACA,SACA,eAAe,SACK;AACpB,QAAM,YAAY,eAAe,IAAI,gBAAgB,YAAY,IAAI,IAAI,gBAAgB;AAEzF,SAAO,QAAQ,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAChD,QAAI,UAAU,QAAQ,UAAU,UAAa,UAAU,IAAI;AACzD,gBAAU,OAAO,GAAG;AAAA,IACtB,OAAO;AACL,gBAAU,IAAI,KAAK,KAAK;AAAA,IAC1B;AAAA,EACF,CAAC;AAED,SAAO;AACT,GAhBoB;AAkBpB,IAAM,YAAY,wBAChB,SACA,UAAmB,CAAC,GACpB,aAAa,SACV;AACH,MAAI,OAAO,WAAW,YAAa;AAEnC,QAAM,EAAE,UAAU,aAAa,UAAU,MAAM,SAAS,OAAO,WAAW,IAAI;AAC9E,QAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;AAExC,QAAM,kBAAkB,YAAY,IAAI,gBAAgB,IAAI,MAAM,GAAG,OAAO;AAC5E,QAAM,YAAY,gBAAgB,SAAS;AAC3C,QAAM,SAAS,IAAI,YAAY,YAAY,IAAI,SAAS,KAAK,MAAM,IAAI;AACvE,QAAM,aACJ,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,MAAM,OAAO,SAAS;AAE9E,MAAI,WAAW,YAAY;AACzB,UAAM,SAAS,6BAAM;AACnB,UAAI,YAAY,gBAAgB;AAC9B,eAAO,QAAQ,aAAa,MAAM,IAAI,MAAM;AAAA,MAC9C,OAAO;AACL,eAAO,QAAQ,UAAU,MAAM,IAAI,MAAM;AAAA,MAC3C;AAEA,UAAI,YAAY;AACd,cAAM,qBAAqB,IAAI,gBAAgB,OAAO,SAAS,MAAM;AACrE,gBAAQ,WAAW,kBAAkB;AAAA,MACvC;AAEA,UAAI,SAAS;AACX,mBAAW,MAAM;AACf,iBAAO,cAAc,IAAI,cAAc,UAAU,CAAC;AAAA,QACpD,GAAG,CAAC;AAAA,MACN;AAEA,UAAI,QAAQ;AACV,eAAO,SAAS,GAAG,CAAC;AAAA,MACtB;AAAA,IACF,GArBe;AAuBf,QAAI,cAAc,aAAa,GAAG;AAChC,YAAM,cAAc,OAAO,KAAK,OAAO,EAAE,KAAK,EAAE,KAAK,GAAG;AAExD,YAAM,kBAAkB,eAAe,IAAI,WAAW;AACtD,UAAI,iBAAiB;AACnB,qBAAa,eAAe;AAAA,MAC9B;AAEA,YAAM,UAAU,WAAW,MAAM;AAC/B,eAAO;AACP,uBAAe,OAAO,WAAW;AAAA,MACnC,GAAG,UAAU;AAEb,qBAAe,IAAI,aAAa,OAAO;AAAA,IACzC,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACF,GA1DkB;AAyEX,SAAS,cACd,KACA,QACA,SAIA;AAEA,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAmB,MAAM;AACjD,QAAI,OAAO,WAAW,YAAa,QAAO,OAAO,MAAM,EAAE;AACzD,UAAM,eAAe,uBAAuB;AAC5C,UAAM,QAAQ,aAAa,IAAI,GAAG;AAClC,UAAM,SAAS,OAAO,MAAM,SAAS,EAAE;AACvC,WAAO;AAAA,EACT,CAAC;AAED,QAAM,WAAW,OAAO,KAAK;AAC7B,QAAM,sBAAsB,OAAO,KAAK;AACxC,WAAS,UAAU;AAEnB,QAAM,WAAW;AAAA,IACf,CAAC,UAAoB;AACnB,0BAAoB,UAAU;AAE9B,eAAS,KAAK;AACd,eAAS,UAAU;AAEnB,YAAM,aAAa,UAAU,OAAO,OAAO,OAAO,UAAU,KAAK;AAEjE,cAAQ,QAAQ,KAAK,EAAE,OAAO,OAAO,OAAO,WAAW,CAAC;AAExD,gBAAU,EAAE,CAAC,GAAG,GAAG,WAAW,GAAG,SAAS,IAAI;AAE9C,iBAAW,MAAM;AACf,4BAAoB,UAAU;AAAA,MAChC,GAAG,EAAE;AAAA,IACP;AAAA,IACA,CAAC,KAAK,QAAQ,OAAO;AAAA,EACvB;AAEA,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAM,aAAa,6BAAM;AACvB,YAAM,eAAe,uBAAuB;AAC5C,YAAM,QAAQ,aAAa,IAAI,GAAG;AAClC,YAAM,SAAS,OAAO,MAAM,SAAS,EAAE;AACvC,UAAI,CAAC,OAAO,GAAG,SAAS,SAAS,MAAM,GAAG;AACxC,iBAAS,MAAM;AACf,iBAAS,UAAU;AAAA,MACrB;AAAA,IACF,GARmB;AAUnB,UAAM,kBAAkB,wBAAC,iBAAkC;AACzD,UAAI,oBAAoB,SAAS;AAC/B;AAAA,MACF;AAEA,YAAM,QAAQ,aAAa,IAAI,GAAG;AAClC,YAAM,SAAS,OAAO,MAAM,SAAS,EAAE;AACvC,UAAI,CAAC,OAAO,GAAG,SAAS,SAAS,MAAM,GAAG;AACxC,iBAAS,MAAM;AACf,iBAAS,UAAU;AAAA,MACrB;AAAA,IACF,GAXwB;AAaxB,UAAM,cAAc,wBAAC,YAAkD;AACrE,UAAI,oBAAoB,SAAS;AAC/B;AAAA,MACF;AAEA,UAAI,CAAC,OAAO,GAAG,SAAS,SAAS,QAAQ,KAAK,GAAG;AAC/C,iBAAS,QAAQ,KAAK;AACtB,iBAAS,UAAU,QAAQ;AAAA,MAC7B;AAAA,IACF,GAToB;AAWpB,WAAO,iBAAiB,YAAY,UAAU;AAC9C,YAAQ,GAAG,UAAU,eAAe;AACpC,YAAQ,MAAM,KAAK,WAAW;AAE9B,WAAO,MAAM;AACX,aAAO,oBAAoB,YAAY,UAAU;AACjD,cAAQ,IAAI,UAAU,eAAe;AACrC,cAAQ,OAAO,KAAK,WAAW;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,KAAK,MAAM,CAAC;AAEhB,SAAO,CAAC,OAAO,QAAQ;AACzB;AA1FgB;AA4FT,SAAS,eACd,SACA,UAAmB,CAAC,GAIpB;AACA,QAAM,OAAO,OAAO,KAAK,OAAO;AAChC,QAAM,YAAY,KAAK,KAAK,GAAG;AAE/B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwD,MAAM;AACtF,UAAM,eACJ,OAAO,WAAW,cAAc,IAAI,gBAAgB,IAAI,uBAAuB;AACjF,UAAM,SAAS,CAAC;AAEhB,WAAO,QAAQ,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,MAAM,MAAM;AACjD,YAAM,QAAQ,aAAa,IAAI,GAAG;AAClC,aAAO,GAAc,IAAI,OAAO,MAAM,SAAS,EAAE;AAAA,IACnD,CAAC;AAED,WAAO;AAAA,EACT,CAAC;AAED,QAAM,WAAW,OAAO,KAAK;AAC7B,WAAS,UAAU;AAEnB,QAAM,YAAY;AAAA,IAChB,CAAC,YAA2E;AAC1E,YAAM,WAAW,EAAE,GAAG,SAAS,SAAS,GAAG,QAAQ;AACnD,eAAS,QAAQ;AACjB,eAAS,UAAU;AAEnB,aAAO,QAAQ,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAChD,cAAM,SAAS,QAAQ,GAAG;AAC1B,YAAI,QAAQ;AACV,gBAAM,aAAa,UAAU,OAAO,OAAO,OAAO,UAAU,KAAK;AACjE,kBAAQ,QAAQ,KAAK,EAAE,OAAO,OAAO,OAAO,WAAW,CAAC;AAAA,QAC1D;AAAA,MACF,CAAC;AAED,YAAM,aAA4C,CAAC;AACnD,aAAO,QAAQ,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAChD,cAAM,SAAS,QAAQ,GAAG;AAC1B,YAAI,QAAQ;AACV,gBAAM,aAAa,UAAU,OAAO,OAAO,OAAO,UAAU,KAAK;AACjE,qBAAW,GAAG,IAAI;AAAA,QACpB;AAAA,MACF,CAAC;AAED,gBAAU,YAAY,SAAS,IAAI;AAAA,IACrC;AAAA,IACA,CAAC,SAAS,OAAO;AAAA,EACnB;AAEA,YAAU,MAAM;AACd,QAAI,OAAO,WAAW,YAAa;AAEnC,UAAMA,eAAc,wBAAC,cAA+B,cAAc,UAAU;AAC1E,YAAM,SAAS,CAAC;AAChB,UAAI,aAAa;AAEjB,aAAO,QAAQ,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,MAAM,MAAM;AACjD,cAAM,QAAQ,aAAa,IAAI,GAAG;AAClC,cAAM,SAAS,OAAO,MAAM,SAAS,EAAE;AACvC,cAAM,eAAe,SAAS,QAAQ,GAAc;AAEpD,YAAI,CAAC,OAAO,GAAG,cAAc,MAAM,GAAG;AACpC,uBAAa;AAAA,QACf;AACA,eAAO,GAAc,IAAI;AAAA,MAC3B,CAAC;AAED,UAAI,YAAY;AACd,iBAAS,MAAM;AACf,iBAAS,UAAU;AAAA,MACrB;AAAA,IACF,GAnBoB;AAqBpB,UAAM,aAAa,6BAAM;AACvB,YAAM,eAAe,uBAAuB;AAC5C,MAAAA,aAAY,cAAc,KAAK;AAAA,IACjC,GAHmB;AAKnB,UAAM,kBAAkB,wBAAC,iBAAkC;AACzD,MAAAA,aAAY,cAAc,IAAI;AAAA,IAChC,GAFwB;AAIxB,WAAO,iBAAiB,YAAY,UAAU;AAC9C,YAAQ,GAAG,UAAU,eAAe;AAEpC,WAAO,MAAM;AACX,aAAO,oBAAoB,YAAY,UAAU;AACjD,cAAQ,IAAI,UAAU,eAAe;AAAA,IACvC;AAAA,EACF,GAAG,CAAC,WAAW,OAAO,CAAC;AAEvB,SAAO,CAAC,OAAO,SAAS;AAC1B;AAjGgB;AAmGT,SAAS,cAAc,SAK3B;AACD,QAAM;AAAA,IACJ,cAAc;AAAA,IACd,eAAe;AAAA,IACf,UAAU;AAAA,IACV,WAAW;AAAA,EACb,IAAI,WAAW,CAAC;AAEhB,QAAM,CAAC,MAAM,OAAO,IAAI,cAAc,SAAS,UAAgB,YAAa,WAAW,CAAC;AACxF,QAAM,CAAC,OAAO,QAAQ,IAAI,cAAc,UAAU,UAAgB,YAAa,YAAY,CAAC;AAE5F,QAAM,cAAc,QAAQ;AAC5B,QAAM,eAAe,SAAS;AAC9B,QAAM,UAAW,cAAyB,KAAM;AAEhD,SAAO;AAAA,IACL,MAAM;AAAA,IACN;AAAA,IACA,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA,iBAAiB,6BAAM;AACrB,cAAQ,WAAW;AACnB,eAAS,YAAY;AAAA,IACvB,GAHiB;AAAA,EAInB;AACF;AA/BgB;AAiCT,SAAS,iBAAgD,SAI7D;AACD,QAAM;AAAA,IACJ,YAAY;AAAA,IACZ,iBAAiB,CAAC;AAAA,IAClB,aAAa,OAAO,KAAK,cAAc;AAAA,EACzC,IAAI,WAAW,CAAC;AAEhB,QAAM,CAAC,QAAQ,SAAS,IAAI,cAAc,WAAW,QAAc;AAEnE,QAAM,gBAAgB,WAAW;AAAA,IAC/B,CAAC,KAAK,QAAQ;AACZ,UAAI,GAAa,IAAI;AACrB,aAAO;AAAA,IACT;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,CAAC,SAAS,UAAU,IAAI,eAAe,aAAa;AAE1D,QAAM,eAAe,6BAAM;AACzB,cAAU,IAAI;AACd,UAAM,iBAAiB,WAAW;AAAA,MAChC,CAAC,KAAK,QAAQ;AACZ,YAAI,GAAa,IAAI;AACrB,eAAO;AAAA,MACT;AAAA,MACA,CAAC;AAAA,IACH;AACA,eAAW,cAAc;AAAA,EAC3B,GAVqB;AAYrB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AA1CgB;","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 { notifyUrlSearchObservers } from \"../client/url-search-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 notifyUrlSearchObservers(shallow);\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"]}
@@ -635,6 +635,16 @@ var _SPARouter = class _SPARouter {
635
635
  this.navigationListeners = /* @__PURE__ */ new Set();
636
636
  this.navigationState = IDLE_NAVIGATION_STATE;
637
637
  this.scrollElements = /* @__PURE__ */ new Map();
638
+ this.onPopState = /* @__PURE__ */ __name((event) => {
639
+ void this.handlePopState(event);
640
+ }, "onPopState");
641
+ this.onBeforeUnload = /* @__PURE__ */ __name((event) => {
642
+ if (this.blockers.size > 0) {
643
+ event.preventDefault();
644
+ event.returnValue = "";
645
+ }
646
+ this.saveScrollPosition(window.location.pathname);
647
+ }, "onBeforeUnload");
638
648
  this.options = {
639
649
  prefetchTimeout: options.prefetchTimeout ?? 100,
640
650
  cacheMaxAge: options.cacheMaxAge ?? 3e4,
@@ -644,16 +654,16 @@ var _SPARouter = class _SPARouter {
644
654
  deploymentId: options.deploymentId ?? readBrowserDeploymentId()
645
655
  };
646
656
  if (typeof window !== "undefined") {
647
- window.addEventListener("popstate", this.handlePopState.bind(this));
648
- window.addEventListener("beforeunload", (event) => {
649
- if (this.blockers.size > 0) {
650
- event.preventDefault();
651
- event.returnValue = "";
652
- }
653
- this.saveScrollPosition(window.location.pathname);
654
- });
657
+ window.addEventListener("popstate", this.onPopState);
658
+ window.addEventListener("beforeunload", this.onBeforeUnload);
655
659
  }
656
660
  }
661
+ /** Remove global listeners. Intended for tests and teardown. */
662
+ destroy() {
663
+ if (typeof window === "undefined") return;
664
+ window.removeEventListener("popstate", this.onPopState);
665
+ window.removeEventListener("beforeunload", this.onBeforeUnload);
666
+ }
657
667
  /**
658
668
  * Set the navigation handler that updates React
659
669
  */
@@ -1116,11 +1126,16 @@ function dispatchDeploymentMismatch(error) {
1116
1126
  window.dispatchEvent(new CustomEvent("farm:deployment-mismatch", { detail: error }));
1117
1127
  }
1118
1128
  __name(dispatchDeploymentMismatch, "dispatchDeploymentMismatch");
1129
+ function getInstalledFarmSPARouter() {
1130
+ if (typeof window === "undefined") return void 0;
1131
+ return window.__FARM_SPA_ROUTER__;
1132
+ }
1133
+ __name(getInstalledFarmSPARouter, "getInstalledFarmSPARouter");
1119
1134
  function getRouter() {
1120
1135
  if (typeof window === "undefined") {
1121
1136
  return new SPARouter();
1122
1137
  }
1123
- const installedRouter = window.__FARM_SPA_ROUTER__;
1138
+ const installedRouter = getInstalledFarmSPARouter();
1124
1139
  if (installedRouter) {
1125
1140
  routerInstance = installedRouter;
1126
1141
  return installedRouter;
@@ -1172,6 +1187,9 @@ function getScrollElementStorageKey(path, key) {
1172
1187
  }
1173
1188
  __name(getScrollElementStorageKey, "getScrollElementStorageKey");
1174
1189
 
1190
+ // src/client/url-search-sync.ts
1191
+ var FARM_URL_SEARCH_CHANGE_EVENT = "farm:urlsearchchange";
1192
+
1175
1193
  // src/tracing.ts
1176
1194
  var import_api = require("@opentelemetry/api");
1177
1195
  var FARM_TRACER_NAME = "@farm.js/core";
@@ -2414,10 +2432,12 @@ function createRendererRouter(options = {}) {
2414
2432
  }, "notify");
2415
2433
  const unsubscribeNavigation = router.subscribeNavigation(notify);
2416
2434
  window.addEventListener("popstate", notify);
2435
+ window.addEventListener(FARM_URL_SEARCH_CHANGE_EVENT, notify);
2417
2436
  read();
2418
2437
  return () => {
2419
2438
  unsubscribeNavigation();
2420
2439
  window.removeEventListener("popstate", notify);
2440
+ window.removeEventListener(FARM_URL_SEARCH_CHANGE_EVENT, notify);
2421
2441
  };
2422
2442
  },
2423
2443
  push(href, navigateOptions) {