@real-router/core 0.55.0 → 0.57.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 (145) hide show
  1. package/dist/cjs/Router-BSGzVINO.js +6 -0
  2. package/dist/cjs/Router-BSGzVINO.js.map +1 -0
  3. package/dist/{esm/Router-Dg-zk8AS.d.mts → cjs/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-C9Rth_8U.js → cloneRouter-7z-60z_f.js} +2 -2
  10. package/dist/cjs/{cloneRouter-C9Rth_8U.js.map → cloneRouter-7z-60z_f.js.map} +1 -1
  11. package/dist/cjs/{index-C-i6vx5Y.d.ts → index-BWUmnecT.d.ts} +1 -2
  12. package/dist/cjs/index-BWUmnecT.d.ts.map +1 -0
  13. package/dist/cjs/{RouterError-WhCzIWuc.d.ts → index-CYpAZCoc.d.ts} +19 -2
  14. package/dist/cjs/index-CYpAZCoc.d.ts.map +1 -0
  15. package/dist/cjs/{index-K1U_fqfJ.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-CWMOL1B8.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-B7txWo9N.mjs +6 -0
  28. package/dist/esm/Router-B7txWo9N.mjs.map +1 -0
  29. package/dist/{cjs/Router-Dg-zk8AS.d.ts → esm/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-BYNiwchg.mjs → cloneRouter-BNCQ7tIa.mjs} +2 -2
  36. package/dist/esm/{cloneRouter-BYNiwchg.mjs.map → cloneRouter-BNCQ7tIa.mjs.map} +1 -1
  37. package/dist/esm/{index-C-i6vx5Y.d.mts → index-BWUmnecT.d.mts} +1 -2
  38. package/dist/esm/index-BWUmnecT.d.mts.map +1 -0
  39. package/dist/esm/{RouterError-WhCzIWuc.d.mts → index-CYpAZCoc.d.mts} +19 -2
  40. package/dist/esm/index-CYpAZCoc.d.mts.map +1 -0
  41. package/dist/esm/{index-DKzxav48.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-DT4mneSz.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 +3 -4
  55. package/dist/cjs/Router-C7eE1kIK.js +0 -6
  56. package/dist/cjs/Router-C7eE1kIK.js.map +0 -1
  57. package/dist/cjs/Router-Dg-zk8AS.d.ts.map +0 -1
  58. package/dist/cjs/RouterError-WhCzIWuc.d.ts.map +0 -1
  59. package/dist/cjs/index-C-i6vx5Y.d.ts.map +0 -1
  60. package/dist/cjs/index-K1U_fqfJ.d.ts.map +0 -1
  61. package/dist/cjs/internals-CWMOL1B8.js.map +0 -1
  62. package/dist/esm/Router-Dg-zk8AS.d.mts.map +0 -1
  63. package/dist/esm/Router-DiZbYMLx.mjs +0 -6
  64. package/dist/esm/Router-DiZbYMLx.mjs.map +0 -1
  65. package/dist/esm/RouterError-WhCzIWuc.d.mts.map +0 -1
  66. package/dist/esm/index-C-i6vx5Y.d.mts.map +0 -1
  67. package/dist/esm/index-DKzxav48.d.mts.map +0 -1
  68. package/dist/esm/internals-DT4mneSz.mjs.map +0 -1
  69. package/src/Router.ts +0 -725
  70. package/src/RouterError.ts +0 -324
  71. package/src/api/cloneRouter.ts +0 -159
  72. package/src/api/getDependenciesApi.ts +0 -160
  73. package/src/api/getLifecycleApi.ts +0 -65
  74. package/src/api/getPluginApi.ts +0 -228
  75. package/src/api/getRoutesApi.ts +0 -546
  76. package/src/api/helpers.ts +0 -10
  77. package/src/api/index.ts +0 -16
  78. package/src/api/types.ts +0 -12
  79. package/src/constants.ts +0 -101
  80. package/src/createRouter.ts +0 -32
  81. package/src/fsm/index.ts +0 -5
  82. package/src/fsm/routerFSM.ts +0 -130
  83. package/src/getNavigator.ts +0 -30
  84. package/src/guards.ts +0 -46
  85. package/src/helpers.ts +0 -197
  86. package/src/index.ts +0 -50
  87. package/src/internals.ts +0 -200
  88. package/src/namespaces/DependenciesNamespace/dependenciesStore.ts +0 -30
  89. package/src/namespaces/DependenciesNamespace/index.ts +0 -5
  90. package/src/namespaces/EventBusNamespace/EventBusNamespace.ts +0 -485
  91. package/src/namespaces/EventBusNamespace/index.ts +0 -5
  92. package/src/namespaces/EventBusNamespace/types.ts +0 -11
  93. package/src/namespaces/NavigationNamespace/NavigationNamespace.ts +0 -552
  94. package/src/namespaces/NavigationNamespace/constants.ts +0 -55
  95. package/src/namespaces/NavigationNamespace/index.ts +0 -5
  96. package/src/namespaces/NavigationNamespace/transition/completeTransition.ts +0 -108
  97. package/src/namespaces/NavigationNamespace/transition/errorHandling.ts +0 -124
  98. package/src/namespaces/NavigationNamespace/transition/guardPhase.ts +0 -283
  99. package/src/namespaces/NavigationNamespace/types.ts +0 -110
  100. package/src/namespaces/OptionsNamespace/OptionsNamespace.ts +0 -28
  101. package/src/namespaces/OptionsNamespace/constants.ts +0 -19
  102. package/src/namespaces/OptionsNamespace/helpers.ts +0 -50
  103. package/src/namespaces/OptionsNamespace/index.ts +0 -7
  104. package/src/namespaces/OptionsNamespace/validators.ts +0 -13
  105. package/src/namespaces/PluginsNamespace/PluginsNamespace.ts +0 -291
  106. package/src/namespaces/PluginsNamespace/constants.ts +0 -34
  107. package/src/namespaces/PluginsNamespace/index.ts +0 -7
  108. package/src/namespaces/PluginsNamespace/types.ts +0 -22
  109. package/src/namespaces/PluginsNamespace/validators.ts +0 -28
  110. package/src/namespaces/RouteLifecycleNamespace/RouteLifecycleNamespace.ts +0 -558
  111. package/src/namespaces/RouteLifecycleNamespace/index.ts +0 -5
  112. package/src/namespaces/RouteLifecycleNamespace/types.ts +0 -10
  113. package/src/namespaces/RouterLifecycleNamespace/RouterLifecycleNamespace.ts +0 -81
  114. package/src/namespaces/RouterLifecycleNamespace/constants.ts +0 -25
  115. package/src/namespaces/RouterLifecycleNamespace/index.ts +0 -5
  116. package/src/namespaces/RouterLifecycleNamespace/types.ts +0 -30
  117. package/src/namespaces/RoutesNamespace/RoutesNamespace.ts +0 -582
  118. package/src/namespaces/RoutesNamespace/constants.ts +0 -6
  119. package/src/namespaces/RoutesNamespace/forwardChain.ts +0 -34
  120. package/src/namespaces/RoutesNamespace/helpers.ts +0 -204
  121. package/src/namespaces/RoutesNamespace/index.ts +0 -11
  122. package/src/namespaces/RoutesNamespace/routeGuards.ts +0 -62
  123. package/src/namespaces/RoutesNamespace/routesStore.ts +0 -566
  124. package/src/namespaces/RoutesNamespace/types.ts +0 -81
  125. package/src/namespaces/StateNamespace/StateNamespace.ts +0 -224
  126. package/src/namespaces/StateNamespace/helpers.ts +0 -24
  127. package/src/namespaces/StateNamespace/index.ts +0 -5
  128. package/src/namespaces/StateNamespace/types.ts +0 -15
  129. package/src/namespaces/index.ts +0 -35
  130. package/src/stateMetaStore.ts +0 -15
  131. package/src/transitionPath.ts +0 -436
  132. package/src/typeGuards.ts +0 -59
  133. package/src/types/RouterValidator.ts +0 -156
  134. package/src/types.ts +0 -69
  135. package/src/utils/createRequestScope.ts +0 -174
  136. package/src/utils/getStaticPaths.ts +0 -50
  137. package/src/utils/hydrateRouter.ts +0 -89
  138. package/src/utils/index.ts +0 -27
  139. package/src/utils/serializeRouterState.ts +0 -120
  140. package/src/utils/serializeState.ts +0 -63
  141. package/src/validation.ts +0 -12
  142. package/src/wiring/RouterWiringBuilder.ts +0 -275
  143. package/src/wiring/index.ts +0 -7
  144. package/src/wiring/types.ts +0 -47
  145. package/src/wiring/wireRouter.ts +0 -26
package/src/types.ts DELETED
@@ -1,69 +0,0 @@
1
- // packages/core/src/types.ts
2
-
3
- /**
4
- * Core-internal types + re-exports from @real-router/types.
5
- *
6
- * Factory types (PluginFactory, GuardFnFactory) and
7
- * route config types (Route, RouteConfigUpdate) are canonical in @real-router/types
8
- * and re-exported here for backward compatibility.
9
- */
10
-
11
- import type {
12
- LimitsConfig,
13
- NavigationOptions,
14
- Params,
15
- RouterError as RouterErrorType,
16
- RouteTreeState,
17
- State,
18
- } from "@real-router/types";
19
-
20
- // Re-export from @real-router/types (canonical source)
21
- export type {
22
- GuardFnFactory,
23
- PluginFactory,
24
- Route,
25
- RouteConfigUpdate,
26
- EventMethodMap,
27
- } from "@real-router/types";
28
-
29
- /**
30
- * Event argument tuples for the router's 7 events.
31
- *
32
- * Uses explicit `| undefined` unions (not optional `?`) to satisfy
33
- * `exactOptionalPropertyTypes` when passing undefined args from FSM payloads.
34
- */
35
- // eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- must be `type` for Record<string, unknown[]> constraint
36
- export type RouterEventMap = {
37
- $start: [];
38
- $stop: [];
39
- $$start: [toState: State, fromState: State | undefined];
40
- $$leaveApprove: [toState: State, fromState: State | undefined];
41
- $$success: [
42
- toState: State,
43
- fromState: State | undefined,
44
- opts: NavigationOptions | undefined,
45
- ];
46
- $$error: [
47
- toState: State | undefined,
48
- fromState: State | undefined,
49
- error: RouterErrorType | undefined,
50
- ];
51
- $$cancel: [toState: State, fromState: State | undefined];
52
- };
53
-
54
- /**
55
- * Immutable limits configuration type.
56
- */
57
- export type Limits = Readonly<LimitsConfig>;
58
-
59
- /**
60
- * Extended build result that includes segments for path building.
61
- * Used internally to avoid duplicate getSegmentsByName calls.
62
- *
63
- * @param segments - Route segments from getSegmentsByName (typed as unknown[] for cross-package compatibility)
64
- * @internal
65
- */
66
- export interface BuildStateResultWithSegments<P extends Params = Params> {
67
- readonly state: RouteTreeState<P>;
68
- readonly segments: readonly unknown[];
69
- }
@@ -1,174 +0,0 @@
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
- }
@@ -1,50 +0,0 @@
1
- import { getPluginApi } from "../api/getPluginApi";
2
-
3
- import type { DefaultDependencies, Router } from "@real-router/types";
4
- import type { RouteTree } from "route-tree";
5
-
6
- export type StaticPathEntries = Record<
7
- string,
8
- () => Promise<Record<string, string>[]>
9
- >;
10
-
11
- function getLeafRouteNames(node: RouteTree): string[] {
12
- const result: string[] = [];
13
-
14
- for (const child of node.children.values()) {
15
- if (child.children.size === 0) {
16
- result.push(child.fullName);
17
- } else {
18
- result.push(...getLeafRouteNames(child));
19
- }
20
- }
21
-
22
- return result;
23
- }
24
-
25
- export async function getStaticPaths<
26
- Dependencies extends DefaultDependencies = DefaultDependencies,
27
- >(
28
- router: Router<Dependencies>,
29
- entries?: StaticPathEntries,
30
- ): Promise<string[]> {
31
- const tree = getPluginApi(router).getTree();
32
- const leafRoutes = getLeafRouteNames(tree);
33
- const paths: string[] = [];
34
-
35
- for (const routeName of leafRoutes) {
36
- const entryFn = entries?.[routeName];
37
-
38
- if (entryFn) {
39
- const paramSets = await entryFn();
40
-
41
- for (const params of paramSets) {
42
- paths.push(router.buildPath(routeName, params));
43
- }
44
- } else {
45
- paths.push(router.buildPath(routeName, {}));
46
- }
47
- }
48
-
49
- return paths;
50
- }
@@ -1,89 +0,0 @@
1
- import { getInternals } from "../internals";
2
-
3
- import type { SerializedRouterState } from "./serializeRouterState";
4
- import type { Router, State } from "@real-router/types";
5
-
6
- /**
7
- * Custom deserializer signature for {@link hydrateRouter} (#606). Compatible
8
- * with `JSON.parse` (default), `devalue.parse`, `superjson.parse`, or any
9
- * user-supplied function.
10
- */
11
- export type Deserialize = (json: string) => unknown;
12
-
13
- export interface HydrateRouterOptions {
14
- /**
15
- * Custom deserializer (e.g., `devalue.parse` / `superjson.parse`) for
16
- * matching the `serialize` passed to {@link serializeRouterState}. Defaults
17
- * to `JSON.parse`. Ignored when `source` is already an object.
18
- *
19
- * @default JSON.parse
20
- *
21
- * @example
22
- * ```typescript
23
- * import * as devalue from "devalue";
24
- * await hydrateRouter(router, ssrJson, { deserialize: devalue.parse });
25
- * ```
26
- */
27
- deserialize?: Deserialize;
28
- }
29
-
30
- /**
31
- * Hydrate a fresh router from server-serialized State (#563, #596).
32
- *
33
- * Accepts either a JSON string (parsed via `JSON.parse` by default, or
34
- * `options.deserialize` when provided) or a State-shaped object. Extracts
35
- * `state.path` and delegates to `router.start(state.path)` — the canonical
36
- * URL is the source of truth for the router on hydration.
37
- *
38
- * The full parsed state (incl. `state.context.<namespace>` payloads) is
39
- * deposited into a one-shot scratchpad on `RouterInternals.hydrationState`
40
- * before `start()` is invoked and cleared in the matching `finally`. SSR
41
- * loader plugins (`@real-router/ssr-data-plugin`,
42
- * `@real-router/rsc-server-plugin`) read this scratchpad to skip their loader
43
- * call when the server-resolved namespace value is already present — avoiding
44
- * the post-hydration loader re-run on first paint.
45
- *
46
- * Single-shot semantics: the scratchpad is consumed during the first `start()`
47
- * triggered by `hydrateRouter` regardless of route mismatch; subsequent
48
- * `start()` calls run loaders normally.
49
- *
50
- * @example
51
- * ```typescript
52
- * // Client
53
- * const router = createAppRouter();
54
- * router.usePlugin(browserPluginFactory());
55
- * await hydrateRouter(router, window.__SSR_STATE__);
56
- * ```
57
- *
58
- * @example
59
- * ```typescript
60
- * // With non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)
61
- * import * as devalue from "devalue";
62
- *
63
- * await hydrateRouter(router, window.__SSR_STATE__, {
64
- * deserialize: devalue.parse,
65
- * });
66
- * ```
67
- */
68
- export async function hydrateRouter(
69
- router: Router,
70
- source: string | { path: string },
71
- options?: HydrateRouterOptions,
72
- ): Promise<State> {
73
- const deserialize: Deserialize = options?.deserialize ?? JSON.parse;
74
- const parsed =
75
- typeof source === "string"
76
- ? (deserialize(source) as SerializedRouterState)
77
- : (source as SerializedRouterState);
78
-
79
- const ctx = getInternals(router);
80
- const previous = ctx.hydrationState;
81
-
82
- ctx.hydrationState = parsed;
83
-
84
- try {
85
- return await router.start(parsed.path);
86
- } finally {
87
- ctx.hydrationState = previous;
88
- }
89
- }
@@ -1,27 +0,0 @@
1
- export { createRequestScope } from "./createRequestScope";
2
-
3
- export type {
4
- IncomingMessageLike,
5
- RequestLike,
6
- RequestScope,
7
- RequestScopeSource,
8
- } from "./createRequestScope";
9
-
10
- export { getStaticPaths } from "./getStaticPaths";
11
-
12
- export { hydrateRouter } from "./hydrateRouter";
13
-
14
- export type { Deserialize, HydrateRouterOptions } from "./hydrateRouter";
15
-
16
- export { serializeRouterState } from "./serializeRouterState";
17
-
18
- export type {
19
- SerializedRouterState,
20
- SerializeRouterStateOptions,
21
- } from "./serializeRouterState";
22
-
23
- export { serializeState } from "./serializeState";
24
-
25
- export type { Serialize, SerializeStateOptions } from "./serializeState";
26
-
27
- export type { StaticPathEntries } from "./getStaticPaths";
@@ -1,120 +0,0 @@
1
- import { serializeState } from "./serializeState";
2
-
3
- import type { Serialize } from "./serializeState";
4
- import type { Params, State } from "@real-router/types";
5
-
6
- /**
7
- * Parsed shape produced by {@link serializeRouterState} (after `JSON.parse`).
8
- *
9
- * Identical to {@link State} minus `transition` (per-navigation `TransitionMeta`
10
- * is meaningless after hydration; the client builds its own on commit). Used as
11
- * the input shape for {@link hydrateRouter} and as the type of the one-shot
12
- * hydration scratchpad consumed by SSR loader plugins.
13
- */
14
- export type SerializedRouterState<P extends Params = Params> = Omit<
15
- State<P>,
16
- "transition"
17
- >;
18
-
19
- export interface SerializeRouterStateOptions {
20
- /**
21
- * Plugin context namespaces to strip from the serialized output.
22
- * Use when a plugin populates `state.context.<ns>` with non-JSON-serializable
23
- * values (e.g., RSC payload: ReactNode trees containing functions/symbols).
24
- *
25
- * @default []
26
- */
27
- excludeContext?: readonly string[];
28
-
29
- /**
30
- * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to
31
- * support non-JSON types in `state.params` and `state.context.<ns>` payloads
32
- * (Date / Map / Set / RegExp / BigInt). Defaults to `JSON.stringify`.
33
- *
34
- * Pair with the matching `deserialize` on `hydrateRouter` to round-trip the
35
- * extended types on the client.
36
- *
37
- * @default JSON.stringify
38
- *
39
- * @example
40
- * ```typescript
41
- * import * as devalue from "devalue";
42
- *
43
- * const json = serializeRouterState(state, { serialize: devalue.stringify });
44
- * ```
45
- */
46
- serialize?: Serialize;
47
- }
48
-
49
- /**
50
- * XSS-safe JSON serialization of router State for SSR → client transport (#563).
51
- *
52
- * Strips `state.transition` (per-navigation `TransitionMeta` — meaningless after
53
- * hydration; the client's hydration commit produces its own `transition`).
54
- * Keeps `name`, `params`, `path`, and `context` (plugin context namespaces are
55
- * preserved as-is — server's `state.context.data` from `ssr-data-plugin` and
56
- * any other plugin claims travel to the client untouched).
57
- *
58
- * Pass `options.excludeContext` to strip specific namespaces from the output —
59
- * required for plugins that publish non-JSON-serializable values (e.g., RSC
60
- * `ReactNode` trees from `@real-router/rsc-server-plugin`).
61
- *
62
- * @example
63
- * ```typescript
64
- * // Server
65
- * const state = await router.start(req.url);
66
- * const html = `<script>window.__SSR_STATE__=${serializeRouterState(state)}</script>`;
67
- *
68
- * // Client
69
- * await hydrateRouter(router, window.__SSR_STATE__);
70
- * ```
71
- *
72
- * @example
73
- * ```typescript
74
- * // With RSC plugin: strip the "rsc" namespace before transport
75
- * const state = await router.start(url);
76
- * const json = serializeRouterState(state, { excludeContext: ["rsc"] });
77
- * ```
78
- *
79
- * @example
80
- * ```typescript
81
- * // Non-JSON types (Date / Map / Set / RegExp / BigInt) via devalue (#606)
82
- * import * as devalue from "devalue";
83
- *
84
- * const json = serializeRouterState(state, { serialize: devalue.stringify });
85
- * // On the client:
86
- * await hydrateRouter(router, json, { deserialize: devalue.parse });
87
- * ```
88
- */
89
- export function serializeRouterState(
90
- state: State,
91
- options?: SerializeRouterStateOptions,
92
- ): string {
93
- const exclude = options?.excludeContext;
94
-
95
- let context = state.context;
96
-
97
- if (exclude?.length) {
98
- const filtered: Record<string, unknown> = {};
99
- const source = state.context;
100
-
101
- for (const key of Object.keys(source)) {
102
- if (!exclude.includes(key)) {
103
- filtered[key] = source[key];
104
- }
105
- }
106
-
107
- context = filtered;
108
- }
109
-
110
- const payload = {
111
- name: state.name,
112
- params: state.params,
113
- path: state.path,
114
- context,
115
- };
116
-
117
- return options?.serialize
118
- ? serializeState(payload, { serialize: options.serialize })
119
- : serializeState(payload);
120
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * Custom serializer signature for {@link serializeState} (#606).
3
- *
4
- * Compatible with `JSON.stringify` (default) as well as `devalue.stringify`,
5
- * `superjson.stringify`, or any user-supplied function that returns a JSON
6
- * string. The output is run through XSS-safe character escapes regardless
7
- * of which serializer produced it.
8
- */
9
- export type Serialize = (data: unknown) => string;
10
-
11
- export interface SerializeStateOptions {
12
- /**
13
- * Custom serializer (e.g., `devalue.stringify` / `superjson.stringify`) to
14
- * support non-JSON types (Date / Map / Set / RegExp / BigInt). Defaults to
15
- * `JSON.stringify`. Output is still XSS-escaped.
16
- *
17
- * @default JSON.stringify
18
- */
19
- serialize?: Serialize;
20
- }
21
-
22
- /**
23
- * XSS-safe JSON serialization for embedding data in HTML `<script>` tags.
24
- *
25
- * Escapes `<`, `>`, and `&` to their Unicode equivalents to prevent
26
- * injection via `</script>` or HTML entities inside inline scripts.
27
- *
28
- * Pass `options.serialize` to use a custom serializer such as `devalue` or
29
- * `superjson` for non-JSON types (Date/Map/Set/RegExp/BigInt). The serializer
30
- * must return a string; XSS-escape is applied to its output.
31
- *
32
- * @example
33
- * ```typescript
34
- * const json = serializeState({ name: "home", path: "/" });
35
- * const html = `<script>window.__STATE__=${json}</script>`;
36
- * ```
37
- *
38
- * @example
39
- * ```typescript
40
- * import * as devalue from "devalue";
41
- *
42
- * const json = serializeState(
43
- * { date: new Date(), tags: new Set(["a", "b"]) },
44
- * { serialize: devalue.stringify },
45
- * );
46
- * ```
47
- */
48
- export function serializeState(
49
- data: unknown,
50
- options?: SerializeStateOptions,
51
- ): string {
52
- const serialize = options?.serialize ?? JSON.stringify;
53
- // JSON.stringify returns undefined for top-level `undefined`, Symbol,
54
- // function, and other non-serializable values (lib.d.ts types it as
55
- // `string` but the runtime can return undefined). A custom serializer
56
- // that returns undefined for unsupported input is normalized the same way.
57
- const serialized = (serialize(data) as string | undefined) ?? "null";
58
-
59
- return serialized
60
- .replaceAll("<", String.raw`\u003c`)
61
- .replaceAll(">", String.raw`\u003e`)
62
- .replaceAll("&", String.raw`\u0026`);
63
- }
package/src/validation.ts DELETED
@@ -1,12 +0,0 @@
1
- /**
2
- * Subpath export for @real-router/validation-plugin.
3
- *
4
- * Provides access to router internals so the plugin can install
5
- * ctx.validator and run retrospective validation at registration time.
6
- */
7
-
8
- export type { RouterValidator } from "./types/RouterValidator";
9
-
10
- export { getInternals } from "./internals";
11
-
12
- export type { RouterInternals } from "./internals";