@real-router/core 0.79.1 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/dist/cjs/api/cloneRouter.d.ts +1 -1
  2. package/dist/cjs/api/cloneRouter.js.map +1 -1
  3. package/dist/cjs/api.js +1 -2
  4. package/dist/cjs/api.js.map +1 -1
  5. package/dist/cjs/buildParamMeta-DniluAIV.js.map +1 -1
  6. package/dist/cjs/index.d.ts +2 -2
  7. package/dist/cjs/index.js +1 -2
  8. package/dist/cjs/internals.d.ts +1 -2
  9. package/dist/cjs/internals.d.ts.map +1 -1
  10. package/dist/cjs/internals.js.map +1 -1
  11. package/dist/cjs/types/base.d.ts +10 -1
  12. package/dist/cjs/types/base.d.ts.map +1 -1
  13. package/dist/cjs/types.d.ts +2 -2
  14. package/dist/cjs/types.d.ts.map +1 -1
  15. package/dist/cjs/validation.js +1 -2
  16. package/dist/esm/api/cloneRouter.d.mts +1 -1
  17. package/dist/esm/api.mjs +1 -1
  18. package/dist/esm/api.mjs.map +1 -1
  19. package/dist/esm/buildParamMeta-bOLhLF9h.mjs.map +1 -1
  20. package/dist/esm/index.d.mts +2 -2
  21. package/dist/esm/internals.d.mts +1 -2
  22. package/dist/esm/internals.d.mts.map +1 -1
  23. package/dist/esm/types/base.d.mts +10 -1
  24. package/dist/esm/types/base.d.mts.map +1 -1
  25. package/dist/esm/types.d.mts +2 -2
  26. package/dist/esm/types.d.mts.map +1 -1
  27. package/package.json +4 -13
  28. package/dist/cjs/cloneRouter-Ch25z3Z2.js +0 -2
  29. package/dist/cjs/cloneRouter-Ch25z3Z2.js.map +0 -1
  30. package/dist/cjs/utils/createRequestScope.d.ts +0 -111
  31. package/dist/cjs/utils/createRequestScope.d.ts.map +0 -1
  32. package/dist/cjs/utils/createRequestScope.js +0 -2
  33. package/dist/cjs/utils/createRequestScope.js.map +0 -1
  34. package/dist/cjs/utils/getStaticPaths.d.ts +0 -7
  35. package/dist/cjs/utils/getStaticPaths.d.ts.map +0 -1
  36. package/dist/cjs/utils/getStaticPaths.js +0 -2
  37. package/dist/cjs/utils/getStaticPaths.js.map +0 -1
  38. package/dist/cjs/utils/hydrateRouter.d.ts +0 -69
  39. package/dist/cjs/utils/hydrateRouter.d.ts.map +0 -1
  40. package/dist/cjs/utils/hydrateRouter.js +0 -2
  41. package/dist/cjs/utils/hydrateRouter.js.map +0 -1
  42. package/dist/cjs/utils/serializeRouterState.d.ts +0 -84
  43. package/dist/cjs/utils/serializeRouterState.d.ts.map +0 -1
  44. package/dist/cjs/utils/serializeRouterState.js +0 -2
  45. package/dist/cjs/utils/serializeRouterState.js.map +0 -1
  46. package/dist/cjs/utils/serializeState.d.ts +0 -50
  47. package/dist/cjs/utils/serializeState.d.ts.map +0 -1
  48. package/dist/cjs/utils/serializeState.js +0 -2
  49. package/dist/cjs/utils/serializeState.js.map +0 -1
  50. package/dist/cjs/utils.d.ts +0 -6
  51. package/dist/cjs/utils.js +0 -2
  52. package/dist/cjs/utils.js.map +0 -1
  53. package/dist/esm/cloneRouter-D4rnqmi9.mjs +0 -2
  54. package/dist/esm/cloneRouter-D4rnqmi9.mjs.map +0 -1
  55. package/dist/esm/utils/createRequestScope.d.mts +0 -111
  56. package/dist/esm/utils/createRequestScope.d.mts.map +0 -1
  57. package/dist/esm/utils/getStaticPaths.d.mts +0 -7
  58. package/dist/esm/utils/getStaticPaths.d.mts.map +0 -1
  59. package/dist/esm/utils/hydrateRouter.d.mts +0 -69
  60. package/dist/esm/utils/hydrateRouter.d.mts.map +0 -1
  61. package/dist/esm/utils/serializeRouterState.d.mts +0 -84
  62. package/dist/esm/utils/serializeRouterState.d.mts.map +0 -1
  63. package/dist/esm/utils/serializeState.d.mts +0 -50
  64. package/dist/esm/utils/serializeState.d.mts.map +0 -1
  65. package/dist/esm/utils.d.mts +0 -6
  66. package/dist/esm/utils.mjs +0 -2
  67. package/dist/esm/utils.mjs.map +0 -1
@@ -1,69 +0,0 @@
1
- import { State } from "../types/base.js";
2
- import { Router } from "../types/router.js";
3
- //#region src/utils/hydrateRouter.d.ts
4
- /**
5
- * Custom deserializer signature for {@link hydrateRouter} (#606). Compatible
6
- * with `JSON.parse` (default), `devalue.parse`, `superjson.parse`, or any
7
- * user-supplied function.
8
- */
9
- type Deserialize = (json: string) => unknown;
10
- interface HydrateRouterOptions {
11
- /**
12
- * Custom deserializer (e.g., `devalue.parse` / `superjson.parse`) for
13
- * matching the `serialize` passed to {@link serializeRouterState}. Defaults
14
- * to `JSON.parse`. Ignored when `source` is already an object.
15
- *
16
- * @default JSON.parse
17
- *
18
- * @example
19
- * ```typescript
20
- * import * as devalue from "devalue";
21
- * await hydrateRouter(router, ssrJson, { deserialize: devalue.parse });
22
- * ```
23
- */
24
- deserialize?: Deserialize;
25
- }
26
- /**
27
- * Hydrate a fresh router from server-serialized State (#563, #596).
28
- *
29
- * Accepts either a JSON string (parsed via `JSON.parse` by default, or
30
- * `options.deserialize` when provided) or a State-shaped object. Extracts
31
- * `state.path` and delegates to `router.start(state.path)` — the canonical
32
- * URL is the source of truth for the router on hydration.
33
- *
34
- * The full parsed state (incl. `state.context.<namespace>` payloads) is
35
- * deposited into a one-shot scratchpad on `RouterInternals.hydrationState`
36
- * before `start()` is invoked and cleared in the matching `finally`. SSR
37
- * loader plugins (`@real-router/ssr-data-plugin`,
38
- * `@real-router/rsc-server-plugin`) read this scratchpad to skip their loader
39
- * call when the server-resolved namespace value is already present — avoiding
40
- * the post-hydration loader re-run on first paint.
41
- *
42
- * Single-shot semantics: the scratchpad is consumed during the first `start()`
43
- * triggered by `hydrateRouter` regardless of route mismatch; subsequent
44
- * `start()` calls run loaders normally.
45
- *
46
- * @example
47
- * ```typescript
48
- * // Client
49
- * const router = createAppRouter();
50
- * router.usePlugin(browserPluginFactory());
51
- * await hydrateRouter(router, window.__SSR_STATE__);
52
- * ```
53
- *
54
- * @example
55
- * ```typescript
56
- * // With non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)
57
- * import * as devalue from "devalue";
58
- *
59
- * await hydrateRouter(router, window.__SSR_STATE__, {
60
- * deserialize: devalue.parse,
61
- * });
62
- * ```
63
- */
64
- declare function hydrateRouter(router: Router, source: string | {
65
- path: string;
66
- }, options?: HydrateRouterOptions): Promise<State>;
67
- //#endregion
68
- export { Deserialize, HydrateRouterOptions, hydrateRouter };
69
- //# sourceMappingURL=hydrateRouter.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hydrateRouter.d.ts","names":[],"sources":["../../../src/utils/hydrateRouter.ts"],"mappings":";;;;;;;;KAUY,eAAe;UAEV;;;;;;;;;;;;;;EAcf,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCM,cACpB,QAAQ,QACR;EAAmB;GACnB,UAAU,uBACT,QAAQ"}
@@ -1,2 +0,0 @@
1
- const e=require("../internals.js");async function t(t,n,r){let i=r?.deserialize??JSON.parse,a=typeof n==`string`?i(n):n,o=e.getInternals(t),s=o.hydrationState;o.hydrationState=a;try{return await t.start(a.path)}finally{o.hydrationState=s}}exports.hydrateRouter=t;
2
- //# sourceMappingURL=hydrateRouter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hydrateRouter.js","names":["getInternals"],"sources":["../../../src/utils/hydrateRouter.ts"],"sourcesContent":["import { getInternals } from \"../internals\";\n\nimport type { SerializedRouterState } from \"./serializeRouterState\";\nimport type { Router, State } from \"../types\";\n\n/**\n * Custom deserializer signature for {@link hydrateRouter} (#606). Compatible\n * with `JSON.parse` (default), `devalue.parse`, `superjson.parse`, or any\n * user-supplied function.\n */\nexport type Deserialize = (json: string) => unknown;\n\nexport interface HydrateRouterOptions {\n /**\n * Custom deserializer (e.g., `devalue.parse` / `superjson.parse`) for\n * matching the `serialize` passed to {@link serializeRouterState}. Defaults\n * to `JSON.parse`. Ignored when `source` is already an object.\n *\n * @default JSON.parse\n *\n * @example\n * ```typescript\n * import * as devalue from \"devalue\";\n * await hydrateRouter(router, ssrJson, { deserialize: devalue.parse });\n * ```\n */\n deserialize?: Deserialize;\n}\n\n/**\n * Hydrate a fresh router from server-serialized State (#563, #596).\n *\n * Accepts either a JSON string (parsed via `JSON.parse` by default, or\n * `options.deserialize` when provided) or a State-shaped object. Extracts\n * `state.path` and delegates to `router.start(state.path)` — the canonical\n * URL is the source of truth for the router on hydration.\n *\n * The full parsed state (incl. `state.context.<namespace>` payloads) is\n * deposited into a one-shot scratchpad on `RouterInternals.hydrationState`\n * before `start()` is invoked and cleared in the matching `finally`. SSR\n * loader plugins (`@real-router/ssr-data-plugin`,\n * `@real-router/rsc-server-plugin`) read this scratchpad to skip their loader\n * call when the server-resolved namespace value is already present — avoiding\n * the post-hydration loader re-run on first paint.\n *\n * Single-shot semantics: the scratchpad is consumed during the first `start()`\n * triggered by `hydrateRouter` regardless of route mismatch; subsequent\n * `start()` calls run loaders normally.\n *\n * @example\n * ```typescript\n * // Client\n * const router = createAppRouter();\n * router.usePlugin(browserPluginFactory());\n * await hydrateRouter(router, window.__SSR_STATE__);\n * ```\n *\n * @example\n * ```typescript\n * // With non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)\n * import * as devalue from \"devalue\";\n *\n * await hydrateRouter(router, window.__SSR_STATE__, {\n * deserialize: devalue.parse,\n * });\n * ```\n */\nexport async function hydrateRouter(\n router: Router,\n source: string | { path: string },\n options?: HydrateRouterOptions,\n): Promise<State> {\n const deserialize: Deserialize = options?.deserialize ?? JSON.parse;\n const parsed =\n typeof source === \"string\"\n ? (deserialize(source) as SerializedRouterState)\n : (source as SerializedRouterState);\n\n const ctx = getInternals(router);\n const previous = ctx.hydrationState;\n\n ctx.hydrationState = parsed;\n\n try {\n return await router.start(parsed.path);\n } finally {\n ctx.hydrationState = previous;\n }\n}\n"],"mappings":"mCAmEA,eAAsB,EACpB,EACA,EACA,EACgB,CAChB,IAAM,EAA2B,GAAS,aAAe,KAAK,MACxD,EACJ,OAAO,GAAW,SACb,EAAY,CAAM,EAClB,EAED,EAAMA,EAAAA,aAAa,CAAM,EACzB,EAAW,EAAI,eAErB,EAAI,eAAiB,EAErB,GAAI,CACF,OAAO,MAAM,EAAO,MAAM,EAAO,IAAI,CACvC,QAAU,CACR,EAAI,eAAiB,CACvB,CACF"}
@@ -1,84 +0,0 @@
1
- import { Params, State } from "../types/base.js";
2
- import { Serialize } from "./serializeState.js";
3
- //#region src/utils/serializeRouterState.d.ts
4
- /**
5
- * Parsed shape produced by {@link serializeRouterState} (after `JSON.parse`).
6
- *
7
- * Identical to {@link State} minus `transition` (per-navigation `TransitionMeta`
8
- * is meaningless after hydration; the client builds its own on commit). Used as
9
- * the input shape for {@link hydrateRouter} and as the type of the one-shot
10
- * hydration scratchpad consumed by SSR loader plugins.
11
- */
12
- type SerializedRouterState<P extends Params = Params> = Omit<State<P>, "transition">;
13
- interface SerializeRouterStateOptions {
14
- /**
15
- * Plugin context namespaces to strip from the serialized output.
16
- * Use when a plugin populates `state.context.<ns>` with non-JSON-serializable
17
- * values (e.g., RSC payload: ReactNode trees containing functions/symbols).
18
- *
19
- * @default []
20
- */
21
- excludeContext?: readonly string[];
22
- /**
23
- * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to
24
- * support non-JSON types in `state.params` and `state.context.<ns>` payloads
25
- * (Date / Map / Set / RegExp / BigInt). Defaults to `JSON.stringify`.
26
- *
27
- * Pair with the matching `deserialize` on `hydrateRouter` to round-trip the
28
- * extended types on the client.
29
- *
30
- * @default JSON.stringify
31
- *
32
- * @example
33
- * ```typescript
34
- * import * as devalue from "devalue";
35
- *
36
- * const json = serializeRouterState(state, { serialize: devalue.stringify });
37
- * ```
38
- */
39
- serialize?: Serialize;
40
- }
41
- /**
42
- * XSS-safe JSON serialization of router State for SSR → client transport (#563).
43
- *
44
- * Strips `state.transition` (per-navigation `TransitionMeta` — meaningless after
45
- * hydration; the client's hydration commit produces its own `transition`).
46
- * Keeps `name`, `params`, `path`, and `context` (plugin context namespaces are
47
- * preserved as-is — server's `state.context.data` from `ssr-data-plugin` and
48
- * any other plugin claims travel to the client untouched).
49
- *
50
- * Pass `options.excludeContext` to strip specific namespaces from the output —
51
- * required for plugins that publish non-JSON-serializable values (e.g., RSC
52
- * `ReactNode` trees from `@real-router/rsc-server-plugin`).
53
- *
54
- * @example
55
- * ```typescript
56
- * // Server
57
- * const state = await router.start(req.url);
58
- * const html = `<script>window.__SSR_STATE__=${serializeRouterState(state)}</script>`;
59
- *
60
- * // Client
61
- * await hydrateRouter(router, window.__SSR_STATE__);
62
- * ```
63
- *
64
- * @example
65
- * ```typescript
66
- * // With RSC plugin: strip the "rsc" namespace before transport
67
- * const state = await router.start(url);
68
- * const json = serializeRouterState(state, { excludeContext: ["rsc"] });
69
- * ```
70
- *
71
- * @example
72
- * ```typescript
73
- * // Non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)
74
- * import * as devalue from "devalue";
75
- *
76
- * const json = serializeRouterState(state, { serialize: devalue.stringify });
77
- * // On the client:
78
- * await hydrateRouter(router, json, { deserialize: devalue.parse });
79
- * ```
80
- */
81
- declare function serializeRouterState(state: State, options?: SerializeRouterStateOptions): string;
82
- //#endregion
83
- export { SerializeRouterStateOptions, SerializedRouterState, serializeRouterState };
84
- //# sourceMappingURL=serializeRouterState.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serializeRouterState.d.ts","names":[],"sources":["../../../src/utils/serializeRouterState.ts"],"mappings":";;;;;;;;;;;KAaY,sBAAsB,UAAU,SAAS,UAAU,KAC7D,MAAM;UAIS;;;;;;;;EAQf;;;;;;;;;;;;;;;;;;EAmBA,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA2CE,qBACd,OAAO,OACP,UAAU"}
@@ -1,2 +0,0 @@
1
- const e=require("./serializeState.js");function t(t,n){let r=n?.excludeContext,i=t.context;if(r?.length){let e=Object.create(null),n=t.context;for(let[t,i]of Object.entries(n))r.includes(t)||(e[t]=i);i=e}let a={name:t.name,params:t.params,path:t.path,context:i};return n?.serialize?e.serializeState(a,{serialize:n.serialize}):e.serializeState(a)}exports.serializeRouterState=t;
2
- //# sourceMappingURL=serializeRouterState.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serializeRouterState.js","names":["serializeState"],"sources":["../../../src/utils/serializeRouterState.ts"],"sourcesContent":["import { serializeState } from \"./serializeState\";\n\nimport type { Serialize } from \"./serializeState\";\nimport type { Params, State } from \"../types\";\n\n/**\n * Parsed shape produced by {@link serializeRouterState} (after `JSON.parse`).\n *\n * Identical to {@link State} minus `transition` (per-navigation `TransitionMeta`\n * is meaningless after hydration; the client builds its own on commit). Used as\n * the input shape for {@link hydrateRouter} and as the type of the one-shot\n * hydration scratchpad consumed by SSR loader plugins.\n */\nexport type SerializedRouterState<P extends Params = Params> = Omit<\n State<P>,\n \"transition\"\n>;\n\nexport interface SerializeRouterStateOptions {\n /**\n * Plugin context namespaces to strip from the serialized output.\n * Use when a plugin populates `state.context.<ns>` with non-JSON-serializable\n * values (e.g., RSC payload: ReactNode trees containing functions/symbols).\n *\n * @default []\n */\n excludeContext?: readonly string[];\n\n /**\n * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to\n * support non-JSON types in `state.params` and `state.context.<ns>` payloads\n * (Date / Map / Set / RegExp / BigInt). Defaults to `JSON.stringify`.\n *\n * Pair with the matching `deserialize` on `hydrateRouter` to round-trip the\n * extended types on the client.\n *\n * @default JSON.stringify\n *\n * @example\n * ```typescript\n * import * as devalue from \"devalue\";\n *\n * const json = serializeRouterState(state, { serialize: devalue.stringify });\n * ```\n */\n serialize?: Serialize;\n}\n\n/**\n * XSS-safe JSON serialization of router State for SSR → client transport (#563).\n *\n * Strips `state.transition` (per-navigation `TransitionMeta` — meaningless after\n * hydration; the client's hydration commit produces its own `transition`).\n * Keeps `name`, `params`, `path`, and `context` (plugin context namespaces are\n * preserved as-is — server's `state.context.data` from `ssr-data-plugin` and\n * any other plugin claims travel to the client untouched).\n *\n * Pass `options.excludeContext` to strip specific namespaces from the output —\n * required for plugins that publish non-JSON-serializable values (e.g., RSC\n * `ReactNode` trees from `@real-router/rsc-server-plugin`).\n *\n * @example\n * ```typescript\n * // Server\n * const state = await router.start(req.url);\n * const html = `<script>window.__SSR_STATE__=${serializeRouterState(state)}</script>`;\n *\n * // Client\n * await hydrateRouter(router, window.__SSR_STATE__);\n * ```\n *\n * @example\n * ```typescript\n * // With RSC plugin: strip the \"rsc\" namespace before transport\n * const state = await router.start(url);\n * const json = serializeRouterState(state, { excludeContext: [\"rsc\"] });\n * ```\n *\n * @example\n * ```typescript\n * // Non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)\n * import * as devalue from \"devalue\";\n *\n * const json = serializeRouterState(state, { serialize: devalue.stringify });\n * // On the client:\n * await hydrateRouter(router, json, { deserialize: devalue.parse });\n * ```\n */\nexport function serializeRouterState(\n state: State,\n options?: SerializeRouterStateOptions,\n): string {\n const exclude = options?.excludeContext;\n\n let context = state.context;\n\n if (exclude?.length) {\n // Null-proto target so a literal \"__proto__\" namespace (a real own key on\n // state.context, e.g. written by claimContextNamespace) is copied as a\n // genuine own entry by the plain assignment below — a `{}` target would\n // instead dispatch into the inherited Object.prototype.__proto__ setter and\n // silently drop the data (#1191, same hazard the write path guards).\n const filtered: Record<string, unknown> = Object.create(null) as Record<\n string,\n unknown\n >;\n const source = state.context;\n\n for (const [key, value] of Object.entries(source)) {\n if (!exclude.includes(key)) {\n filtered[key] = value;\n }\n }\n\n context = filtered;\n }\n\n const payload = {\n name: state.name,\n params: state.params,\n path: state.path,\n context,\n };\n\n return options?.serialize\n ? serializeState(payload, { serialize: options.serialize })\n : serializeState(payload);\n}\n"],"mappings":"uCAwFA,SAAgB,EACd,EACA,EACQ,CACR,IAAM,EAAU,GAAS,eAErB,EAAU,EAAM,QAEpB,GAAI,GAAS,OAAQ,CAMnB,IAAM,EAAoC,OAAO,OAAO,IAAI,EAItD,EAAS,EAAM,QAErB,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EACzC,EAAQ,SAAS,CAAG,IACvB,EAAS,GAAO,GAIpB,EAAU,CACZ,CAEA,IAAM,EAAU,CACd,KAAM,EAAM,KACZ,OAAQ,EAAM,OACd,KAAM,EAAM,KACZ,SACF,EAEA,OAAO,GAAS,UACZA,EAAAA,eAAe,EAAS,CAAE,UAAW,EAAQ,SAAU,CAAC,EACxDA,EAAAA,eAAe,CAAO,CAC5B"}
@@ -1,50 +0,0 @@
1
- //#region src/utils/serializeState.d.ts
2
- /**
3
- * Custom serializer signature for {@link serializeState} (#606).
4
- *
5
- * Compatible with `JSON.stringify` (default) as well as `devalue.stringify`,
6
- * `superjson.stringify`, or any user-supplied function that returns a JSON
7
- * string. The output is run through XSS-safe character escapes regardless
8
- * of which serializer produced it.
9
- */
10
- type Serialize = (data: unknown) => string;
11
- interface SerializeStateOptions {
12
- /**
13
- * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to
14
- * support non-JSON types (Date / Map / Set / RegExp / BigInt). Defaults to
15
- * `JSON.stringify`. Output is still XSS-escaped.
16
- *
17
- * @default JSON.stringify
18
- */
19
- serialize?: Serialize;
20
- }
21
- /**
22
- * XSS-safe JSON serialization for embedding data in HTML `<script>` tags.
23
- *
24
- * Escapes `<`, `>`, and `&` to their Unicode equivalents to prevent
25
- * injection via `</script>` or HTML entities inside inline scripts.
26
- *
27
- * Pass `options.serialize` to use a custom serializer such as `devalue` or
28
- * `superjson` for non-JSON types (Date/Map/Set/RegExp/BigInt). The serializer
29
- * must return a string; XSS-escape is applied to its output.
30
- *
31
- * @example
32
- * ```typescript
33
- * const json = serializeState({ name: "home", path: "/" });
34
- * const html = `<script>window.__STATE__=${json}</script>`;
35
- * ```
36
- *
37
- * @example
38
- * ```typescript
39
- * import * as devalue from "devalue";
40
- *
41
- * const json = serializeState(
42
- * { date: new Date(), tags: new Set(["a", "b"]) },
43
- * { serialize: devalue.stringify },
44
- * );
45
- * ```
46
- */
47
- declare function serializeState(data: unknown, options?: SerializeStateOptions): string;
48
- //#endregion
49
- export { Serialize, SerializeStateOptions, serializeState };
50
- //# sourceMappingURL=serializeState.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serializeState.d.ts","names":[],"sources":["../../../src/utils/serializeState.ts"],"mappings":";;;;;;;;;KAQY,aAAa;UAER;;;;;;;;EAQf,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6BE,eACd,eACA,UAAU"}
@@ -1,2 +0,0 @@
1
- function e(e,t){return((t?.serialize??JSON.stringify)(e)??`null`).replaceAll(`<`,String.raw`\u003c`).replaceAll(`>`,String.raw`\u003e`).replaceAll(`&`,String.raw`\u0026`)}exports.serializeState=e;
2
- //# sourceMappingURL=serializeState.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"serializeState.js","names":[],"sources":["../../../src/utils/serializeState.ts"],"sourcesContent":["/**\n * Custom serializer signature for {@link serializeState} (#606).\n *\n * Compatible with `JSON.stringify` (default) as well as `devalue.stringify`,\n * `superjson.stringify`, or any user-supplied function that returns a JSON\n * string. The output is run through XSS-safe character escapes regardless\n * of which serializer produced it.\n */\nexport type Serialize = (data: unknown) => string;\n\nexport interface SerializeStateOptions {\n /**\n * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to\n * support non-JSON types (Date / Map / Set / RegExp / BigInt). Defaults to\n * `JSON.stringify`. Output is still XSS-escaped.\n *\n * @default JSON.stringify\n */\n serialize?: Serialize;\n}\n\n/**\n * XSS-safe JSON serialization for embedding data in HTML `<script>` tags.\n *\n * Escapes `<`, `>`, and `&` to their Unicode equivalents to prevent\n * injection via `</script>` or HTML entities inside inline scripts.\n *\n * Pass `options.serialize` to use a custom serializer such as `devalue` or\n * `superjson` for non-JSON types (Date/Map/Set/RegExp/BigInt). The serializer\n * must return a string; XSS-escape is applied to its output.\n *\n * @example\n * ```typescript\n * const json = serializeState({ name: \"home\", path: \"/\" });\n * const html = `<script>window.__STATE__=${json}</script>`;\n * ```\n *\n * @example\n * ```typescript\n * import * as devalue from \"devalue\";\n *\n * const json = serializeState(\n * { date: new Date(), tags: new Set([\"a\", \"b\"]) },\n * { serialize: devalue.stringify },\n * );\n * ```\n */\nexport function serializeState(\n data: unknown,\n options?: SerializeStateOptions,\n): string {\n const serialize = options?.serialize ?? JSON.stringify;\n // JSON.stringify returns undefined for top-level `undefined`, Symbol,\n // function, and other non-serializable values (lib.d.ts types it as\n // `string` but the runtime can return undefined). A custom serializer\n // that returns undefined for unsupported input is normalized the same way.\n const serialized = (serialize(data) as string | undefined) ?? \"null\";\n\n return serialized\n .replaceAll(\"<\", String.raw`\\u003c`)\n .replaceAll(\">\", String.raw`\\u003e`)\n .replaceAll(\"&\", String.raw`\\u0026`);\n}\n"],"mappings":"AA+CA,SAAgB,EACd,EACA,EACQ,CAQR,QAPkB,GAAS,WAAa,KAAK,UAAA,CAKf,CAAI,GAA4B,OAAA,CAG3D,WAAW,IAAK,OAAO,GAAG,QAAQ,CAAC,CACnC,WAAW,IAAK,OAAO,GAAG,QAAQ,CAAC,CACnC,WAAW,IAAK,OAAO,GAAG,QAAQ,CACvC"}
@@ -1,6 +0,0 @@
1
- import { IncomingMessageLike, RequestLike, RequestScope, RequestScopeSource, createRequestScope } from "./utils/createRequestScope.js";
2
- import { StaticPathEntries, getStaticPaths } from "./utils/getStaticPaths.js";
3
- import { Deserialize, HydrateRouterOptions, hydrateRouter } from "./utils/hydrateRouter.js";
4
- import { Serialize, SerializeStateOptions, serializeState } from "./utils/serializeState.js";
5
- import { SerializeRouterStateOptions, SerializedRouterState, serializeRouterState } from "./utils/serializeRouterState.js";
6
- export { type Deserialize, type HydrateRouterOptions, type IncomingMessageLike, type RequestLike, type RequestScope, type RequestScopeSource, type Serialize, type SerializeRouterStateOptions, type SerializeStateOptions, type SerializedRouterState, type StaticPathEntries, createRequestScope, getStaticPaths, hydrateRouter, serializeRouterState, serializeState };
package/dist/cjs/utils.js DELETED
@@ -1,2 +0,0 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./buildParamMeta-DniluAIV.js"),t=require("./cloneRouter-Ch25z3Z2.js");function n(e){return`signal`in e&&typeof e.signal==`object`&&e.signal!==void 0&&typeof e.signal.aborted==`boolean`}function r(e,r,i){let a,o,s;if(n(e))s=e.signal;else{let t=new AbortController,n=()=>{t.abort()};s=t.signal,o=()=>{e.on(`close`,n)},a=()=>{e.removeListener?.(`close`,n)}}let c=t.t(r,{...i,abortSignal:s});o?.();let l=!1,u=()=>l?Promise.resolve():(l=!0,a?.(),c.dispose(),Promise.resolve());return{router:c,signal:s,dispose:u,[Symbol.asyncDispose]:u}}function i(e,t){for(let n of e.children.values())n.children.size===0?t.push(n.fullName):i(n,t)}function a(e){let t=[];return i(e,t),t}async function o(e,n){let r=a(t.r(e).getTree()),i=[];for(let t of r){let r=n?.[t];if(r){let n=await r();for(let r of n)i.push(e.buildPath(t,r))}else i.push(e.buildPath(t,{}))}return i}async function s(t,n,r){let i=r?.deserialize??JSON.parse,a=typeof n==`string`?i(n):n,o=e.u(t),s=o.hydrationState;o.hydrationState=a;try{return await t.start(a.path)}finally{o.hydrationState=s}}function c(e,t){return((t?.serialize??JSON.stringify)(e)??`null`).replaceAll(`<`,String.raw`\u003c`).replaceAll(`>`,String.raw`\u003e`).replaceAll(`&`,String.raw`\u0026`)}function l(e,t){let n=t?.excludeContext,r=e.context;if(n?.length){let t=Object.create(null),i=e.context;for(let[e,r]of Object.entries(i))n.includes(e)||(t[e]=r);r=t}let i={name:e.name,params:e.params,path:e.path,context:r};return t?.serialize?c(i,{serialize:t.serialize}):c(i)}exports.createRequestScope=r,exports.getStaticPaths=o,exports.hydrateRouter=s,exports.serializeRouterState=l,exports.serializeState=c;
2
- //# sourceMappingURL=utils.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utils.js","names":["cloneRouter","getPluginApi","getInternals"],"sources":["../../src/utils/createRequestScope.ts","../../src/utils/getStaticPaths.ts","../../src/utils/hydrateRouter.ts","../../src/utils/serializeState.ts","../../src/utils/serializeRouterState.ts"],"sourcesContent":["import { cloneRouter } from \"../api/cloneRouter\";\n\nimport type { Router as RouterClass } from \"../Router\";\nimport type { DefaultDependencies, Router } from \"../types\";\n\n/**\n * Subset of Node's `http.IncomingMessage` that `createRequestScope` relies on:\n * a `\"close\"` event indicating that the client disconnected (or the response\n * was fully sent) and the standard `removeListener` cleanup hook.\n */\nexport interface IncomingMessageLike {\n on: (event: \"close\", listener: () => void) => unknown;\n removeListener?: (event: \"close\", listener: () => void) => unknown;\n}\n\n/**\n * Web `Request`-shaped object — anything carrying an `AbortSignal`. Web\n * runtimes (Bun, Cloudflare Workers, Vite RSC) surface client-disconnect via\n * `request.signal` directly, so no listener attachment is needed.\n */\nexport interface RequestLike {\n signal: AbortSignal;\n}\n\nexport type RequestScopeSource = IncomingMessageLike | RequestLike;\n\nexport interface RequestScope<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n> extends AsyncDisposable {\n /**\n * Per-request router clone. Carries `abortSignal` injected into its\n * dependencies — loaders can `getDep(\"abortSignal\")` and pass it to fetch /\n * `withTimeout` for cooperative cancellation when the client disconnects.\n */\n readonly router: RouterClass<Dependencies>;\n\n /**\n * Aborts when the request closes (Node `IncomingMessage`'s `\"close\"` event)\n * or when the upstream Web `Request.signal` aborts.\n */\n readonly signal: AbortSignal;\n\n /**\n * Detach the close listener (if attached to a Node `IncomingMessage`) and\n * dispose the cloned router. Idempotent — safe to call multiple times or in\n * combination with `Symbol.asyncDispose`.\n */\n dispose: () => Promise<void>;\n}\n\nfunction isRequestLike(request: RequestScopeSource): request is RequestLike {\n return (\n \"signal\" in request &&\n typeof (request as Partial<RequestLike>).signal === \"object\" &&\n (request as Partial<RequestLike>).signal !== undefined &&\n typeof request.signal.aborted === \"boolean\"\n );\n}\n\n/**\n * Build a per-request router scope: clones `base`, attaches an `AbortSignal`\n * tied to the request's lifetime, and exposes `dispose()` (plus\n * `Symbol.asyncDispose` for `await using` declarations).\n *\n * Replaces the four-step boilerplate that every server entry repeats:\n *\n * 1. `new AbortController()` per request\n * 2. `req.on(\"close\", () => controller.abort())`\n * 3. `cloneRouter(base, { ...deps, abortSignal: signal })`\n * 4. `try { ... } finally { router.dispose() }`\n *\n * The signal is injected into the router clone under `abortSignal` so existing\n * loaders that read `getDep(\"abortSignal\")` keep working without changes. If\n * `deps` already carries an `abortSignal`, the request-tied signal **wins** (it\n * is spread last) — a caller-supplied `abortSignal` is intentionally overridden,\n * since the scope's purpose is to own the request-lifecycle signal.\n *\n * ## `await using` compatibility\n *\n * The scope implements `Symbol.asyncDispose`, so `await using scope = …` is\n * supported on runtimes that ship the well-known `Symbol.asyncDispose`:\n *\n * - **Node.js 24+** (full support; partial in 20.4–20.17 only for `fs`/`stream`)\n * - **Bun 1.0.23+**, **Deno 1.37+**\n * - **Chrome / Edge 127+**, **Firefox 141+**\n * - **Safari**: not yet supported (irrelevant in practice — this helper is\n * server-side only and never reaches the browser)\n *\n * On Node.js 22 LTS the well-known symbol is unavailable, so `await using`\n * fails. **The bundled SSR examples therefore use the explicit\n * `try/finally` + `await scope.dispose()` form**, which works on every\n * runtime. Use `await using` only when you control the deployment target and\n * know it ships the symbol.\n *\n * @example\n * ```typescript\n * // Explicit dispose — works on Node 18+, all browsers, every CI image\n * export async function render(url: string, req: IncomingMessage) {\n * const scope = createRequestScope(req, baseRouter, { currentUser });\n * try {\n * scope.router.usePlugin(ssrDataPluginFactory(loaders));\n * return await renderShell(scope.router, url);\n * } finally {\n * await scope.dispose();\n * }\n * }\n *\n * // `await using` — Node 24+, Bun, Deno, modern browsers\n * export async function render(url: string, req: IncomingMessage) {\n * await using scope = createRequestScope(req, baseRouter, { currentUser });\n * scope.router.usePlugin(ssrDataPluginFactory(loaders));\n * return await renderShell(scope.router, url);\n * }\n *\n * // Web runtime (signal already on the request)\n * async function handler(request: Request) {\n * const scope = createRequestScope(request, baseRouter, { db });\n * try {\n * scope.router.usePlugin(rscServerPluginFactory(loaders));\n * return await render(scope.router, request.url);\n * } finally {\n * await scope.dispose();\n * }\n * }\n * ```\n */\nexport function createRequestScope<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n request: RequestScopeSource,\n base: Router<Dependencies>,\n deps?: Partial<Dependencies>,\n): RequestScope<Dependencies> {\n let detach: (() => void) | undefined;\n let attach: (() => void) | undefined;\n let signal: AbortSignal;\n\n if (isRequestLike(request)) {\n signal = request.signal;\n } else {\n const controller = new AbortController();\n const onClose = (): void => {\n controller.abort();\n };\n\n signal = controller.signal;\n // Clone-before-attach (#969): defer attaching the \"close\" listener until\n // after cloneRouter succeeds. cloneRouter is synchronous, so no \"close\"\n // macrotask can fire in the gap; and if it throws (e.g. ROUTER_DISPOSED on\n // an already-disposed base) the helper exits without returning a scope\n // handle — so a listener attached here would strand on the request with no\n // way to detach it.\n attach = () => {\n request.on(\"close\", onClose);\n };\n detach = () => {\n request.removeListener?.(\"close\", onClose);\n };\n }\n\n const router = cloneRouter(base, {\n ...deps,\n abortSignal: signal,\n } as Dependencies);\n\n // The clone exists — now it is safe to attach the close listener (Node path\n // only; `attach` is undefined for the Web path).\n attach?.();\n\n let disposed = false;\n\n const dispose = (): Promise<void> => {\n if (disposed) {\n return Promise.resolve();\n }\n\n disposed = true;\n detach?.();\n router.dispose();\n\n return Promise.resolve();\n };\n\n return {\n router,\n signal,\n dispose,\n // eslint-disable-next-line unicorn/no-nonstandard-builtin-properties -- Symbol.asyncDispose is a standard ES2023 well-known symbol (`await using`)\n [Symbol.asyncDispose]: dispose,\n };\n}\n","import { getPluginApi } from \"../api/getPluginApi\";\n\nimport type { RouteTree } from \"../engine\";\nimport type { DefaultDependencies, Router } from \"../types\";\n\nexport type StaticPathEntries = Record<\n string,\n () => Promise<Record<string, string>[]>\n>;\n\nfunction collectLeafRouteNames(node: RouteTree, result: string[]): void {\n for (const child of node.children.values()) {\n if (child.children.size === 0) {\n result.push(child.fullName);\n } else {\n // Accumulate into the shared array rather than\n // `result.push(...getLeafRouteNames(child))`: the spread passes one\n // argument per leaf, and V8 caps spread/apply arguments (~124k on Node 24),\n // so a section with that many static leaf routes threw\n // `RangeError: Maximum call stack size exceeded`. Accumulating also drops\n // the per-subtree intermediate-array allocation.\n collectLeafRouteNames(child, result);\n }\n }\n}\n\nfunction getLeafRouteNames(node: RouteTree): string[] {\n const result: string[] = [];\n\n collectLeafRouteNames(node, result);\n\n return result;\n}\n\nexport async function getStaticPaths<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n router: Router<Dependencies>,\n entries?: StaticPathEntries,\n): Promise<string[]> {\n const tree = getPluginApi(router).getTree();\n const leafRoutes = getLeafRouteNames(tree);\n const paths: string[] = [];\n\n for (const routeName of leafRoutes) {\n const entryFn = entries?.[routeName];\n\n if (entryFn) {\n const paramSets = await entryFn();\n\n for (const params of paramSets) {\n paths.push(router.buildPath(routeName, params));\n }\n } else {\n paths.push(router.buildPath(routeName, {}));\n }\n }\n\n return paths;\n}\n","import { getInternals } from \"../internals\";\n\nimport type { SerializedRouterState } from \"./serializeRouterState\";\nimport type { Router, State } from \"../types\";\n\n/**\n * Custom deserializer signature for {@link hydrateRouter} (#606). Compatible\n * with `JSON.parse` (default), `devalue.parse`, `superjson.parse`, or any\n * user-supplied function.\n */\nexport type Deserialize = (json: string) => unknown;\n\nexport interface HydrateRouterOptions {\n /**\n * Custom deserializer (e.g., `devalue.parse` / `superjson.parse`) for\n * matching the `serialize` passed to {@link serializeRouterState}. Defaults\n * to `JSON.parse`. Ignored when `source` is already an object.\n *\n * @default JSON.parse\n *\n * @example\n * ```typescript\n * import * as devalue from \"devalue\";\n * await hydrateRouter(router, ssrJson, { deserialize: devalue.parse });\n * ```\n */\n deserialize?: Deserialize;\n}\n\n/**\n * Hydrate a fresh router from server-serialized State (#563, #596).\n *\n * Accepts either a JSON string (parsed via `JSON.parse` by default, or\n * `options.deserialize` when provided) or a State-shaped object. Extracts\n * `state.path` and delegates to `router.start(state.path)` — the canonical\n * URL is the source of truth for the router on hydration.\n *\n * The full parsed state (incl. `state.context.<namespace>` payloads) is\n * deposited into a one-shot scratchpad on `RouterInternals.hydrationState`\n * before `start()` is invoked and cleared in the matching `finally`. SSR\n * loader plugins (`@real-router/ssr-data-plugin`,\n * `@real-router/rsc-server-plugin`) read this scratchpad to skip their loader\n * call when the server-resolved namespace value is already present — avoiding\n * the post-hydration loader re-run on first paint.\n *\n * Single-shot semantics: the scratchpad is consumed during the first `start()`\n * triggered by `hydrateRouter` regardless of route mismatch; subsequent\n * `start()` calls run loaders normally.\n *\n * @example\n * ```typescript\n * // Client\n * const router = createAppRouter();\n * router.usePlugin(browserPluginFactory());\n * await hydrateRouter(router, window.__SSR_STATE__);\n * ```\n *\n * @example\n * ```typescript\n * // With non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)\n * import * as devalue from \"devalue\";\n *\n * await hydrateRouter(router, window.__SSR_STATE__, {\n * deserialize: devalue.parse,\n * });\n * ```\n */\nexport async function hydrateRouter(\n router: Router,\n source: string | { path: string },\n options?: HydrateRouterOptions,\n): Promise<State> {\n const deserialize: Deserialize = options?.deserialize ?? JSON.parse;\n const parsed =\n typeof source === \"string\"\n ? (deserialize(source) as SerializedRouterState)\n : (source as SerializedRouterState);\n\n const ctx = getInternals(router);\n const previous = ctx.hydrationState;\n\n ctx.hydrationState = parsed;\n\n try {\n return await router.start(parsed.path);\n } finally {\n ctx.hydrationState = previous;\n }\n}\n","/**\n * Custom serializer signature for {@link serializeState} (#606).\n *\n * Compatible with `JSON.stringify` (default) as well as `devalue.stringify`,\n * `superjson.stringify`, or any user-supplied function that returns a JSON\n * string. The output is run through XSS-safe character escapes regardless\n * of which serializer produced it.\n */\nexport type Serialize = (data: unknown) => string;\n\nexport interface SerializeStateOptions {\n /**\n * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to\n * support non-JSON types (Date / Map / Set / RegExp / BigInt). Defaults to\n * `JSON.stringify`. Output is still XSS-escaped.\n *\n * @default JSON.stringify\n */\n serialize?: Serialize;\n}\n\n/**\n * XSS-safe JSON serialization for embedding data in HTML `<script>` tags.\n *\n * Escapes `<`, `>`, and `&` to their Unicode equivalents to prevent\n * injection via `</script>` or HTML entities inside inline scripts.\n *\n * Pass `options.serialize` to use a custom serializer such as `devalue` or\n * `superjson` for non-JSON types (Date/Map/Set/RegExp/BigInt). The serializer\n * must return a string; XSS-escape is applied to its output.\n *\n * @example\n * ```typescript\n * const json = serializeState({ name: \"home\", path: \"/\" });\n * const html = `<script>window.__STATE__=${json}</script>`;\n * ```\n *\n * @example\n * ```typescript\n * import * as devalue from \"devalue\";\n *\n * const json = serializeState(\n * { date: new Date(), tags: new Set([\"a\", \"b\"]) },\n * { serialize: devalue.stringify },\n * );\n * ```\n */\nexport function serializeState(\n data: unknown,\n options?: SerializeStateOptions,\n): string {\n const serialize = options?.serialize ?? JSON.stringify;\n // JSON.stringify returns undefined for top-level `undefined`, Symbol,\n // function, and other non-serializable values (lib.d.ts types it as\n // `string` but the runtime can return undefined). A custom serializer\n // that returns undefined for unsupported input is normalized the same way.\n const serialized = (serialize(data) as string | undefined) ?? \"null\";\n\n return serialized\n .replaceAll(\"<\", String.raw`\\u003c`)\n .replaceAll(\">\", String.raw`\\u003e`)\n .replaceAll(\"&\", String.raw`\\u0026`);\n}\n","import { serializeState } from \"./serializeState\";\n\nimport type { Serialize } from \"./serializeState\";\nimport type { Params, State } from \"../types\";\n\n/**\n * Parsed shape produced by {@link serializeRouterState} (after `JSON.parse`).\n *\n * Identical to {@link State} minus `transition` (per-navigation `TransitionMeta`\n * is meaningless after hydration; the client builds its own on commit). Used as\n * the input shape for {@link hydrateRouter} and as the type of the one-shot\n * hydration scratchpad consumed by SSR loader plugins.\n */\nexport type SerializedRouterState<P extends Params = Params> = Omit<\n State<P>,\n \"transition\"\n>;\n\nexport interface SerializeRouterStateOptions {\n /**\n * Plugin context namespaces to strip from the serialized output.\n * Use when a plugin populates `state.context.<ns>` with non-JSON-serializable\n * values (e.g., RSC payload: ReactNode trees containing functions/symbols).\n *\n * @default []\n */\n excludeContext?: readonly string[];\n\n /**\n * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to\n * support non-JSON types in `state.params` and `state.context.<ns>` payloads\n * (Date / Map / Set / RegExp / BigInt). Defaults to `JSON.stringify`.\n *\n * Pair with the matching `deserialize` on `hydrateRouter` to round-trip the\n * extended types on the client.\n *\n * @default JSON.stringify\n *\n * @example\n * ```typescript\n * import * as devalue from \"devalue\";\n *\n * const json = serializeRouterState(state, { serialize: devalue.stringify });\n * ```\n */\n serialize?: Serialize;\n}\n\n/**\n * XSS-safe JSON serialization of router State for SSR → client transport (#563).\n *\n * Strips `state.transition` (per-navigation `TransitionMeta` — meaningless after\n * hydration; the client's hydration commit produces its own `transition`).\n * Keeps `name`, `params`, `path`, and `context` (plugin context namespaces are\n * preserved as-is — server's `state.context.data` from `ssr-data-plugin` and\n * any other plugin claims travel to the client untouched).\n *\n * Pass `options.excludeContext` to strip specific namespaces from the output —\n * required for plugins that publish non-JSON-serializable values (e.g., RSC\n * `ReactNode` trees from `@real-router/rsc-server-plugin`).\n *\n * @example\n * ```typescript\n * // Server\n * const state = await router.start(req.url);\n * const html = `<script>window.__SSR_STATE__=${serializeRouterState(state)}</script>`;\n *\n * // Client\n * await hydrateRouter(router, window.__SSR_STATE__);\n * ```\n *\n * @example\n * ```typescript\n * // With RSC plugin: strip the \"rsc\" namespace before transport\n * const state = await router.start(url);\n * const json = serializeRouterState(state, { excludeContext: [\"rsc\"] });\n * ```\n *\n * @example\n * ```typescript\n * // Non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)\n * import * as devalue from \"devalue\";\n *\n * const json = serializeRouterState(state, { serialize: devalue.stringify });\n * // On the client:\n * await hydrateRouter(router, json, { deserialize: devalue.parse });\n * ```\n */\nexport function serializeRouterState(\n state: State,\n options?: SerializeRouterStateOptions,\n): string {\n const exclude = options?.excludeContext;\n\n let context = state.context;\n\n if (exclude?.length) {\n // Null-proto target so a literal \"__proto__\" namespace (a real own key on\n // state.context, e.g. written by claimContextNamespace) is copied as a\n // genuine own entry by the plain assignment below — a `{}` target would\n // instead dispatch into the inherited Object.prototype.__proto__ setter and\n // silently drop the data (#1191, same hazard the write path guards).\n const filtered: Record<string, unknown> = Object.create(null) as Record<\n string,\n unknown\n >;\n const source = state.context;\n\n for (const [key, value] of Object.entries(source)) {\n if (!exclude.includes(key)) {\n filtered[key] = value;\n }\n }\n\n context = filtered;\n }\n\n const payload = {\n name: state.name,\n params: state.params,\n path: state.path,\n context,\n };\n\n return options?.serialize\n ? serializeState(payload, { serialize: options.serialize })\n : serializeState(payload);\n}\n"],"mappings":"0JAkDA,SAAS,EAAc,EAAqD,CAC1E,MACE,WAAY,GACZ,OAAQ,EAAiC,QAAW,UACnD,EAAiC,SAAW,IAAA,IAC7C,OAAO,EAAQ,OAAO,SAAY,SAEtC,CAqEA,SAAgB,EAGd,EACA,EACA,EAC4B,CAC5B,IAAI,EACA,EACA,EAEJ,GAAI,EAAc,CAAO,EACvB,EAAS,EAAQ,WACZ,CACL,IAAM,EAAa,IAAI,gBACjB,MAAsB,CAC1B,EAAW,MAAM,CACnB,EAEA,EAAS,EAAW,OAOpB,MAAe,CACb,EAAQ,GAAG,QAAS,CAAO,CAC7B,EACA,MAAe,CACb,EAAQ,iBAAiB,QAAS,CAAO,CAC3C,CACF,CAEA,IAAM,EAASA,EAAAA,EAAY,EAAM,CAC/B,GAAG,EACH,YAAa,CACf,CAAiB,EAIjB,IAAS,EAET,IAAI,EAAW,GAET,MACA,EACK,QAAQ,QAAQ,GAGzB,EAAW,GACX,IAAS,EACT,EAAO,QAAQ,EAER,QAAQ,QAAQ,GAGzB,MAAO,CACL,SACA,SACA,WAEC,OAAO,cAAe,CACzB,CACF,CCpLA,SAAS,EAAsB,EAAiB,EAAwB,CACtE,IAAK,IAAM,KAAS,EAAK,SAAS,OAAO,EACnC,EAAM,SAAS,OAAS,EAC1B,EAAO,KAAK,EAAM,QAAQ,EAQ1B,EAAsB,EAAO,CAAM,CAGzC,CAEA,SAAS,EAAkB,EAA2B,CACpD,IAAM,EAAmB,CAAC,EAI1B,OAFA,EAAsB,EAAM,CAAM,EAE3B,CACT,CAEA,eAAsB,EAGpB,EACA,EACmB,CAEnB,IAAM,EAAa,EADNC,EAAAA,EAAa,CAAM,CAAC,CAAC,QACM,CAAC,EACnC,EAAkB,CAAC,EAEzB,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAU,IAAU,GAE1B,GAAI,EAAS,CACX,IAAM,EAAY,MAAM,EAAQ,EAEhC,IAAK,IAAM,KAAU,EACnB,EAAM,KAAK,EAAO,UAAU,EAAW,CAAM,CAAC,CAElD,MACE,EAAM,KAAK,EAAO,UAAU,EAAW,CAAC,CAAC,CAAC,CAE9C,CAEA,OAAO,CACT,CCQA,eAAsB,EACpB,EACA,EACA,EACgB,CAChB,IAAM,EAA2B,GAAS,aAAe,KAAK,MACxD,EACJ,OAAO,GAAW,SACb,EAAY,CAAM,EAClB,EAED,EAAMC,EAAAA,EAAa,CAAM,EACzB,EAAW,EAAI,eAErB,EAAI,eAAiB,EAErB,GAAI,CACF,OAAO,MAAM,EAAO,MAAM,EAAO,IAAI,CACvC,QAAU,CACR,EAAI,eAAiB,CACvB,CACF,CCzCA,SAAgB,EACd,EACA,EACQ,CAQR,QAPkB,GAAS,WAAa,KAAK,UAAA,CAKf,CAAI,GAA4B,OAAA,CAG3D,WAAW,IAAK,OAAO,GAAG,QAAQ,CAAC,CACnC,WAAW,IAAK,OAAO,GAAG,QAAQ,CAAC,CACnC,WAAW,IAAK,OAAO,GAAG,QAAQ,CACvC,CC0BA,SAAgB,EACd,EACA,EACQ,CACR,IAAM,EAAU,GAAS,eAErB,EAAU,EAAM,QAEpB,GAAI,GAAS,OAAQ,CAMnB,IAAM,EAAoC,OAAO,OAAO,IAAI,EAItD,EAAS,EAAM,QAErB,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EACzC,EAAQ,SAAS,CAAG,IACvB,EAAS,GAAO,GAIpB,EAAU,CACZ,CAEA,IAAM,EAAU,CACd,KAAM,EAAM,KACZ,OAAQ,EAAM,OACd,KAAM,EAAM,KACZ,SACF,EAEA,OAAO,GAAS,UACZ,EAAe,EAAS,CAAE,UAAW,EAAQ,SAAU,CAAC,EACxD,EAAe,CAAO,CAC5B"}
@@ -1,2 +0,0 @@
1
- import{T as e,_ as t,n,t as r,x as i}from"./Router-Bk6PfSE8.mjs";import{u as a}from"./buildParamMeta-bOLhLF9h.mjs";function o(t){if(t())throw new n(e.ROUTER_DISPOSED)}function s(t){if(t())throw new n(e.REENTRANT_TREE_MUTATION)}const c=new WeakMap;function l(t){let r=c.get(t);if(r)return r;let i=a(t),s={makeState:(e,t,n,r)=>(i.validator?.state.validateMakeStateArgs(e,t,n),i.makeState(e,t,n,r?.params)),buildState:(e,t)=>{i.validator?.routes.validateStateBuilderArgs(e,t,`buildState`);let{name:n,params:r}=i.forwardState(e,t);return i.buildStateResolved(n,r)},forwardState:(e,t)=>(i.validator?.routes.validateStateBuilderArgs(e,t,`forwardState`),i.forwardState(e,t)),matchPath:e=>(i.validator?.routes.validateMatchPathArgs(e),i.matchPath(e,i.getOptions())),navigateToState:(e,t)=>(o(i.isDisposed),i.validator?.navigation.validateNavigateToStateArgs(e),t!==void 0&&i.validator?.navigation.validateNavigationOptions(t,`navigateToState`),i.navigateToState(e,t)),setRootPath:e=>{o(i.isDisposed),i.validator?.routes.validateSetRootPathArgs(e),i.setRootPath(e)},getRootPath:i.getRootPath,addEventListener:(e,t)=>(o(i.isDisposed),i.validator?.eventBus.validateListenerArgs(e,t),i.addEventListener(e,t)),buildNavigationState:(e,t={})=>{i.validator?.routes.validateStateBuilderArgs(e,t,`buildNavigationState`);let{name:n,params:r}=i.forwardState(e,t),a=i.buildStateResolved(n,r);if(a)return i.makeState(a.name,a.params,i.buildPath(a.name,a.params),a.meta)},getOptions:i.getOptions,getTree:i.getTree,addInterceptor:(e,t)=>{o(i.isDisposed),i.validator?.plugins.validateAddInterceptorArgs(e,t);let n=i.interceptors.get(e);n||(n=[],i.interceptors.set(e,n)),n.push(t);let r=!1;return()=>{r||(r=!0,n.splice(n.indexOf(t),1))}},getRouteConfig:e=>{let t=i.routeGetStore();if(t.matcher.hasRoute(e))return t.routeCustomFields[e]},extendRouter:r=>{o(i.isDisposed);let a=Object.keys(r);for(let r of a)if(r in t)throw new n(e.PLUGIN_CONFLICT,{message:`Cannot extend router: property "${r}" already exists`});for(let e of a)t[e]=r[e];let s={keys:a};i.routerExtensions.push(s);let c=!1;return()=>{if(c)return;c=!0;for(let e of s.keys)delete t[e];let e=i.routerExtensions.indexOf(s);e!==-1&&i.routerExtensions.splice(e,1)}},emitTransitionError:e=>{o(i.isDisposed),i.emitTransitionError(e)},claimContextNamespace:t=>{if(o(i.isDisposed),typeof t!=`string`||t===``)throw TypeError(`[claimContextNamespace] namespace must be a non-empty string, got ${typeof t==`string`?`an empty string`:typeof t}`);if(i.contextClaimRecords.has(t))throw new n(e.CONTEXT_NAMESPACE_ALREADY_CLAIMED,{message:`Cannot claim context namespace: "${t}" is already claimed by another plugin`});return i.contextClaimRecords.add(t),{write(e,n){t===`__proto__`?Object.defineProperty(e.context,t,{value:n,writable:!0,enumerable:!0,configurable:!0}):e.context[t]=n},release(){i.contextClaimRecords.delete(t)}}}};return c.set(t,s),s}function u(e){let t=a(e),n=t.routeGetStore().lifecycleNamespace;return{addActivateGuard(e,r){o(t.isDisposed),t.validator?.routes.validateRouteName(e,`addActivateGuard`),t.validator?.lifecycle.validateHandler(r,`addActivateGuard`),n.addCanActivate(e,r)},addDeactivateGuard(e,r){o(t.isDisposed),t.validator?.routes.validateRouteName(e,`addDeactivateGuard`),t.validator?.lifecycle.validateHandler(r,`addDeactivateGuard`),n.addCanDeactivate(e,r)},removeActivateGuard(e){o(t.isDisposed),t.validator?.routes.validateRouteName(e,`removeActivateGuard`),n.clearCanActivate(e,`external`)},removeDeactivateGuard(e){o(t.isDisposed),t.validator?.routes.validateRouteName(e,`removeDeactivateGuard`),n.clearCanDeactivate(e,`external`)}}}function d(o,s,c){let l=a(o);if(l.isDisposed())throw new n(e.ROUTER_DISPOSED);l.validator?.dependencies.validateCloneArgs(s);let d=l.routeGetStore(),f=i(d.tree),p=d.config,m=d.resolvedForwardMap,h=d.routeCustomFields,{options:g,dependencies:_,pluginFactories:v,loggerConfig:y}=l.getCloneState(),{definition:b,external:x}=d.lifecycleNamespace.getFactoriesByOrigin(),S={..._,...s},C=c?.logger?{...y,...c.logger}:y,w=new r(f,{...g,logger:C},S),T=a(w),E=T.routeGetStore(),D=E.lifecycleNamespace;t(E.config,p),Object.assign(E.resolvedForwardMap,m),Object.assign(E.routeCustomFields,h),d.rootPath!==``&&T.setRootPath(d.rootPath);let[O,k]=b,[A,j]=x;for(let[e,t]of Object.entries(O))D.addCanDeactivate(e,t,!0);for(let[e,t]of Object.entries(k))D.addCanActivate(e,t,!0);let M=u(w);for(let[e,t]of Object.entries(A))M.addDeactivateGuard(e,t);for(let[e,t]of Object.entries(j))M.addActivateGuard(e,t);let N=new Set(T.getCloneState().pluginFactories),P=v.filter(e=>!N.has(e));return P.length>0&&w.usePlugin(...P),w}export{s as a,o as i,u as n,l as r,d as t};
2
- //# sourceMappingURL=cloneRouter-D4rnqmi9.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"cloneRouter-D4rnqmi9.mjs","names":["RouterClass"],"sources":["../../src/api/helpers.ts","../../src/api/getPluginApi.ts","../../src/api/getLifecycleApi.ts","../../src/api/cloneRouter.ts"],"sourcesContent":["// packages/core/src/api/helpers.ts\n\nimport { errorCodes } from \"../constants\";\nimport { RouterError } from \"../RouterError\";\n\nexport function throwIfDisposed(isDisposed: () => boolean): void {\n if (isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n}\n\n/**\n * Bans synchronous reentrant route-CRUD: a CRUD op called while a `TREE_CHANGED`\n * emit is on the stack (i.e. from inside a `subscribeChanges` handler) throws\n * `REENTRANT_TREE_MUTATION` BEFORE mutating — the tree stays atomic (#1032).\n * Deferred CRUD (`queueMicrotask` / `await`) runs after the dispatch settles and\n * is unaffected; CRUD from a transition listener is not a TREE_CHANGED dispatch.\n */\nexport function throwIfReentrantTreeMutation(isEmitting: () => boolean): void {\n if (isEmitting()) {\n throw new RouterError(errorCodes.REENTRANT_TREE_MUTATION);\n }\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { errorCodes } from \"../constants\";\nimport { getInternals } from \"../internals\";\nimport { RouterError } from \"../RouterError\";\n\nimport type { PluginApi } from \"./types\";\nimport type {\n ContextNamespaceClaim,\n DefaultDependencies,\n Params,\n Router,\n State,\n} from \"../types\";\n\n// Cache the assembled PluginApi per router — mirrors getNavigator() (#525):\n// avoids re-allocating the closure-bag on each call (plugins call this once\n// at init, but tests + nested plugins poll it), and gives spy/stub helpers\n// a stable object identity to attach to (e.g. spying on\n// `getPluginApi(router).navigateToState` to inject errors in popstate\n// recovery tests).\nconst cache = new WeakMap<object, PluginApi>();\n\nexport function getPluginApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): PluginApi {\n const cached = cache.get(router);\n\n if (cached) {\n return cached;\n }\n\n const ctx = getInternals(router);\n const api: PluginApi = {\n makeState: (name, params, path, meta) => {\n ctx.validator?.state.validateMakeStateArgs(name, params, path);\n\n return ctx.makeState(\n name,\n params,\n path,\n meta?.params as\n Record<string, Record<string, \"url\" | \"query\">> | undefined,\n );\n },\n buildState: (routeName, routeParams) => {\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"buildState\",\n );\n\n const { name, params } = ctx.forwardState(routeName, routeParams);\n\n return ctx.buildStateResolved(name, params);\n },\n forwardState: <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => {\n ctx.validator?.routes.validateStateBuilderArgs(\n routeName,\n routeParams,\n \"forwardState\",\n );\n\n return ctx.forwardState(routeName, routeParams);\n },\n matchPath: (path) => {\n ctx.validator?.routes.validateMatchPathArgs(path);\n\n return ctx.matchPath(path, ctx.getOptions());\n },\n navigateToState: (state, options) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.navigation.validateNavigateToStateArgs(state);\n\n if (options !== undefined) {\n ctx.validator?.navigation.validateNavigationOptions(\n options,\n \"navigateToState\",\n );\n }\n\n return ctx.navigateToState(state, options);\n },\n setRootPath: (rootPath) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.routes.validateSetRootPathArgs(rootPath);\n\n ctx.setRootPath(rootPath);\n },\n getRootPath: ctx.getRootPath,\n addEventListener: (eventName, cb) => {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.eventBus.validateListenerArgs(eventName, cb);\n\n return ctx.addEventListener(eventName, cb);\n },\n buildNavigationState: (name, params = {}) => {\n ctx.validator?.routes.validateStateBuilderArgs(\n name,\n params,\n \"buildNavigationState\",\n );\n\n const { name: resolvedName, params: resolvedParams } = ctx.forwardState(\n name,\n params,\n );\n const routeInfo = ctx.buildStateResolved(resolvedName, resolvedParams);\n\n if (!routeInfo) {\n return;\n }\n\n return ctx.makeState(\n routeInfo.name,\n routeInfo.params,\n ctx.buildPath(routeInfo.name, routeInfo.params),\n routeInfo.meta,\n );\n },\n getOptions: ctx.getOptions,\n getTree: ctx.getTree,\n addInterceptor: (method, fn) => {\n throwIfDisposed(ctx.isDisposed);\n ctx.validator?.plugins.validateAddInterceptorArgs(method, fn);\n let list = ctx.interceptors.get(method);\n\n if (!list) {\n list = [];\n ctx.interceptors.set(method, list);\n }\n\n list.push(fn);\n\n // Idempotency flag (#1198). Without it, a double call would `indexOf(fn)`\n // again and splice a DUPLICATE registration of the same fn — silently\n // deactivating another plugin's interceptor whose own unsubscribe was never\n // called. The `Unsubscribe` contract is documented idempotent. The flag\n // guarantees exactly one splice of a still-present `fn`, so no `index !== -1`\n // guard is needed (it would be dead — the second call returns above).\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n list.splice(list.indexOf(fn), 1);\n };\n },\n getRouteConfig: (name) => {\n const store = ctx.routeGetStore();\n\n // Stryker disable next-line ConditionalExpression,BlockStatement: equivalent — a missing route yields routeCustomFields[name] === undefined, identical to this early return\n if (!store.matcher.hasRoute(name)) {\n return;\n }\n\n return store.routeCustomFields[name];\n },\n extendRouter: (extensions: Record<string, unknown>) => {\n throwIfDisposed(ctx.isDisposed);\n\n const keys = Object.keys(extensions);\n\n for (const key of keys) {\n if (key in router) {\n throw new RouterError(errorCodes.PLUGIN_CONFLICT, {\n message: `Cannot extend router: property \"${key}\" already exists`,\n });\n }\n }\n\n for (const key of keys) {\n (router as Record<string, unknown>)[key] = extensions[key];\n }\n\n const extensionRecord = { keys };\n\n ctx.routerExtensions.push(extensionRecord);\n\n let removed = false;\n\n return () => {\n if (removed) {\n return;\n }\n\n removed = true;\n\n for (const key of extensionRecord.keys) {\n delete (router as Record<string, unknown>)[key];\n }\n\n const idx = ctx.routerExtensions.indexOf(extensionRecord);\n\n // Stryker disable next-line ConditionalExpression,EqualityOperator,UnaryOperator,BlockStatement: equivalent — this splice only tidies the `routerExtensions` TRACKING array; the router INSTANCE is cleaned by the `delete router[key]` loop above, and dispose()'s safety-net re-deletes any leaked key harmlessly. So no mutation of this guard/splice is behaviourally observable (full suite green with `===`, `+1`, and an empty body). Contrast the addInterceptor splice, which IS observable through buildPath and is killed behaviourally by invariantGuardMutants.test.ts.\n if (idx !== -1) {\n ctx.routerExtensions.splice(idx, 1);\n }\n };\n },\n emitTransitionError: (error) => {\n throwIfDisposed(ctx.isDisposed);\n ctx.emitTransitionError(error);\n },\n claimContextNamespace: (namespace: string) => {\n throwIfDisposed(ctx.isDisposed);\n\n // Input-shape guard, symmetric with the other always-on invariant guards\n // (subscribe / start / navigateToNotFound each typeof-check their input).\n // A non-string namespace coerces to an inconsistent key (\"42\"); an empty\n // string is a meaningless namespace (#1191 N4).\n if (typeof namespace !== \"string\" || namespace === \"\") {\n throw new TypeError(\n `[claimContextNamespace] namespace must be a non-empty string, got ${\n typeof namespace === \"string\" ? \"an empty string\" : typeof namespace\n }`,\n );\n }\n\n if (ctx.contextClaimRecords.has(namespace)) {\n throw new RouterError(errorCodes.CONTEXT_NAMESPACE_ALREADY_CLAIMED, {\n message: `Cannot claim context namespace: \"${namespace}\" is already claimed by another plugin`,\n });\n }\n\n ctx.contextClaimRecords.add(namespace);\n\n return {\n write(state: State, value: unknown) {\n // `state.context[namespace] = value` dispatches into the inherited\n // Object.prototype.__proto__ setter for the literal key \"__proto__\",\n // swapping the prototype instead of creating an own entry — the data\n // then vanishes from Object.keys / serializeRouterState (#1191 N3).\n // Mirror search-params' assignParam: defineProperty writes a genuine\n // own property; normal names keep the plain-assignment fast path.\n if (namespace === \"__proto__\") {\n Object.defineProperty(state.context, namespace, {\n value,\n writable: true,\n enumerable: true,\n configurable: true,\n });\n } else {\n state.context[namespace] = value;\n }\n },\n release() {\n ctx.contextClaimRecords.delete(namespace);\n },\n } satisfies ContextNamespaceClaim;\n },\n };\n\n cache.set(router, api);\n\n return api;\n}\n","import { throwIfDisposed } from \"./helpers\";\nimport { getInternals } from \"../internals\";\n\nimport type { LifecycleApi } from \"./types\";\nimport type { DefaultDependencies, Router } from \"../types\";\n\nexport function getLifecycleApi<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(router: Router<Dependencies>): LifecycleApi<Dependencies> {\n const ctx = getInternals(router);\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const lifecycleNamespace = ctx.routeGetStore().lifecycleNamespace!;\n\n return {\n addActivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.routes.validateRouteName(name, \"addActivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addActivateGuard\");\n\n // Handler-limit enforcement lives at the namespace registration choke point\n // (RouteLifecycleNamespace.#registerHandler) so all paths are bounded\n // uniformly — see #961.\n lifecycleNamespace.addCanActivate(name, handler);\n },\n\n addDeactivateGuard(name, handler) {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.routes.validateRouteName(name, \"addDeactivateGuard\");\n ctx.validator?.lifecycle.validateHandler(handler, \"addDeactivateGuard\");\n\n lifecycleNamespace.addCanDeactivate(name, handler);\n },\n\n removeActivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.routes.validateRouteName(name, \"removeActivateGuard\");\n\n // Inverse of addActivateGuard (external): clears only the external guard;\n // a route-config (definition) canActivate survives (#1171).\n lifecycleNamespace.clearCanActivate(name, \"external\");\n },\n\n removeDeactivateGuard(name) {\n throwIfDisposed(ctx.isDisposed);\n\n ctx.validator?.routes.validateRouteName(name, \"removeDeactivateGuard\");\n\n // Inverse of addDeactivateGuard (external): clears only the external guard;\n // a route-config (definition) canDeactivate survives (#1171).\n lifecycleNamespace.clearCanDeactivate(name, \"external\");\n },\n };\n}\n","import { errorCodes } from \"../constants\";\nimport { routeTreeToDefinitions } from \"../engine\";\nimport { getInternals } from \"../internals\";\nimport { getLifecycleApi } from \"./getLifecycleApi\";\nimport { assignConfigEntries } from \"../namespaces/RoutesNamespace/helpers\";\nimport { Router as RouterClass } from \"../Router\";\nimport { RouterError } from \"../RouterError\";\n\nimport type {\n DefaultDependencies,\n LoggerConfig,\n Router,\n Route,\n} from \"../types\";\n\n/**\n * Per-clone overrides beyond dependencies.\n */\nexport interface CloneOptions {\n /**\n * Per-clone logger config override, merged **over** the base router's resolved\n * logger config. Primary use: per-request `traceId` in SSR — a fresh\n * `callback` closed over the request id, while `level` inherits the base.\n * Omitted keys inherit the base (level / callback / callbackIgnoresLevel).\n *\n * Override is by **config**, not a logger instance: `RouterLogger` is\n * core-internal (only its `{ log, warn, error }` interface is public), so\n * nothing outside core constructs one — configuration is the whole surface.\n */\n logger?: Partial<LoggerConfig>;\n}\n\n/**\n * Build an independent router instance that shares the route tree, options,\n * lifecycle guards, and plugin factories of `router`. The primary use case\n * is **SSR multi-tenancy** — one base router per process, one clone per\n * request.\n *\n * @param router - Source router (must not be disposed).\n * @param dependencies - Optional per-clone overrides merged on top of the\n * base router's dependencies. Always **fresh per call** in the documented\n * SSR pattern: pass per-request state here, never store it in the base.\n *\n * @remarks\n *\n * **Dependency merge — shallow by design.** `base.dependencies` are spread\n * into the clone via `{ ...sourceDeps, ...dependencies }`. Top-level keys\n * are new objects, but **values are shared by reference**: a `Map`, `Set`,\n * class instance, function, or nested plain object stored in\n * `base.dependencies` is the **same instance** in every clone. Mutations\n * in one clone are visible in the base and in every sibling clone.\n *\n * This is intentional. `structuredClone` of dep values is **not** applied\n * because it would:\n * - strip class prototypes (`new DbClient()` → plain object, methods lost)\n * - reject functions and symbols (`DataCloneError`)\n * - fragment singleton pools (one connection pool per request — pool\n * semantics destroyed)\n * - reject circular references\n *\n * **SSR rule of thumb.** Place values in `base.dependencies` according to\n * their lifecycle:\n *\n * - **Singletons / shared services** → `base.dependencies`. Examples: DB\n * client, connection pool, logger, config, feature-flag client. Process-\n * wide pooling depends on sharing these by reference.\n * - **Per-request state** → the `dependencies` override parameter (or\n * `createRequestScope`'s `deps` argument). Examples: `currentUser`,\n * `traceId`, `sessionId`, `abortSignal`. The override is applied last,\n * so it wins over base keys; pass a fresh object per call.\n *\n * Cross-request data leaks are **only possible** when per-request mutable\n * state is incorrectly placed in `base.dependencies`. The override slot is\n * the safe channel.\n *\n * @example\n * ```typescript\n * // Server boot — singletons only\n * const base = createRouter(routes, options, {\n * db: new DbClient(dbUrl),\n * logger,\n * });\n *\n * // Per request — fresh override per call\n * const clone = cloneRouter(base, {\n * currentUser,\n * traceId,\n * });\n * // clone.deps.db === base.deps.db ✓ shared pool (intentional)\n * // clone.deps.currentUser ✓ unique per request\n * ```\n *\n * @see createRequestScope — `@real-router/core/utils` SSR helper that\n * wraps this function and injects `abortSignal` automatically.\n */\nexport function cloneRouter<\n Dependencies extends DefaultDependencies = DefaultDependencies,\n>(\n router: Router<Dependencies>,\n dependencies?: Dependencies,\n opts?: CloneOptions,\n): RouterClass<Dependencies> {\n const ctx = getInternals(router);\n\n if (ctx.isDisposed()) {\n throw new RouterError(errorCodes.ROUTER_DISPOSED);\n }\n\n ctx.validator?.dependencies.validateCloneArgs(dependencies);\n\n // Get source store directly\n const sourceStore = ctx.routeGetStore();\n const routes = routeTreeToDefinitions(sourceStore.tree);\n const routeConfig = sourceStore.config;\n const resolvedForwardMap = sourceStore.resolvedForwardMap;\n const routeCustomFields = sourceStore.routeCustomFields;\n\n const {\n options,\n dependencies: sourceDeps,\n pluginFactories,\n loggerConfig,\n } = ctx.getCloneState();\n // Origin-aware factory snapshot — definition guards are re-registered with\n // `isFromDefinition=true` on the clone so `replace()` can still strip them\n // via `clearDefinitionGuards()`. External guards take the public lifecycle\n // API path so they survive `replace()` symmetric with the base.\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const sourceLifecycleNamespace = sourceStore.lifecycleNamespace!;\n const { definition: definitionFactories, external: externalFactories } =\n sourceLifecycleNamespace.getFactoriesByOrigin();\n\n const mergedDeps = {\n ...sourceDeps,\n ...dependencies,\n } as Dependencies;\n\n // The clone builds its OWN logger (isolation, #724) but INHERITS the base's\n // resolved config — frozen options don't carry `logger`, so without this the\n // clone would fall back to the default logger and lose the base's\n // callback/level (an M1 regression the singleton used to mask). A per-request\n // `opts.logger` override (e.g. a traceId-bound callback) merges on top.\n const clonedLoggerConfig: Partial<LoggerConfig> = opts?.logger\n ? { ...loggerConfig, ...opts.logger }\n : loggerConfig;\n\n const newRouter = new RouterClass<Dependencies>(\n routes as Route<Dependencies>[],\n { ...options, logger: clonedLoggerConfig },\n mergedDeps,\n );\n\n const newCtx = getInternals(newRouter);\n const newStore = newCtx.routeGetStore();\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- guaranteed set after wiring\n const newLifecycleNamespace = newStore.lifecycleNamespace!;\n\n // Copy the source config + store-level maps BEFORE re-registering guards\n // (#1331 review): the definition-guard factories re-executed below must\n // observe the fully-built clone (encoders/decoders/defaultParams/custom\n // fields), mirroring the constructor where flushPendingGuards runs after the\n // store is complete. The five RouteConfig sub-maps go through a single\n // enumeration so a newly added config field is carried over automatically\n // (#965); resolvedForwardMap and routeCustomFields are store-level (not part\n // of RouteConfig) and stay explicit.\n assignConfigEntries(newStore.config, routeConfig);\n Object.assign(newStore.resolvedForwardMap, resolvedForwardMap);\n Object.assign(newStore.routeCustomFields, routeCustomFields);\n\n // #1175: carry the source rootPath. It lives in the store (not options/config),\n // and neither routeTreeToDefinitions nor getCloneState include it — so a clone\n // of a base configured with `setRootPath(\"/app\")` would otherwise build/match\n // under \"\" and 404 every request of a sub-path SSR deployment. setRootPath\n // rebuilds the tree in place with the just-copied config; the rebuild is only\n // paid when a rootPath is actually set, and it runs before the definition-guard\n // factories below so they observe the fully-built clone (rootPath included).\n if (sourceStore.rootPath !== \"\") {\n newCtx.setRootPath(sourceStore.rootPath);\n }\n\n const [definitionDeactivate, definitionActivate] = definitionFactories;\n const [externalDeactivate, externalActivate] = externalFactories;\n\n for (const [name, handler] of Object.entries(definitionDeactivate)) {\n newLifecycleNamespace.addCanDeactivate(name, handler, true);\n }\n\n for (const [name, handler] of Object.entries(definitionActivate)) {\n newLifecycleNamespace.addCanActivate(name, handler, true);\n }\n\n const lifecycle = getLifecycleApi(newRouter);\n\n for (const [name, handler] of Object.entries(externalDeactivate)) {\n lifecycle.addDeactivateGuard(name, handler);\n }\n\n for (const [name, handler] of Object.entries(externalActivate)) {\n lifecycle.addActivateGuard(name, handler);\n }\n\n // Plugin replay runs last and skips factories that a (contract-violating)\n // definition-guard factory already registered on the clone during the\n // re-compilation above — without the filter every clone would double-apply\n // such a plugin: once via the factory, once via this replay (#1331 review).\n const alreadyRegistered = new Set(newCtx.getCloneState().pluginFactories);\n const pluginsToReplay = pluginFactories.filter(\n (factory) => !alreadyRegistered.has(factory),\n );\n\n // Stryker disable next-line EqualityOperator: equivalent — `>= 0` is always true, but `usePlugin(...[])` with an empty spread is a no-op, so entering the block on an empty list behaves identically to skipping it. (ConditionalExpression stays live: `→false` skips a real plugin list and is killable.)\n if (pluginsToReplay.length > 0) {\n newRouter.usePlugin(...pluginsToReplay);\n }\n\n return newRouter;\n}\n"],"mappings":"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"}
@@ -1,111 +0,0 @@
1
- import { DefaultDependencies, Router } from "../types/router.mjs";
2
- import { Router as Router$1 } from "../Router.mjs";
3
- //#region src/utils/createRequestScope.d.ts
4
- /**
5
- * Subset of Node's `http.IncomingMessage` that `createRequestScope` relies on:
6
- * a `"close"` event indicating that the client disconnected (or the response
7
- * was fully sent) and the standard `removeListener` cleanup hook.
8
- */
9
- interface IncomingMessageLike {
10
- on: (event: "close", listener: () => void) => unknown;
11
- removeListener?: (event: "close", listener: () => void) => unknown;
12
- }
13
- /**
14
- * Web `Request`-shaped object — anything carrying an `AbortSignal`. Web
15
- * runtimes (Bun, Cloudflare Workers, Vite RSC) surface client-disconnect via
16
- * `request.signal` directly, so no listener attachment is needed.
17
- */
18
- interface RequestLike {
19
- signal: AbortSignal;
20
- }
21
- type RequestScopeSource = IncomingMessageLike | RequestLike;
22
- interface RequestScope<Dependencies extends DefaultDependencies = DefaultDependencies> extends AsyncDisposable {
23
- /**
24
- * Per-request router clone. Carries `abortSignal` injected into its
25
- * dependencies — loaders can `getDep("abortSignal")` and pass it to fetch /
26
- * `withTimeout` for cooperative cancellation when the client disconnects.
27
- */
28
- readonly router: Router$1<Dependencies>;
29
- /**
30
- * Aborts when the request closes (Node `IncomingMessage`'s `"close"` event)
31
- * or when the upstream Web `Request.signal` aborts.
32
- */
33
- readonly signal: AbortSignal;
34
- /**
35
- * Detach the close listener (if attached to a Node `IncomingMessage`) and
36
- * dispose the cloned router. Idempotent — safe to call multiple times or in
37
- * combination with `Symbol.asyncDispose`.
38
- */
39
- dispose: () => Promise<void>;
40
- }
41
- /**
42
- * Build a per-request router scope: clones `base`, attaches an `AbortSignal`
43
- * tied to the request's lifetime, and exposes `dispose()` (plus
44
- * `Symbol.asyncDispose` for `await using` declarations).
45
- *
46
- * Replaces the four-step boilerplate that every server entry repeats:
47
- *
48
- * 1. `new AbortController()` per request
49
- * 2. `req.on("close", () => controller.abort())`
50
- * 3. `cloneRouter(base, { ...deps, abortSignal: signal })`
51
- * 4. `try { ... } finally { router.dispose() }`
52
- *
53
- * The signal is injected into the router clone under `abortSignal` so existing
54
- * loaders that read `getDep("abortSignal")` keep working without changes. If
55
- * `deps` already carries an `abortSignal`, the request-tied signal **wins** (it
56
- * is spread last) — a caller-supplied `abortSignal` is intentionally overridden,
57
- * since the scope's purpose is to own the request-lifecycle signal.
58
- *
59
- * ## `await using` compatibility
60
- *
61
- * The scope implements `Symbol.asyncDispose`, so `await using scope = …` is
62
- * supported on runtimes that ship the well-known `Symbol.asyncDispose`:
63
- *
64
- * - **Node.js 24+** (full support; partial in 20.4–20.17 only for `fs`/`stream`)
65
- * - **Bun 1.0.23+**, **Deno 1.37+**
66
- * - **Chrome / Edge 127+**, **Firefox 141+**
67
- * - **Safari**: not yet supported (irrelevant in practice — this helper is
68
- * server-side only and never reaches the browser)
69
- *
70
- * On Node.js 22 LTS the well-known symbol is unavailable, so `await using`
71
- * fails. **The bundled SSR examples therefore use the explicit
72
- * `try/finally` + `await scope.dispose()` form**, which works on every
73
- * runtime. Use `await using` only when you control the deployment target and
74
- * know it ships the symbol.
75
- *
76
- * @example
77
- * ```typescript
78
- * // Explicit dispose — works on Node 18+, all browsers, every CI image
79
- * export async function render(url: string, req: IncomingMessage) {
80
- * const scope = createRequestScope(req, baseRouter, { currentUser });
81
- * try {
82
- * scope.router.usePlugin(ssrDataPluginFactory(loaders));
83
- * return await renderShell(scope.router, url);
84
- * } finally {
85
- * await scope.dispose();
86
- * }
87
- * }
88
- *
89
- * // `await using` — Node 24+, Bun, Deno, modern browsers
90
- * export async function render(url: string, req: IncomingMessage) {
91
- * await using scope = createRequestScope(req, baseRouter, { currentUser });
92
- * scope.router.usePlugin(ssrDataPluginFactory(loaders));
93
- * return await renderShell(scope.router, url);
94
- * }
95
- *
96
- * // Web runtime (signal already on the request)
97
- * async function handler(request: Request) {
98
- * const scope = createRequestScope(request, baseRouter, { db });
99
- * try {
100
- * scope.router.usePlugin(rscServerPluginFactory(loaders));
101
- * return await render(scope.router, request.url);
102
- * } finally {
103
- * await scope.dispose();
104
- * }
105
- * }
106
- * ```
107
- */
108
- declare function createRequestScope<Dependencies extends DefaultDependencies = DefaultDependencies>(request: RequestScopeSource, base: Router<Dependencies>, deps?: Partial<Dependencies>): RequestScope<Dependencies>;
109
- //#endregion
110
- export { IncomingMessageLike, RequestLike, RequestScope, RequestScopeSource, createRequestScope };
111
- //# sourceMappingURL=createRequestScope.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"createRequestScope.d.mts","names":[],"sources":["../../../src/utils/createRequestScope.ts"],"mappings":";;;;;;;;UAUiB;EACf,KAAK,gBAAgB;EACrB,kBAAkB,gBAAgB;;;;;;;UAQnB;EACf,QAAQ;;KAGE,qBAAqB,sBAAsB;UAEtC,aACf,qBAAqB,sBAAsB,6BACnC;;;;;;WAMC,QAAQ,SAAY;;;;;WAMpB,QAAQ;;;;;;EAOjB,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+ED,mBACd,qBAAqB,sBAAsB,qBAE3C,SAAS,oBACT,MAAM,OAAO,eACb,OAAO,QAAQ,gBACd,aAAa"}
@@ -1,7 +0,0 @@
1
- import { DefaultDependencies, Router } from "../types/router.mjs";
2
- //#region src/utils/getStaticPaths.d.ts
3
- type StaticPathEntries = Record<string, () => Promise<Record<string, string>[]>>;
4
- declare function getStaticPaths<Dependencies extends DefaultDependencies = DefaultDependencies>(router: Router<Dependencies>, entries?: StaticPathEntries): Promise<string[]>;
5
- //#endregion
6
- export { StaticPathEntries, getStaticPaths };
7
- //# sourceMappingURL=getStaticPaths.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getStaticPaths.d.mts","names":[],"sources":["../../../src/utils/getStaticPaths.ts"],"mappings":";;KAKY,oBAAoB,qBAExB,QAAQ;iBA2BM,eACpB,qBAAqB,sBAAsB,qBAE3C,QAAQ,OAAO,eACf,UAAU,oBACT"}
@@ -1,69 +0,0 @@
1
- import { State } from "../types/base.mjs";
2
- import { Router } from "../types/router.mjs";
3
- //#region src/utils/hydrateRouter.d.ts
4
- /**
5
- * Custom deserializer signature for {@link hydrateRouter} (#606). Compatible
6
- * with `JSON.parse` (default), `devalue.parse`, `superjson.parse`, or any
7
- * user-supplied function.
8
- */
9
- type Deserialize = (json: string) => unknown;
10
- interface HydrateRouterOptions {
11
- /**
12
- * Custom deserializer (e.g., `devalue.parse` / `superjson.parse`) for
13
- * matching the `serialize` passed to {@link serializeRouterState}. Defaults
14
- * to `JSON.parse`. Ignored when `source` is already an object.
15
- *
16
- * @default JSON.parse
17
- *
18
- * @example
19
- * ```typescript
20
- * import * as devalue from "devalue";
21
- * await hydrateRouter(router, ssrJson, { deserialize: devalue.parse });
22
- * ```
23
- */
24
- deserialize?: Deserialize;
25
- }
26
- /**
27
- * Hydrate a fresh router from server-serialized State (#563, #596).
28
- *
29
- * Accepts either a JSON string (parsed via `JSON.parse` by default, or
30
- * `options.deserialize` when provided) or a State-shaped object. Extracts
31
- * `state.path` and delegates to `router.start(state.path)` — the canonical
32
- * URL is the source of truth for the router on hydration.
33
- *
34
- * The full parsed state (incl. `state.context.<namespace>` payloads) is
35
- * deposited into a one-shot scratchpad on `RouterInternals.hydrationState`
36
- * before `start()` is invoked and cleared in the matching `finally`. SSR
37
- * loader plugins (`@real-router/ssr-data-plugin`,
38
- * `@real-router/rsc-server-plugin`) read this scratchpad to skip their loader
39
- * call when the server-resolved namespace value is already present — avoiding
40
- * the post-hydration loader re-run on first paint.
41
- *
42
- * Single-shot semantics: the scratchpad is consumed during the first `start()`
43
- * triggered by `hydrateRouter` regardless of route mismatch; subsequent
44
- * `start()` calls run loaders normally.
45
- *
46
- * @example
47
- * ```typescript
48
- * // Client
49
- * const router = createAppRouter();
50
- * router.usePlugin(browserPluginFactory());
51
- * await hydrateRouter(router, window.__SSR_STATE__);
52
- * ```
53
- *
54
- * @example
55
- * ```typescript
56
- * // With non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)
57
- * import * as devalue from "devalue";
58
- *
59
- * await hydrateRouter(router, window.__SSR_STATE__, {
60
- * deserialize: devalue.parse,
61
- * });
62
- * ```
63
- */
64
- declare function hydrateRouter(router: Router, source: string | {
65
- path: string;
66
- }, options?: HydrateRouterOptions): Promise<State>;
67
- //#endregion
68
- export { Deserialize, HydrateRouterOptions, hydrateRouter };
69
- //# sourceMappingURL=hydrateRouter.d.mts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hydrateRouter.d.mts","names":[],"sources":["../../../src/utils/hydrateRouter.ts"],"mappings":";;;;;;;;KAUY,eAAe;UAEV;;;;;;;;;;;;;;EAcf,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAyCM,cACpB,QAAQ,QACR;EAAmB;GACnB,UAAU,uBACT,QAAQ"}