@real-router/core 0.54.6 → 0.56.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 (78) hide show
  1. package/dist/cjs/Router-IEGavTKk.js +6 -0
  2. package/dist/cjs/Router-IEGavTKk.js.map +1 -0
  3. package/dist/cjs/{Router-CJihdrWA.d.ts → Router-hW6ivqrX.d.ts} +2 -2
  4. package/dist/cjs/Router-hW6ivqrX.d.ts.map +1 -0
  5. package/dist/cjs/api.d.ts +2 -2
  6. package/dist/cjs/api.d.ts.map +1 -1
  7. package/dist/cjs/api.js +1 -1
  8. package/dist/cjs/api.js.map +1 -1
  9. package/dist/cjs/{cloneRouter-zhA3NNoI.js → cloneRouter-DRieJvam.js} +2 -2
  10. package/dist/cjs/{cloneRouter-zhA3NNoI.js.map → cloneRouter-DRieJvam.js.map} +1 -1
  11. package/dist/cjs/{index-EwbhzRQw.d.ts → index-C-i6vx5Y.d.ts} +1 -1
  12. package/dist/cjs/index-C-i6vx5Y.d.ts.map +1 -0
  13. package/dist/cjs/{RouterError-Bm9YnZ6e.d.ts → index-CYpAZCoc.d.ts} +19 -2
  14. package/dist/cjs/index-CYpAZCoc.d.ts.map +1 -0
  15. package/dist/cjs/{index-8oPDJBQc.d.ts → index-D2WRiyWS.d.ts} +2 -2
  16. package/dist/cjs/index-D2WRiyWS.d.ts.map +1 -0
  17. package/dist/cjs/index.d.ts +5 -5
  18. package/dist/cjs/index.js +1 -1
  19. package/dist/cjs/{internals-CM6oaz9n.js → internals-DJjgSePy.js} +2 -2
  20. package/dist/cjs/internals-DJjgSePy.js.map +1 -0
  21. package/dist/cjs/utils.d.ts +1 -1
  22. package/dist/cjs/utils.js +1 -1
  23. package/dist/cjs/utils.js.map +1 -1
  24. package/dist/cjs/validation.d.ts +17 -5
  25. package/dist/cjs/validation.d.ts.map +1 -1
  26. package/dist/cjs/validation.js +1 -1
  27. package/dist/esm/Router-B3aeavRb.mjs +6 -0
  28. package/dist/esm/Router-B3aeavRb.mjs.map +1 -0
  29. package/dist/esm/{Router-BmhiDQUJ.d.mts → Router-hW6ivqrX.d.mts} +2 -2
  30. package/dist/esm/Router-hW6ivqrX.d.mts.map +1 -0
  31. package/dist/esm/api.d.mts +2 -2
  32. package/dist/esm/api.d.mts.map +1 -1
  33. package/dist/esm/api.mjs +1 -1
  34. package/dist/esm/api.mjs.map +1 -1
  35. package/dist/esm/cloneRouter-DHrH6D_z.mjs +2 -0
  36. package/dist/esm/{cloneRouter-U8NeEoPX.mjs.map → cloneRouter-DHrH6D_z.mjs.map} +1 -1
  37. package/dist/esm/{index-DNjaY7KH.d.mts → index-C-i6vx5Y.d.mts} +1 -1
  38. package/dist/esm/index-C-i6vx5Y.d.mts.map +1 -0
  39. package/dist/esm/{RouterError-hhfSVGtY.d.mts → index-CYpAZCoc.d.mts} +19 -2
  40. package/dist/esm/index-CYpAZCoc.d.mts.map +1 -0
  41. package/dist/esm/{index-r_JTvSBH.d.mts → index-CjWKWPY6.d.mts} +2 -2
  42. package/dist/esm/index-CjWKWPY6.d.mts.map +1 -0
  43. package/dist/esm/index.d.mts +5 -5
  44. package/dist/esm/index.mjs +1 -1
  45. package/dist/esm/index.mjs.map +1 -1
  46. package/dist/esm/{internals-C59msvHY.mjs → internals-C8mRvTxc.mjs} +2 -2
  47. package/dist/esm/internals-C8mRvTxc.mjs.map +1 -0
  48. package/dist/esm/utils.d.mts +1 -1
  49. package/dist/esm/utils.mjs +1 -1
  50. package/dist/esm/utils.mjs.map +1 -1
  51. package/dist/esm/validation.d.mts +17 -5
  52. package/dist/esm/validation.d.mts.map +1 -1
  53. package/dist/esm/validation.mjs +1 -1
  54. package/package.json +4 -4
  55. package/src/Router.ts +20 -8
  56. package/src/api/getRoutesApi.ts +368 -124
  57. package/src/index.ts +16 -0
  58. package/src/internals.ts +29 -1
  59. package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +37 -0
  60. package/src/namespaces/NavigationNamespace/types.ts +1 -1
  61. package/src/namespaces/RoutesNamespace/routesStore.ts +272 -52
  62. package/src/transitionPath.ts +7 -3
  63. package/src/types.ts +9 -1
  64. package/dist/cjs/Router-CJihdrWA.d.ts.map +0 -1
  65. package/dist/cjs/Router-DnIAA87f.js +0 -6
  66. package/dist/cjs/Router-DnIAA87f.js.map +0 -1
  67. package/dist/cjs/RouterError-Bm9YnZ6e.d.ts.map +0 -1
  68. package/dist/cjs/index-8oPDJBQc.d.ts.map +0 -1
  69. package/dist/cjs/index-EwbhzRQw.d.ts.map +0 -1
  70. package/dist/cjs/internals-CM6oaz9n.js.map +0 -1
  71. package/dist/esm/Router-BmhiDQUJ.d.mts.map +0 -1
  72. package/dist/esm/Router-pwd8YBWr.mjs +0 -6
  73. package/dist/esm/Router-pwd8YBWr.mjs.map +0 -1
  74. package/dist/esm/RouterError-hhfSVGtY.d.mts.map +0 -1
  75. package/dist/esm/cloneRouter-U8NeEoPX.mjs +0 -2
  76. package/dist/esm/index-DNjaY7KH.d.mts.map +0 -1
  77. package/dist/esm/index-r_JTvSBH.d.mts.map +0 -1
  78. package/dist/esm/internals-C59msvHY.mjs.map +0 -1
@@ -1 +1 @@
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;\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,SAEtC,CAkEA,SAAgB,EAGd,EACA,EACA,EAC4B,CAC5B,IAAI,EACA,EAEJ,GAAI,EAAc,CAAO,EACvB,EAAS,EAAQ,WACZ,CACL,IAAM,EAAa,IAAI,gBACjB,MAAsB,CAC1B,EAAW,MAAM,CACnB,EAEA,EAAQ,GAAG,QAAS,CAAO,EAC3B,EAAS,EAAW,OACpB,MAAe,CACb,EAAQ,iBAAiB,QAAS,CAAO,CAC3C,CACF,CAEA,IAAM,EAAS,EAAY,EAAM,CAC/B,GAAG,EACH,YAAa,CACf,CAAiB,EAEb,EAAW,GAET,MACA,EACK,QAAQ,QAAQ,GAGzB,EAAW,GACX,IAAS,EACT,EAAO,QAAQ,EAER,QAAQ,QAAQ,GAGzB,MAAO,CACL,SACA,SACA,WACC,OAAO,cAAe,CACzB,CACF,CCnKA,SAAS,EAAkB,EAA2B,CACpD,IAAM,EAAmB,CAAC,EAE1B,IAAK,IAAM,KAAS,EAAK,SAAS,OAAO,EACnC,EAAM,SAAS,OAAS,EAC1B,EAAO,KAAK,EAAM,QAAQ,EAE1B,EAAO,KAAK,GAAG,EAAkB,CAAK,CAAC,EAI3C,OAAO,CACT,CAEA,eAAsB,EAGpB,EACA,EACmB,CAEnB,IAAM,EAAa,EADN,EAAa,CAAM,EAAE,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,CCkBA,eAAsB,EACpB,EACA,EACA,EACgB,CAChB,IAAM,EAA2B,GAAS,aAAe,KAAK,MACxD,EACJ,OAAO,GAAW,SACb,EAAY,CAAM,EAClB,EAED,EAAM,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,WAKf,CAAI,GAA4B,QAG3D,WAAW,IAAK,OAAO,GAAG,QAAQ,EAClC,WAAW,IAAK,OAAO,GAAG,QAAQ,EAClC,WAAW,IAAK,OAAO,GAAG,QAAQ,CACvC,CC0BA,SAAgB,EACd,EACA,EACQ,CACR,IAAM,EAAU,GAAS,eAErB,EAAU,EAAM,QAEpB,GAAI,GAAS,OAAQ,CACnB,IAAM,EAAoC,CAAC,EACrC,EAAS,EAAM,QAErB,IAAK,IAAM,KAAO,OAAO,KAAK,CAAM,EAC7B,EAAQ,SAAS,CAAG,IACvB,EAAS,GAAO,EAAO,IAI3B,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
+ {"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;\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,SAEtC,CAkEA,SAAgB,EAGd,EACA,EACA,EAC4B,CAC5B,IAAI,EACA,EAEJ,GAAI,EAAc,CAAO,EACvB,EAAS,EAAQ,WACZ,CACL,IAAM,EAAa,IAAI,gBACjB,MAAsB,CAC1B,EAAW,MAAM,CACnB,EAEA,EAAQ,GAAG,QAAS,CAAO,EAC3B,EAAS,EAAW,OACpB,MAAe,CACb,EAAQ,iBAAiB,QAAS,CAAO,CAC3C,CACF,CAEA,IAAM,EAAS,EAAY,EAAM,CAC/B,GAAG,EACH,YAAa,CACf,CAAiB,EAEb,EAAW,GAET,MACA,EACK,QAAQ,QAAQ,GAGzB,EAAW,GACX,IAAS,EACT,EAAO,QAAQ,EAER,QAAQ,QAAQ,GAGzB,MAAO,CACL,SACA,SACA,WACC,OAAO,cAAe,CACzB,CACF,CCnKA,SAAS,EAAkB,EAA2B,CACpD,IAAM,EAAmB,CAAC,EAE1B,IAAK,IAAM,KAAS,EAAK,SAAS,OAAO,EACnC,EAAM,SAAS,OAAS,EAC1B,EAAO,KAAK,EAAM,QAAQ,EAE1B,EAAO,KAAK,GAAG,EAAkB,CAAK,CAAC,EAI3C,OAAO,CACT,CAEA,eAAsB,EAGpB,EACA,EACmB,CAEnB,IAAM,EAAa,EADN,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,CCkBA,eAAsB,EACpB,EACA,EACA,EACgB,CAChB,IAAM,EAA2B,GAAS,aAAe,KAAK,MACxD,EACJ,OAAO,GAAW,SACb,EAAY,CAAM,EAClB,EAED,EAAM,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,CACnB,IAAM,EAAoC,CAAC,EACrC,EAAS,EAAM,QAErB,IAAK,IAAM,KAAO,OAAO,KAAK,CAAM,EAC7B,EAAQ,SAAS,CAAG,IACvB,EAAS,GAAO,EAAO,IAI3B,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,9 +1,9 @@
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";
1
+ import { i as RouteTree, n as Matcher, r as RouteDefinition, t as CreateMatcherOptions } from "./index-C-i6vx5Y.mjs";
2
+ import { a as Limits, i as GuardFnFactory, o as PluginFactory, r as EventMethodMap } from "./Router-hW6ivqrX.mjs";
3
+ import { r as RouterValidator } from "./index-CYpAZCoc.mjs";
4
+ import { n as SerializedRouterState } from "./index-CjWKWPY6.mjs";
5
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";
6
+ import { DefaultDependencies, EventName, ForwardToCallback, GuardFn, LeaveFn, NavigationOptions, Options, Params, Plugin, RouteTreeState, Router, SimpleState, State, SubscribeFn, TreeChangedEvent, Unsubscribe } from "@real-router/types";
7
7
 
8
8
  //#region src/namespaces/DependenciesNamespace/dependenciesStore.d.ts
9
9
  interface DependenciesStore<Dependencies extends DefaultDependencies = DefaultDependencies> {
@@ -201,6 +201,18 @@ interface RouterInternals<D extends DefaultDependencies = DefaultDependencies> {
201
201
  readonly matchPath: <P extends Params = Params>(path: string, options?: Options) => State<P> | undefined;
202
202
  readonly getOptions: () => Options;
203
203
  readonly addEventListener: <E extends EventName>(eventName: E, cb: Plugin[EventMethodMap[E]]) => Unsubscribe;
204
+ /**
205
+ * Route-tree mutation channel — internal access for the `getRoutesApi`
206
+ * wrapper. A dedicated bridge is required because the public
207
+ * `addEventListener<E extends EventName>` structurally rejects
208
+ * `"TREE_CHANGED"` (it is not in the public `EventName` union), is strict on
209
+ * duplicates, and exposes neither `emit` nor `listenerCount`.
210
+ */
211
+ readonly treeChanged: {
212
+ readonly emit: (event: TreeChangedEvent) => void;
213
+ readonly subscribe: (handler: (event: TreeChangedEvent) => void) => Unsubscribe;
214
+ readonly listenerCount: () => number;
215
+ };
204
216
  readonly buildPath: (route: string, params?: Params) => string;
205
217
  readonly emitTransitionError: (error: Error) => void;
206
218
  readonly start: (path: string) => Promise<State>;
@@ -1 +1 @@
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
+ {"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-C59msvHY.mjs";export{e as getInternals};
1
+ import{r as e}from"./internals-C8mRvTxc.mjs";export{e as getInternals};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real-router/core",
3
- "version": "0.54.6",
3
+ "version": "0.56.0",
4
4
  "type": "commonjs",
5
5
  "description": "A simple, powerful, view-agnostic, modular and extensible router",
6
6
  "main": "./dist/cjs/index.js",
@@ -89,11 +89,11 @@
89
89
  "dependencies": {
90
90
  "@real-router/fsm": "^0.4.0",
91
91
  "@real-router/logger": "^0.3.0",
92
- "@real-router/types": "^0.35.0"
92
+ "@real-router/types": "^0.36.0"
93
93
  },
94
94
  "devDependencies": {
95
- "route-tree": "^0.3.4",
96
- "event-emitter": "^0.1.2"
95
+ "event-emitter": "^0.1.2",
96
+ "route-tree": "^0.3.4"
97
97
  },
98
98
  "scripts": {
99
99
  "test": "vitest run functional/",
package/src/Router.ts CHANGED
@@ -14,8 +14,8 @@ import { createRouterFSM } from "./fsm";
14
14
  import { guardDependencies, guardRouteStructure } from "./guards";
15
15
  import { createLimits, normalizeParams } from "./helpers";
16
16
  import {
17
+ createBinaryInterceptable,
17
18
  createInterceptable,
18
- createInterceptable2,
19
19
  getInternals,
20
20
  registerInternals,
21
21
  } from "./internals";
@@ -54,6 +54,14 @@ import type { CreateMatcherOptions } from "route-tree";
54
54
 
55
55
  const EMPTY_OPTS: Readonly<NavigationOptions> = Object.freeze({});
56
56
 
57
+ // Module-level so #onSuppressedError allocates nothing per navigate() call.
58
+ const SUPPRESSED_ERROR_CODES: ReadonlySet<string> = new Set([
59
+ errorCodes.SAME_STATES,
60
+ errorCodes.TRANSITION_CANCELLED,
61
+ errorCodes.ROUTER_NOT_STARTED,
62
+ errorCodes.ROUTE_NOT_FOUND,
63
+ ]);
64
+
57
65
  /**
58
66
  * Router class with integrated namespace architecture.
59
67
  *
@@ -192,12 +200,12 @@ export class Router<
192
200
  registerInternals(this, {
193
201
  makeState: (name, params, path, meta) =>
194
202
  this.#state.makeState(name, params, path, meta),
195
- // `as unknown as` is required: createInterceptable2 returns a
203
+ // `as unknown as` is required: createBinaryInterceptable returns a
196
204
  // non-generic `(a: A, b: B) => R`, but RouterInternals["forwardState"]
197
205
  // is declared with a generic parameter `<P extends Params = Params>`,
198
206
  // which tsc will not infer from the non-generic source. Sonar S4325
199
207
  // misclassifies this as a redundant cast.
200
- forwardState: createInterceptable2(
208
+ forwardState: createBinaryInterceptable(
201
209
  "forwardState",
202
210
  (name: string, params: Params) =>
203
211
  this.#routes.forwardState(name, params),
@@ -210,7 +218,14 @@ export class Router<
210
218
  getOptions: () => this.#options.get(),
211
219
  addEventListener: (eventName, cb) =>
212
220
  this.#eventBus.addEventListener(eventName, cb),
213
- buildPath: createInterceptable2(
221
+ treeChanged: {
222
+ emit: (event) => {
223
+ this.#eventBus.emitTreeChanged(event);
224
+ },
225
+ subscribe: (handler) => this.#eventBus.subscribeTreeChanged(handler),
226
+ listenerCount: () => this.#eventBus.treeChangedListenerCount(),
227
+ },
228
+ buildPath: createBinaryInterceptable(
214
229
  "buildPath",
215
230
  (route: string, params?: Params) =>
216
231
  this.#routes.buildPath(
@@ -670,10 +685,7 @@ export class Router<
670
685
  static readonly #onSuppressedError = (error: unknown): void => {
671
686
  if (
672
687
  error instanceof RouterError &&
673
- (error.code === errorCodes.SAME_STATES ||
674
- error.code === errorCodes.TRANSITION_CANCELLED ||
675
- error.code === errorCodes.ROUTER_NOT_STARTED ||
676
- error.code === errorCodes.ROUTE_NOT_FOUND)
688
+ SUPPRESSED_ERROR_CODES.has(error.code)
677
689
  ) {
678
690
  return;
679
691
  }