@real-router/core 0.51.0 → 0.53.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 (87) hide show
  1. package/README.md +22 -10
  2. package/dist/cjs/Router-CJihdrWA.d.ts +67 -0
  3. package/dist/cjs/Router-CJihdrWA.d.ts.map +1 -0
  4. package/dist/cjs/Router-DlmLM8Yo.js +6 -0
  5. package/dist/cjs/Router-DlmLM8Yo.js.map +1 -0
  6. package/dist/cjs/RouterError-Bm9YnZ6e.d.ts +310 -0
  7. package/dist/cjs/RouterError-Bm9YnZ6e.d.ts.map +1 -0
  8. package/dist/cjs/api.d.ts +2 -1
  9. package/dist/cjs/api.d.ts.map +1 -1
  10. package/dist/cjs/api.js +1 -1
  11. package/dist/cjs/api.js.map +1 -1
  12. package/dist/cjs/cloneRouter-jNfrYcI_.js +2 -0
  13. package/dist/cjs/cloneRouter-jNfrYcI_.js.map +1 -0
  14. package/dist/cjs/index-8oPDJBQc.d.ts +306 -0
  15. package/dist/cjs/index-8oPDJBQc.d.ts.map +1 -0
  16. package/dist/cjs/{Router-DrBkBdZ5.d.ts → index-EwbhzRQw.d.ts} +4 -69
  17. package/dist/cjs/index-EwbhzRQw.d.ts.map +1 -0
  18. package/dist/cjs/index.d.ts +4 -197
  19. package/dist/cjs/index.d.ts.map +1 -1
  20. package/dist/cjs/index.js +1 -1
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/{internals-na15rxo_.js → internals-CM6oaz9n.js} +1 -1
  23. package/dist/cjs/internals-CM6oaz9n.js.map +1 -0
  24. package/dist/cjs/utils.d.ts +2 -70
  25. package/dist/cjs/utils.js +1 -1
  26. package/dist/cjs/utils.js.map +1 -1
  27. package/dist/cjs/validation.d.ts +55 -44
  28. package/dist/cjs/validation.d.ts.map +1 -1
  29. package/dist/cjs/validation.js +1 -1
  30. package/dist/esm/Router-BmhiDQUJ.d.mts +67 -0
  31. package/dist/esm/Router-BmhiDQUJ.d.mts.map +1 -0
  32. package/dist/esm/Router-DRM2Fd06.mjs +6 -0
  33. package/dist/esm/Router-DRM2Fd06.mjs.map +1 -0
  34. package/dist/esm/RouterError-hhfSVGtY.d.mts +310 -0
  35. package/dist/esm/RouterError-hhfSVGtY.d.mts.map +1 -0
  36. package/dist/esm/api.d.mts +2 -1
  37. package/dist/esm/api.d.mts.map +1 -1
  38. package/dist/esm/api.mjs +1 -1
  39. package/dist/esm/api.mjs.map +1 -1
  40. package/dist/esm/cloneRouter-DdGGwYdL.mjs +2 -0
  41. package/dist/esm/cloneRouter-DdGGwYdL.mjs.map +1 -0
  42. package/dist/esm/{Router-BeXr2zW4.d.mts → index-DNjaY7KH.d.mts} +4 -69
  43. package/dist/esm/index-DNjaY7KH.d.mts.map +1 -0
  44. package/dist/esm/index-r_JTvSBH.d.mts +306 -0
  45. package/dist/esm/index-r_JTvSBH.d.mts.map +1 -0
  46. package/dist/esm/index.d.mts +4 -197
  47. package/dist/esm/index.d.mts.map +1 -1
  48. package/dist/esm/index.mjs +1 -1
  49. package/dist/esm/index.mjs.map +1 -1
  50. package/dist/esm/{internals-CCymabFj.mjs → internals-C59msvHY.mjs} +1 -1
  51. package/dist/esm/internals-C59msvHY.mjs.map +1 -0
  52. package/dist/esm/utils.d.mts +2 -70
  53. package/dist/esm/utils.mjs +1 -1
  54. package/dist/esm/utils.mjs.map +1 -1
  55. package/dist/esm/validation.d.mts +56 -43
  56. package/dist/esm/validation.d.mts.map +1 -1
  57. package/dist/esm/validation.mjs +1 -1
  58. package/package.json +1 -1
  59. package/src/Router.ts +13 -16
  60. package/src/internals.ts +13 -1
  61. package/src/utils/createRequestScope.ts +174 -0
  62. package/src/utils/hydrateRouter.ts +68 -12
  63. package/src/utils/index.ts +18 -0
  64. package/src/utils/serializeRouterState.ts +94 -5
  65. package/src/utils/serializeState.ts +46 -5
  66. package/dist/cjs/Router-CmI2Uw9u.js +0 -6
  67. package/dist/cjs/Router-CmI2Uw9u.js.map +0 -1
  68. package/dist/cjs/Router-DrBkBdZ5.d.ts.map +0 -1
  69. package/dist/cjs/RouterError-BmvAyBlx.js +0 -2
  70. package/dist/cjs/RouterError-BmvAyBlx.js.map +0 -1
  71. package/dist/cjs/RouterValidator-DLy_W2du.d.ts +0 -114
  72. package/dist/cjs/RouterValidator-DLy_W2du.d.ts.map +0 -1
  73. package/dist/cjs/getPluginApi-CUcFDzuA.js +0 -2
  74. package/dist/cjs/getPluginApi-CUcFDzuA.js.map +0 -1
  75. package/dist/cjs/internals-na15rxo_.js.map +0 -1
  76. package/dist/cjs/utils.d.ts.map +0 -1
  77. package/dist/esm/Router-BeXr2zW4.d.mts.map +0 -1
  78. package/dist/esm/Router-MsNZv4zY.mjs +0 -6
  79. package/dist/esm/Router-MsNZv4zY.mjs.map +0 -1
  80. package/dist/esm/RouterError-D-Zjbdv9.mjs +0 -2
  81. package/dist/esm/RouterError-D-Zjbdv9.mjs.map +0 -1
  82. package/dist/esm/RouterValidator-C-PvV00i.d.mts +0 -114
  83. package/dist/esm/RouterValidator-C-PvV00i.d.mts.map +0 -1
  84. package/dist/esm/getPluginApi-CsTfDB-O.mjs +0 -2
  85. package/dist/esm/getPluginApi-CsTfDB-O.mjs.map +0 -1
  86. package/dist/esm/internals-CCymabFj.mjs.map +0 -1
  87. package/dist/esm/utils.d.mts.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internals-C59msvHY.mjs","names":[],"sources":["../../src/internals.ts"],"sourcesContent":["import type { DependenciesStore } from \"./namespaces\";\nimport type { RoutesStore } from \"./namespaces/RoutesNamespace\";\nimport type { Router as RouterClass } from \"./Router\";\nimport type { EventMethodMap, GuardFnFactory, PluginFactory } from \"./types\";\nimport type { RouterValidator } from \"./types/RouterValidator\";\nimport type { SerializedRouterState } from \"./utils\";\nimport type {\n DefaultDependencies,\n EventName,\n NavigationOptions,\n Options,\n Params,\n Plugin,\n Router as RouterInterface,\n RouteTreeState,\n SimpleState,\n State,\n Unsubscribe,\n} from \"@real-router/types\";\nimport type { RouteTree } from \"route-tree\";\n\nexport interface RouterInternals<\n D extends DefaultDependencies = DefaultDependencies,\n> {\n readonly makeState: <P extends Params = Params>(\n name: string,\n params?: P,\n path?: string,\n meta?: Record<string, Record<string, \"url\" | \"query\">>,\n ) => State<P>;\n\n readonly forwardState: <P extends Params = Params>(\n routeName: string,\n routeParams: P,\n ) => SimpleState<P>;\n\n readonly buildStateResolved: (\n resolvedName: string,\n resolvedParams: Params,\n ) => RouteTreeState | undefined;\n\n readonly matchPath: <P extends Params = Params>(\n path: string,\n options?: Options,\n ) => State<P> | undefined;\n\n readonly getOptions: () => Options;\n\n readonly addEventListener: <E extends EventName>(\n eventName: E,\n cb: Plugin[EventMethodMap[E]],\n ) => Unsubscribe;\n\n readonly buildPath: (route: string, params?: Params) => string;\n\n readonly emitTransitionError: (error: Error) => void;\n\n readonly start: (path: string) => Promise<State>;\n\n /**\n * Plugin-only navigation entry point — delegates to\n * `NavigationNamespace.navigateToState` (`getPluginApi(router).navigateToState`).\n * Hidden from `Router`/`Navigator` to keep the userland surface minimal;\n * see `core-types/src/api.ts` for usage docs.\n */\n readonly navigateToState: (\n state: State,\n options?: NavigationOptions,\n ) => Promise<State>;\n\n /* eslint-disable @typescript-eslint/no-explicit-any -- heterogeneous map: stores different InterceptorFn<M> types under different keys */\n readonly interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >;\n /* eslint-enable @typescript-eslint/no-explicit-any */\n\n readonly setRootPath: (rootPath: string) => void;\n readonly getRootPath: () => string;\n\n readonly getTree: () => RouteTree;\n\n readonly isDisposed: () => boolean;\n\n validator: RouterValidator | null;\n\n // Dependencies (issue #172)\n readonly dependenciesGetStore: () => DependenciesStore<D>;\n\n // Clone support (issue #173)\n readonly cloneOptions: () => Options;\n readonly cloneDependencies: () => Record<string, unknown>;\n readonly getLifecycleFactories: () => [\n Record<string, GuardFnFactory<D>>,\n Record<string, GuardFnFactory<D>>,\n ];\n readonly getPluginFactories: () => PluginFactory<D>[];\n\n // Consolidated route data store (issue #174 Phase 2)\n readonly routeGetStore: () => RoutesStore<D>;\n\n // Cross-namespace state (issue #174)\n readonly getStateName: () => string | undefined;\n readonly isTransitioning: () => boolean;\n readonly clearState: () => void;\n readonly setState: (state: State) => void;\n readonly routerExtensions: { keys: string[] }[];\n readonly contextClaimRecords: Set<string>;\n\n /**\n * One-shot hydration scratchpad populated by `hydrateRouter` immediately\n * before delegating to `router.start(parsed.path)` and cleared in the\n * matching `finally`. SSR loader plugins read this slot directly via\n * `getInternals(router).hydrationState` to short-circuit their own loader\n * call when the server-resolved namespace value is already present in the\n * parsed state (#596). `null` outside of an active `hydrateRouter`\n * invocation.\n */\n hydrationState: SerializedRouterState | null;\n}\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any -- existential type: stores RouterInternals for all Dependencies types\nconst internals = new WeakMap<object, RouterInternals<any>>();\n\nexport function getInternals<D extends DefaultDependencies>(\n router: RouterInterface<D>,\n): RouterInternals<D> {\n const ctx = internals.get(router);\n\n if (!ctx) {\n throw new TypeError(\n \"[real-router] Invalid router instance — not found in internals registry\",\n );\n }\n\n return ctx as RouterInternals<D>;\n}\n\nexport function registerInternals<D extends DefaultDependencies>(\n router: RouterClass<D>,\n ctx: RouterInternals<D>,\n): void {\n internals.set(router, ctx);\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument -- internal chain execution: type safety enforced at public API boundary (PluginApi.addInterceptor) */\nfunction executeInterceptorChain<T>(\n interceptors: ((next: (...args: any[]) => any, ...args: any[]) => any)[],\n original: (...args: any[]) => T,\n args: any[],\n): T {\n let chain = original as (...args: any[]) => any;\n\n for (const interceptor of interceptors) {\n const prev = chain;\n\n chain = (...chainArgs: any[]) => interceptor(prev, ...chainArgs);\n }\n\n return chain(...args) as T;\n}\n\nexport function createInterceptable<T extends (...args: any[]) => any>(\n name: string,\n original: T,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): T {\n return ((...args: any[]) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(...args);\n }\n\n return executeInterceptorChain(chain, original, args);\n }) as T;\n}\n\nexport function createInterceptable2<A, B, R>(\n name: string,\n original: (a: A, b: B) => R,\n interceptors: Map<\n string,\n ((next: (...args: any[]) => any, ...args: any[]) => any)[]\n >,\n): (a: A, b: B) => R {\n return (arg1: A, arg2: B) => {\n const chain = interceptors.get(name);\n\n if (!chain || chain.length === 0) {\n return original(arg1, arg2);\n }\n\n return executeInterceptorChain(chain, original, [arg1, arg2]);\n };\n}\n/* eslint-enable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument */\n"],"mappings":"AA0HA,MAAM,EAAY,IAAI,QAEtB,SAAgB,EACd,EACoB,CACpB,IAAM,EAAM,EAAU,IAAI,EAAO,CAEjC,GAAI,CAAC,EACH,MAAU,UACR,0EACD,CAGH,OAAO,EAGT,SAAgB,EACd,EACA,EACM,CACN,EAAU,IAAI,EAAQ,EAAI,CAI5B,SAAS,EACP,EACA,EACA,EACG,CACH,IAAI,EAAQ,EAEZ,IAAK,IAAM,KAAe,EAAc,CACtC,IAAM,EAAO,EAEb,GAAS,GAAG,IAAqB,EAAY,EAAM,GAAG,EAAU,CAGlE,OAAO,EAAM,GAAG,EAAK,CAGvB,SAAgB,EACd,EACA,EACA,EAIG,CACH,QAAS,GAAG,IAAgB,CAC1B,IAAM,EAAQ,EAAa,IAAI,EAAK,CAMpC,MAJI,CAAC,GAAS,EAAM,SAAW,EACtB,EAAS,GAAG,EAAK,CAGnB,EAAwB,EAAO,EAAU,EAAK,GAIzD,SAAgB,EACd,EACA,EACA,EAImB,CACnB,OAAQ,EAAS,IAAY,CAC3B,IAAM,EAAQ,EAAa,IAAI,EAAK,CAMpC,MAJI,CAAC,GAAS,EAAM,SAAW,EACtB,EAAS,EAAM,EAAK,CAGtB,EAAwB,EAAO,EAAU,CAAC,EAAM,EAAK,CAAC"}
@@ -1,70 +1,2 @@
1
- import { DefaultDependencies, Router, State } from "@real-router/types";
2
-
3
- //#region src/utils/getStaticPaths.d.ts
4
- type StaticPathEntries = Record<string, () => Promise<Record<string, string>[]>>;
5
- declare function getStaticPaths<Dependencies extends DefaultDependencies = DefaultDependencies>(router: Router<Dependencies>, entries?: StaticPathEntries): Promise<string[]>;
6
- //#endregion
7
- //#region src/utils/hydrateRouter.d.ts
8
- /**
9
- * Hydrate a fresh router from server-serialized State (#563).
10
- *
11
- * Accepts either a JSON string (parsed via `JSON.parse`) or a State-shaped
12
- * object. Extracts `state.path` and delegates to `router.start(state.path)` —
13
- * the canonical URL is the source of truth for the router on hydration.
14
- *
15
- * The serialized State (produced by `serializeRouterState`) is still useful
16
- * for application-level concerns: `state.context.<namespace>` payloads (e.g.
17
- * server-side data from `ssr-data-plugin`) can be read separately by app code
18
- * before or after `hydrateRouter` resolves.
19
- *
20
- * @example
21
- * ```typescript
22
- * // Client
23
- * const router = createAppRouter();
24
- * router.usePlugin(browserPluginFactory());
25
- * await hydrateRouter(router, window.__SSR_STATE__);
26
- * ```
27
- */
28
- declare function hydrateRouter(router: Router, source: string | {
29
- path: string;
30
- }): Promise<State>;
31
- //#endregion
32
- //#region src/utils/serializeRouterState.d.ts
33
- /**
34
- * XSS-safe JSON serialization of router State for SSR → client transport (#563).
35
- *
36
- * Strips `state.transition` (per-navigation `TransitionMeta` — meaningless after
37
- * hydration; the client's hydration commit produces its own `transition`).
38
- * Keeps `name`, `params`, `path`, and `context` (plugin context namespaces are
39
- * preserved as-is — server's `state.context.data` from `ssr-data-plugin` and
40
- * any other plugin claims travel to the client untouched).
41
- *
42
- * @example
43
- * ```typescript
44
- * // Server
45
- * const state = await router.start(req.url);
46
- * const html = `<script>window.__SSR_STATE__=${serializeRouterState(state)}</script>`;
47
- *
48
- * // Client
49
- * await hydrateRouter(router, window.__SSR_STATE__);
50
- * ```
51
- */
52
- declare function serializeRouterState(state: State): string;
53
- //#endregion
54
- //#region src/utils/serializeState.d.ts
55
- /**
56
- * XSS-safe JSON serialization for embedding data in HTML `<script>` tags.
57
- *
58
- * Escapes `<`, `>`, and `&` to their Unicode equivalents to prevent
59
- * injection via `</script>` or HTML entities inside inline scripts.
60
- *
61
- * @example
62
- * ```typescript
63
- * const json = serializeState({ name: "home", path: "/" });
64
- * const html = `<script>window.__STATE__=${json}</script>`;
65
- * ```
66
- */
67
- declare function serializeState(data: unknown): string;
68
- //#endregion
69
- export { type StaticPathEntries, getStaticPaths, hydrateRouter, serializeRouterState, serializeState };
70
- //# sourceMappingURL=utils.d.mts.map
1
+ import { a as SerializeStateOptions, c as HydrateRouterOptions, d as getStaticPaths, f as IncomingMessageLike, g as createRequestScope, h as RequestScopeSource, i as Serialize, l as hydrateRouter, m as RequestScope, n as SerializedRouterState, o as serializeState, p as RequestLike, r as serializeRouterState, s as Deserialize, t as SerializeRouterStateOptions, u as StaticPathEntries } from "./index-r_JTvSBH.mjs";
2
+ export { Deserialize, HydrateRouterOptions, IncomingMessageLike, RequestLike, RequestScope, RequestScopeSource, Serialize, SerializeRouterStateOptions, SerializeStateOptions, SerializedRouterState, StaticPathEntries, createRequestScope, getStaticPaths, hydrateRouter, serializeRouterState, serializeState };
@@ -1,2 +1,2 @@
1
- import{t as e}from"./getPluginApi-CsTfDB-O.mjs";function t(e){let n=[];for(let r of e.children.values())r.children.size===0?n.push(r.fullName):n.push(...t(r));return n}async function n(n,r){let i=t(e(n).getTree()),a=[];for(let e of i){let t=r?.[e];if(t){let r=await t();for(let t of r)a.push(n.buildPath(e,t))}else a.push(n.buildPath(e,{}))}return a}function r(e,t){let n=typeof t==`string`?JSON.parse(t):t;return e.start(n.path)}function i(e){return e===void 0?`null`:JSON.stringify(e).replaceAll(`<`,String.raw`\u003c`).replaceAll(`>`,String.raw`\u003e`).replaceAll(`&`,String.raw`\u0026`)}function a(e){return i({name:e.name,params:e.params,path:e.path,context:e.context})}export{n as getStaticPaths,r as hydrateRouter,a as serializeRouterState,i as serializeState};
1
+ import{r as e}from"./internals-C59msvHY.mjs";import{r as t,t as n}from"./cloneRouter-DdGGwYdL.mjs";function r(e){return`signal`in e&&typeof e.signal==`object`&&e.signal!==void 0&&typeof e.signal.aborted==`boolean`}function i(e,t,i){let a,o;if(r(e))o=e.signal;else{let t=new AbortController,n=()=>{t.abort()};e.on(`close`,n),o=t.signal,a=()=>{e.removeListener?.(`close`,n)}}let s=n(t,{...i,abortSignal:o}),c=!1,l=()=>c?Promise.resolve():(c=!0,a?.(),s.dispose(),Promise.resolve());return{router:s,signal:o,dispose:l,[Symbol.asyncDispose]:l}}function a(e){let t=[];for(let n of e.children.values())n.children.size===0?t.push(n.fullName):t.push(...a(n));return t}async function o(e,n){let r=a(t(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(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={},i=e.context;for(let e of Object.keys(i))n.includes(e)||(t[e]=i[e]);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)}export{i as createRequestScope,o as getStaticPaths,s as hydrateRouter,l as serializeRouterState,c as serializeState};
2
2
  //# sourceMappingURL=utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","names":[],"sources":["../../src/utils/getStaticPaths.ts","../../src/utils/hydrateRouter.ts","../../src/utils/serializeState.ts","../../src/utils/serializeRouterState.ts"],"sourcesContent":["import { getPluginApi } from \"../api/getPluginApi\";\n\nimport type { DefaultDependencies, Router } from \"@real-router/types\";\nimport type { RouteTree } from \"route-tree\";\n\nexport type StaticPathEntries = Record<\n string,\n () => Promise<Record<string, string>[]>\n>;\n\nfunction getLeafRouteNames(node: RouteTree): string[] {\n const result: string[] = [];\n\n for (const child of node.children.values()) {\n if (child.children.size === 0) {\n result.push(child.fullName);\n } else {\n result.push(...getLeafRouteNames(child));\n }\n }\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 type { Router, State } from \"@real-router/types\";\n\n/**\n * Hydrate a fresh router from server-serialized State (#563).\n *\n * Accepts either a JSON string (parsed via `JSON.parse`) or a State-shaped\n * object. Extracts `state.path` and delegates to `router.start(state.path)` —\n * the canonical URL is the source of truth for the router on hydration.\n *\n * The serialized State (produced by `serializeRouterState`) is still useful\n * for application-level concerns: `state.context.<namespace>` payloads (e.g.\n * server-side data from `ssr-data-plugin`) can be read separately by app code\n * before or after `hydrateRouter` resolves.\n *\n * @example\n * ```typescript\n * // Client\n * const router = createAppRouter();\n * router.usePlugin(browserPluginFactory());\n * await hydrateRouter(router, window.__SSR_STATE__);\n * ```\n */\nexport function hydrateRouter(\n router: Router,\n source: string | { path: string },\n): Promise<State> {\n const parsed =\n typeof source === \"string\"\n ? (JSON.parse(source) as { path: string })\n : source;\n\n return router.start(parsed.path);\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 * @example\n * ```typescript\n * const json = serializeState({ name: \"home\", path: \"/\" });\n * const html = `<script>window.__STATE__=${json}</script>`;\n * ```\n */\nexport function serializeState(data: unknown): string {\n if (data === undefined) {\n return \"null\";\n }\n\n return JSON.stringify(data)\n .replaceAll(\"<\", String.raw`\\u003c`)\n .replaceAll(\">\", String.raw`\\u003e`)\n .replaceAll(\"&\", String.raw`\\u0026`);\n}\n","import { serializeState } from \"./serializeState\";\n\nimport type { State } from \"@real-router/types\";\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 * @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 */\nexport function serializeRouterState(state: State): string {\n return serializeState({\n name: state.name,\n params: state.params,\n path: state.path,\n context: state.context,\n });\n}\n"],"mappings":"gDAUA,SAAS,EAAkB,EAA2B,CACpD,IAAM,EAAmB,EAAE,CAE3B,IAAK,IAAM,KAAS,EAAK,SAAS,QAAQ,CACpC,EAAM,SAAS,OAAS,EAC1B,EAAO,KAAK,EAAM,SAAS,CAE3B,EAAO,KAAK,GAAG,EAAkB,EAAM,CAAC,CAI5C,OAAO,EAGT,eAAsB,EAGpB,EACA,EACmB,CAEnB,IAAM,EAAa,EADN,EAAa,EAAO,CAAC,SAAS,CACD,CACpC,EAAkB,EAAE,CAE1B,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAU,IAAU,GAE1B,GAAI,EAAS,CACX,IAAM,EAAY,MAAM,GAAS,CAEjC,IAAK,IAAM,KAAU,EACnB,EAAM,KAAK,EAAO,UAAU,EAAW,EAAO,CAAC,MAGjD,EAAM,KAAK,EAAO,UAAU,EAAW,EAAE,CAAC,CAAC,CAI/C,OAAO,EC1BT,SAAgB,EACd,EACA,EACgB,CAChB,IAAM,EACJ,OAAO,GAAW,SACb,KAAK,MAAM,EAAO,CACnB,EAEN,OAAO,EAAO,MAAM,EAAO,KAAK,CCnBlC,SAAgB,EAAe,EAAuB,CAKpD,OAJI,IAAS,IAAA,GACJ,OAGF,KAAK,UAAU,EAAK,CACxB,WAAW,IAAK,OAAO,GAAG,SAAS,CACnC,WAAW,IAAK,OAAO,GAAG,SAAS,CACnC,WAAW,IAAK,OAAO,GAAG,SAAS,CCGxC,SAAgB,EAAqB,EAAsB,CACzD,OAAO,EAAe,CACpB,KAAM,EAAM,KACZ,OAAQ,EAAM,OACd,KAAM,EAAM,KACZ,QAAS,EAAM,QAChB,CAAC"}
1
+ {"version":3,"file":"utils.mjs","names":[],"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 \"@real-router/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.\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 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 request.on(\"close\", onClose);\n signal = controller.signal;\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 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 [Symbol.asyncDispose]: dispose,\n };\n}\n","import { getPluginApi } from \"../api/getPluginApi\";\n\nimport type { DefaultDependencies, Router } from \"@real-router/types\";\nimport type { RouteTree } from \"route-tree\";\n\nexport type StaticPathEntries = Record<\n string,\n () => Promise<Record<string, string>[]>\n>;\n\nfunction getLeafRouteNames(node: RouteTree): string[] {\n const result: string[] = [];\n\n for (const child of node.children.values()) {\n if (child.children.size === 0) {\n result.push(child.fullName);\n } else {\n result.push(...getLeafRouteNames(child));\n }\n }\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 \"@real-router/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 \"@real-router/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 const filtered: Record<string, unknown> = {};\n const source = state.context as Record<string, unknown>;\n\n for (const key of Object.keys(source)) {\n if (!exclude.includes(key)) {\n filtered[key] = source[key];\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":"mGAkDA,SAAS,EAAc,EAAqD,CAC1E,MACE,WAAY,GACZ,OAAQ,EAAiC,QAAW,UACnD,EAAiC,SAAW,IAAA,IAC7C,OAAO,EAAQ,OAAO,SAAY,UAoEtC,SAAgB,EAGd,EACA,EACA,EAC4B,CAC5B,IAAI,EACA,EAEJ,GAAI,EAAc,EAAQ,CACxB,EAAS,EAAQ,WACZ,CACL,IAAM,EAAa,IAAI,gBACjB,MAAsB,CAC1B,EAAW,OAAO,EAGpB,EAAQ,GAAG,QAAS,EAAQ,CAC5B,EAAS,EAAW,OACpB,MAAe,CACb,EAAQ,iBAAiB,QAAS,EAAQ,EAI9C,IAAM,EAAS,EAAY,EAAM,CAC/B,GAAG,EACH,YAAa,EACd,CAAiB,CAEd,EAAW,GAET,MACA,EACK,QAAQ,SAAS,EAG1B,EAAW,GACX,KAAU,CACV,EAAO,SAAS,CAET,QAAQ,SAAS,EAG1B,MAAO,CACL,SACA,SACA,WACC,OAAO,cAAe,EACxB,CClKH,SAAS,EAAkB,EAA2B,CACpD,IAAM,EAAmB,EAAE,CAE3B,IAAK,IAAM,KAAS,EAAK,SAAS,QAAQ,CACpC,EAAM,SAAS,OAAS,EAC1B,EAAO,KAAK,EAAM,SAAS,CAE3B,EAAO,KAAK,GAAG,EAAkB,EAAM,CAAC,CAI5C,OAAO,EAGT,eAAsB,EAGpB,EACA,EACmB,CAEnB,IAAM,EAAa,EADN,EAAa,EAAO,CAAC,SACO,CAAC,CACpC,EAAkB,EAAE,CAE1B,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAU,IAAU,GAE1B,GAAI,EAAS,CACX,IAAM,EAAY,MAAM,GAAS,CAEjC,IAAK,IAAM,KAAU,EACnB,EAAM,KAAK,EAAO,UAAU,EAAW,EAAO,CAAC,MAGjD,EAAM,KAAK,EAAO,UAAU,EAAW,EAAE,CAAC,CAAC,CAI/C,OAAO,ECmBT,eAAsB,EACpB,EACA,EACA,EACgB,CAChB,IAAM,EAA2B,GAAS,aAAe,KAAK,MACxD,EACJ,OAAO,GAAW,SACb,EAAY,EAAO,CACnB,EAED,EAAM,EAAa,EAAO,CAC1B,EAAW,EAAI,eAErB,EAAI,eAAiB,EAErB,GAAI,CACF,OAAO,MAAM,EAAO,MAAM,EAAO,KAAK,QAC9B,CACR,EAAI,eAAiB,GCvCzB,SAAgB,EACd,EACA,EACQ,CAQR,QAPkB,GAAS,WAAa,KAAK,WAKf,EAAK,EAA2B,QAG3D,WAAW,IAAK,OAAO,GAAG,SAAS,CACnC,WAAW,IAAK,OAAO,GAAG,SAAS,CACnC,WAAW,IAAK,OAAO,GAAG,SAAS,CC2BxC,SAAgB,EACd,EACA,EACQ,CACR,IAAM,EAAU,GAAS,eAErB,EAAU,EAAM,QAEpB,GAAI,GAAS,OAAQ,CACnB,IAAM,EAAoC,EAAE,CACtC,EAAS,EAAM,QAErB,IAAK,IAAM,KAAO,OAAO,KAAK,EAAO,CAC9B,EAAQ,SAAS,EAAI,GACxB,EAAS,GAAO,EAAO,IAI3B,EAAU,EAGZ,IAAM,EAAU,CACd,KAAM,EAAM,KACZ,OAAQ,EAAM,OACd,KAAM,EAAM,KACZ,UACD,CAED,OAAO,GAAS,UACZ,EAAe,EAAS,CAAE,UAAW,EAAQ,UAAW,CAAC,CACzD,EAAe,EAAQ"}
@@ -1,6 +1,9 @@
1
- import { a as Limits, d as RouteDefinition, f as RouteTree, i as GuardFnFactory, l as CreateMatcherOptions, o as PluginFactory, r as EventMethodMap, u as Matcher } from "./Router-BeXr2zW4.mjs";
2
- import { t as RouterValidator } from "./RouterValidator-C-PvV00i.mjs";
3
- import { DefaultDependencies, EventName, ForwardToCallback, GuardFn, NavigationOptions, Options, Params, Plugin, RouteTreeState, Router, SimpleState, State, Unsubscribe } from "@real-router/types";
1
+ import { i as RouteTree, n as Matcher, r as RouteDefinition, t as CreateMatcherOptions } from "./index-DNjaY7KH.mjs";
2
+ import { a as Limits, i as GuardFnFactory, o as PluginFactory, r as EventMethodMap } from "./Router-BmhiDQUJ.mjs";
3
+ import { n as RouterValidator } from "./RouterError-hhfSVGtY.mjs";
4
+ import { n as SerializedRouterState } from "./index-r_JTvSBH.mjs";
5
+ import { FSM } from "@real-router/fsm";
6
+ import { DefaultDependencies, EventName, ForwardToCallback, GuardFn, LeaveFn, NavigationOptions, Options, Params, Plugin, RouteTreeState, Router, SimpleState, State, SubscribeFn, Unsubscribe } from "@real-router/types";
4
7
 
5
8
  //#region src/namespaces/DependenciesNamespace/dependenciesStore.d.ts
6
9
  interface DependenciesStore<Dependencies extends DefaultDependencies = DefaultDependencies> {
@@ -8,46 +11,6 @@ interface DependenciesStore<Dependencies extends DefaultDependencies = DefaultDe
8
11
  limits: Limits;
9
12
  }
10
13
  //#endregion
11
- //#region src/namespaces/RoutesNamespace/types.d.ts
12
- /**
13
- * Dependencies injected into RoutesNamespace.
14
- *
15
- * These are function references from the Router facade,
16
- * avoiding the need to pass the entire Router object.
17
- */
18
- interface RoutesDependencies<Dependencies extends DefaultDependencies = DefaultDependencies> {
19
- /** Register canActivate handler for a route */
20
- addActivateGuard: (name: string, handler: GuardFnFactory<Dependencies>) => void;
21
- /** Register canDeactivate handler for a route */
22
- addDeactivateGuard: (name: string, handler: GuardFnFactory<Dependencies>) => void;
23
- /** Create state object */
24
- makeState: <P extends Params = Params>(name: string, params?: P, path?: string, meta?: Record<string, Record<string, "url" | "query">>) => State<P>;
25
- /** Get current router state */
26
- getState: () => State | undefined;
27
- /** Compare two states for equality */
28
- areStatesEqual: (state1: State | undefined, state2: State | undefined, ignoreQueryParams?: boolean) => boolean;
29
- /** Get a dependency by name */
30
- getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K];
31
- /** Forward state through facade (allows plugin interception) */
32
- forwardState: <P extends Params = Params>(name: string, params: P) => SimpleState<P>;
33
- }
34
- /**
35
- * Configuration storage for routes.
36
- * Stores decoders, encoders, default params, and forward mappings.
37
- */
38
- interface RouteConfig {
39
- /** Custom param decoders per route */
40
- decoders: Record<string, (params: Params) => Params>;
41
- /** Custom param encoders per route */
42
- encoders: Record<string, (params: Params) => Params>;
43
- /** Default params per route */
44
- defaultParams: Record<string, Params>;
45
- /** Forward mappings (source -> target) */
46
- forwardMap: Record<string, string>;
47
- /** Dynamic forward callbacks (source -> callback) */
48
- forwardFnMap: Record<string, ForwardToCallback<any>>;
49
- }
50
- //#endregion
51
14
  //#region src/namespaces/RouteLifecycleNamespace/types.d.ts
52
15
  interface RouteLifecycleDependencies<Dependencies extends DefaultDependencies = DefaultDependencies> {
53
16
  compileFactory: (factory: GuardFnFactory<Dependencies>) => GuardFn;
@@ -128,6 +91,46 @@ declare class RouteLifecycleNamespace<Dependencies extends DefaultDependencies =
128
91
  canNavigateTo(toDeactivate: string[], toActivate: string[], toState: State, fromState: State | undefined): boolean;
129
92
  }
130
93
  //#endregion
94
+ //#region src/namespaces/RoutesNamespace/types.d.ts
95
+ /**
96
+ * Dependencies injected into RoutesNamespace.
97
+ *
98
+ * These are function references from the Router facade,
99
+ * avoiding the need to pass the entire Router object.
100
+ */
101
+ interface RoutesDependencies<Dependencies extends DefaultDependencies = DefaultDependencies> {
102
+ /** Register canActivate handler for a route */
103
+ addActivateGuard: (name: string, handler: GuardFnFactory<Dependencies>) => void;
104
+ /** Register canDeactivate handler for a route */
105
+ addDeactivateGuard: (name: string, handler: GuardFnFactory<Dependencies>) => void;
106
+ /** Create state object */
107
+ makeState: <P extends Params = Params>(name: string, params?: P, path?: string, meta?: Record<string, Record<string, "url" | "query">>) => State<P>;
108
+ /** Get current router state */
109
+ getState: () => State | undefined;
110
+ /** Compare two states for equality */
111
+ areStatesEqual: (state1: State | undefined, state2: State | undefined, ignoreQueryParams?: boolean) => boolean;
112
+ /** Get a dependency by name */
113
+ getDependency: <K extends keyof Dependencies>(name: K) => Dependencies[K];
114
+ /** Forward state through facade (allows plugin interception) */
115
+ forwardState: <P extends Params = Params>(name: string, params: P) => SimpleState<P>;
116
+ }
117
+ /**
118
+ * Configuration storage for routes.
119
+ * Stores decoders, encoders, default params, and forward mappings.
120
+ */
121
+ interface RouteConfig {
122
+ /** Custom param decoders per route */
123
+ decoders: Record<string, (params: Params) => Params>;
124
+ /** Custom param encoders per route */
125
+ encoders: Record<string, (params: Params) => Params>;
126
+ /** Default params per route */
127
+ defaultParams: Record<string, Params>;
128
+ /** Forward mappings (source -> target) */
129
+ forwardMap: Record<string, string>;
130
+ /** Dynamic forward callbacks (source -> callback) */
131
+ forwardFnMap: Record<string, ForwardToCallback<any>>;
132
+ }
133
+ //#endregion
131
134
  //#region src/namespaces/RoutesNamespace/routesStore.d.ts
132
135
  interface RoutesStore<Dependencies extends DefaultDependencies = DefaultDependencies> {
133
136
  readonly definitions: RouteDefinition[];
@@ -187,6 +190,16 @@ interface RouterInternals<D extends DefaultDependencies = DefaultDependencies> {
187
190
  keys: string[];
188
191
  }[];
189
192
  readonly contextClaimRecords: Set<string>;
193
+ /**
194
+ * One-shot hydration scratchpad populated by `hydrateRouter` immediately
195
+ * before delegating to `router.start(parsed.path)` and cleared in the
196
+ * matching `finally`. SSR loader plugins read this slot directly via
197
+ * `getInternals(router).hydrationState` to short-circuit their own loader
198
+ * call when the server-resolved namespace value is already present in the
199
+ * parsed state (#596). `null` outside of an active `hydrateRouter`
200
+ * invocation.
201
+ */
202
+ hydrationState: SerializedRouterState | null;
190
203
  }
191
204
  declare function getInternals<D extends DefaultDependencies>(router: Router<D>): RouterInternals<D>;
192
205
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"validation.d.mts","names":[],"sources":["../../src/namespaces/DependenciesNamespace/dependenciesStore.ts","../../src/namespaces/RoutesNamespace/types.ts","../../src/namespaces/RouteLifecycleNamespace/types.ts","../../src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts","../../src/namespaces/RoutesNamespace/routesStore.ts","../../src/internals.ts"],"mappings":"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"}
1
+ {"version":3,"file":"validation.d.mts","names":[],"sources":["../../src/namespaces/DependenciesNamespace/dependenciesStore.ts","../../src/namespaces/RouteLifecycleNamespace/types.ts","../../src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts","../../src/namespaces/RoutesNamespace/types.ts","../../src/namespaces/RoutesNamespace/routesStore.ts","../../src/internals.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- import{r as e}from"./internals-CCymabFj.mjs";export{e as getInternals};
1
+ import{r as e}from"./internals-C59msvHY.mjs";export{e as getInternals};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/core",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "type": "commonjs",
5
5
  "description": "A simple, powerful, view-agnostic, modular and extensible router",
6
6
  "main": "./dist/cjs/index.js",
package/src/Router.ts CHANGED
@@ -214,7 +214,7 @@ export class Router<
214
214
  this.#options.get(),
215
215
  ),
216
216
  interceptorsMap,
217
- ) as unknown as RouterInternals["buildPath"],
217
+ ),
218
218
  emitTransitionError: (error) => {
219
219
  this.#eventBus.sendFailSafe(undefined, this.#state.get(), error);
220
220
  },
@@ -257,11 +257,7 @@ export class Router<
257
257
  dependenciesGetStore: () => this.#dependenciesStore,
258
258
  // Clone support (issue #173)
259
259
  cloneOptions: () => ({ ...this.#options.get() }),
260
- cloneDependencies: () =>
261
- ({ ...this.#dependenciesStore.dependencies }) as Record<
262
- string,
263
- unknown
264
- >,
260
+ cloneDependencies: () => ({ ...this.#dependenciesStore.dependencies }),
265
261
  getLifecycleFactories: () => this.#routeLifecycle.getFactories(),
266
262
  getPluginFactories: () => this.#plugins.getAll(),
267
263
  routeGetStore: () => this.#routes.getStore(),
@@ -276,6 +272,7 @@ export class Router<
276
272
  },
277
273
  routerExtensions: [],
278
274
  contextClaimRecords: new Set(),
275
+ hydrationState: null,
279
276
  });
280
277
 
281
278
  // =========================================================================
@@ -677,16 +674,16 @@ export class Router<
677
674
  }
678
675
 
679
676
  #markDisposed(): void {
680
- this.navigate = throwDisposed as never;
681
- this.navigateToDefault = throwDisposed as never;
682
- this.navigateToNotFound = throwDisposed as never;
683
- this.start = throwDisposed as never;
684
- this.stop = throwDisposed as never;
685
- this.usePlugin = throwDisposed as never;
686
-
687
- this.subscribe = throwDisposed as never;
688
- this.subscribeLeave = throwDisposed as never;
689
- this.canNavigateTo = throwDisposed as never;
677
+ this.navigate = throwDisposed;
678
+ this.navigateToDefault = throwDisposed;
679
+ this.navigateToNotFound = throwDisposed;
680
+ this.start = throwDisposed;
681
+ this.stop = throwDisposed;
682
+ this.usePlugin = throwDisposed;
683
+
684
+ this.subscribe = throwDisposed;
685
+ this.subscribeLeave = throwDisposed;
686
+ this.canNavigateTo = throwDisposed;
690
687
  }
691
688
  }
692
689
 
package/src/internals.ts CHANGED
@@ -1,8 +1,9 @@
1
- import type { DependenciesStore } from "./namespaces/DependenciesNamespace";
1
+ import type { DependenciesStore } from "./namespaces";
2
2
  import type { RoutesStore } from "./namespaces/RoutesNamespace";
3
3
  import type { Router as RouterClass } from "./Router";
4
4
  import type { EventMethodMap, GuardFnFactory, PluginFactory } from "./types";
5
5
  import type { RouterValidator } from "./types/RouterValidator";
6
+ import type { SerializedRouterState } from "./utils";
6
7
  import type {
7
8
  DefaultDependencies,
8
9
  EventName,
@@ -105,6 +106,17 @@ export interface RouterInternals<
105
106
  readonly setState: (state: State) => void;
106
107
  readonly routerExtensions: { keys: string[] }[];
107
108
  readonly contextClaimRecords: Set<string>;
109
+
110
+ /**
111
+ * One-shot hydration scratchpad populated by `hydrateRouter` immediately
112
+ * before delegating to `router.start(parsed.path)` and cleared in the
113
+ * matching `finally`. SSR loader plugins read this slot directly via
114
+ * `getInternals(router).hydrationState` to short-circuit their own loader
115
+ * call when the server-resolved namespace value is already present in the
116
+ * parsed state (#596). `null` outside of an active `hydrateRouter`
117
+ * invocation.
118
+ */
119
+ hydrationState: SerializedRouterState | null;
108
120
  }
109
121
 
110
122
  // eslint-disable-next-line @typescript-eslint/no-explicit-any -- existential type: stores RouterInternals for all Dependencies types
@@ -0,0 +1,174 @@
1
+ import { cloneRouter } from "../api/cloneRouter";
2
+
3
+ import type { Router as RouterClass } from "../Router";
4
+ import type { DefaultDependencies, Router } from "@real-router/types";
5
+
6
+ /**
7
+ * Subset of Node's `http.IncomingMessage` that `createRequestScope` relies on:
8
+ * a `"close"` event indicating that the client disconnected (or the response
9
+ * was fully sent) and the standard `removeListener` cleanup hook.
10
+ */
11
+ export interface IncomingMessageLike {
12
+ on: (event: "close", listener: () => void) => unknown;
13
+ removeListener?: (event: "close", listener: () => void) => unknown;
14
+ }
15
+
16
+ /**
17
+ * Web `Request`-shaped object — anything carrying an `AbortSignal`. Web
18
+ * runtimes (Bun, Cloudflare Workers, Vite RSC) surface client-disconnect via
19
+ * `request.signal` directly, so no listener attachment is needed.
20
+ */
21
+ export interface RequestLike {
22
+ signal: AbortSignal;
23
+ }
24
+
25
+ export type RequestScopeSource = IncomingMessageLike | RequestLike;
26
+
27
+ export interface RequestScope<
28
+ Dependencies extends DefaultDependencies = DefaultDependencies,
29
+ > extends AsyncDisposable {
30
+ /**
31
+ * Per-request router clone. Carries `abortSignal` injected into its
32
+ * dependencies — loaders can `getDep("abortSignal")` and pass it to fetch /
33
+ * `withTimeout` for cooperative cancellation when the client disconnects.
34
+ */
35
+ readonly router: RouterClass<Dependencies>;
36
+
37
+ /**
38
+ * Aborts when the request closes (Node `IncomingMessage`'s `"close"` event)
39
+ * or when the upstream Web `Request.signal` aborts.
40
+ */
41
+ readonly signal: AbortSignal;
42
+
43
+ /**
44
+ * Detach the close listener (if attached to a Node `IncomingMessage`) and
45
+ * dispose the cloned router. Idempotent — safe to call multiple times or in
46
+ * combination with `Symbol.asyncDispose`.
47
+ */
48
+ dispose: () => Promise<void>;
49
+ }
50
+
51
+ function isRequestLike(request: RequestScopeSource): request is RequestLike {
52
+ return (
53
+ "signal" in request &&
54
+ typeof (request as Partial<RequestLike>).signal === "object" &&
55
+ (request as Partial<RequestLike>).signal !== undefined &&
56
+ typeof request.signal.aborted === "boolean"
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Build a per-request router scope: clones `base`, attaches an `AbortSignal`
62
+ * tied to the request's lifetime, and exposes `dispose()` (plus
63
+ * `Symbol.asyncDispose` for `await using` declarations).
64
+ *
65
+ * Replaces the four-step boilerplate that every server entry repeats:
66
+ *
67
+ * 1. `new AbortController()` per request
68
+ * 2. `req.on("close", () => controller.abort())`
69
+ * 3. `cloneRouter(base, { ...deps, abortSignal: signal })`
70
+ * 4. `try { ... } finally { router.dispose() }`
71
+ *
72
+ * The signal is injected into the router clone under `abortSignal` so existing
73
+ * loaders that read `getDep("abortSignal")` keep working without changes.
74
+ *
75
+ * ## `await using` compatibility
76
+ *
77
+ * The scope implements `Symbol.asyncDispose`, so `await using scope = …` is
78
+ * supported on runtimes that ship the well-known `Symbol.asyncDispose`:
79
+ *
80
+ * - **Node.js 24+** (full support; partial in 20.4–20.17 only for `fs`/`stream`)
81
+ * - **Bun 1.0.23+**, **Deno 1.37+**
82
+ * - **Chrome / Edge 127+**, **Firefox 141+**
83
+ * - **Safari**: not yet supported (irrelevant in practice — this helper is
84
+ * server-side only and never reaches the browser)
85
+ *
86
+ * On Node.js 22 LTS the well-known symbol is unavailable, so `await using`
87
+ * fails. **The bundled SSR examples therefore use the explicit
88
+ * `try/finally` + `await scope.dispose()` form**, which works on every
89
+ * runtime. Use `await using` only when you control the deployment target and
90
+ * know it ships the symbol.
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * // Explicit dispose — works on Node 18+, all browsers, every CI image
95
+ * export async function render(url: string, req: IncomingMessage) {
96
+ * const scope = createRequestScope(req, baseRouter, { currentUser });
97
+ * try {
98
+ * scope.router.usePlugin(ssrDataPluginFactory(loaders));
99
+ * return await renderShell(scope.router, url);
100
+ * } finally {
101
+ * await scope.dispose();
102
+ * }
103
+ * }
104
+ *
105
+ * // `await using` — Node 24+, Bun, Deno, modern browsers
106
+ * export async function render(url: string, req: IncomingMessage) {
107
+ * await using scope = createRequestScope(req, baseRouter, { currentUser });
108
+ * scope.router.usePlugin(ssrDataPluginFactory(loaders));
109
+ * return await renderShell(scope.router, url);
110
+ * }
111
+ *
112
+ * // Web runtime (signal already on the request)
113
+ * async function handler(request: Request) {
114
+ * const scope = createRequestScope(request, baseRouter, { db });
115
+ * try {
116
+ * scope.router.usePlugin(rscServerPluginFactory(loaders));
117
+ * return await render(scope.router, request.url);
118
+ * } finally {
119
+ * await scope.dispose();
120
+ * }
121
+ * }
122
+ * ```
123
+ */
124
+ export function createRequestScope<
125
+ Dependencies extends DefaultDependencies = DefaultDependencies,
126
+ >(
127
+ request: RequestScopeSource,
128
+ base: Router<Dependencies>,
129
+ deps?: Partial<Dependencies>,
130
+ ): RequestScope<Dependencies> {
131
+ let detach: (() => void) | undefined;
132
+ let signal: AbortSignal;
133
+
134
+ if (isRequestLike(request)) {
135
+ signal = request.signal;
136
+ } else {
137
+ const controller = new AbortController();
138
+ const onClose = (): void => {
139
+ controller.abort();
140
+ };
141
+
142
+ request.on("close", onClose);
143
+ signal = controller.signal;
144
+ detach = () => {
145
+ request.removeListener?.("close", onClose);
146
+ };
147
+ }
148
+
149
+ const router = cloneRouter(base, {
150
+ ...deps,
151
+ abortSignal: signal,
152
+ } as Dependencies);
153
+
154
+ let disposed = false;
155
+
156
+ const dispose = (): Promise<void> => {
157
+ if (disposed) {
158
+ return Promise.resolve();
159
+ }
160
+
161
+ disposed = true;
162
+ detach?.();
163
+ router.dispose();
164
+
165
+ return Promise.resolve();
166
+ };
167
+
168
+ return {
169
+ router,
170
+ signal,
171
+ dispose,
172
+ [Symbol.asyncDispose]: dispose,
173
+ };
174
+ }