@rangojs/router 0.0.0-experimental.9 → 0.0.0-experimental.a5f27bd5

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 (299) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -155
  4. package/dist/vite/index.js +4440 -2170
  5. package/package.json +60 -54
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +50 -21
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +12 -8
  11. package/skills/document-cache/SKILL.md +18 -16
  12. package/skills/fonts/SKILL.md +6 -4
  13. package/skills/hooks/SKILL.md +333 -71
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +131 -8
  16. package/skills/layout/SKILL.md +100 -3
  17. package/skills/links/SKILL.md +74 -15
  18. package/skills/loader/SKILL.md +388 -38
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +15 -11
  21. package/skills/parallel/SKILL.md +78 -1
  22. package/skills/prerender/SKILL.md +405 -45
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +144 -91
  25. package/skills/route/SKILL.md +226 -14
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +316 -87
  29. package/skills/use-cache/SKILL.md +324 -0
  30. package/src/__internal.ts +102 -4
  31. package/src/bin/rango.ts +312 -15
  32. package/src/browser/action-coordinator.ts +97 -0
  33. package/src/browser/action-response-classifier.ts +99 -0
  34. package/src/browser/event-controller.ts +87 -64
  35. package/src/browser/history-state.ts +80 -0
  36. package/src/browser/intercept-utils.ts +52 -0
  37. package/src/browser/link-interceptor.ts +24 -4
  38. package/src/browser/logging.ts +55 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +285 -553
  41. package/src/browser/navigation-client.ts +123 -73
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +295 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +261 -309
  46. package/src/browser/prefetch/cache.ts +154 -0
  47. package/src/browser/prefetch/fetch.ts +135 -0
  48. package/src/browser/prefetch/observer.ts +65 -0
  49. package/src/browser/prefetch/policy.ts +48 -0
  50. package/src/browser/prefetch/queue.ts +88 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +182 -70
  53. package/src/browser/react/NavigationProvider.tsx +51 -11
  54. package/src/browser/react/context.ts +6 -0
  55. package/src/browser/react/filter-segment-order.ts +11 -0
  56. package/src/browser/react/index.ts +12 -12
  57. package/src/browser/react/location-state-shared.ts +95 -53
  58. package/src/browser/react/location-state.ts +60 -15
  59. package/src/browser/react/mount-context.ts +6 -1
  60. package/src/browser/react/nonce-context.ts +23 -0
  61. package/src/browser/react/shallow-equal.ts +27 -0
  62. package/src/browser/react/use-action.ts +29 -51
  63. package/src/browser/react/use-client-cache.ts +5 -3
  64. package/src/browser/react/use-handle.ts +29 -70
  65. package/src/browser/react/use-link-status.ts +6 -5
  66. package/src/browser/react/use-navigation.ts +22 -63
  67. package/src/browser/react/use-params.ts +65 -0
  68. package/src/browser/react/use-pathname.ts +47 -0
  69. package/src/browser/react/use-router.ts +63 -0
  70. package/src/browser/react/use-search-params.ts +56 -0
  71. package/src/browser/react/use-segments.ts +80 -97
  72. package/src/browser/response-adapter.ts +73 -0
  73. package/src/browser/rsc-router.tsx +106 -27
  74. package/src/browser/scroll-restoration.ts +92 -16
  75. package/src/browser/segment-reconciler.ts +216 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +504 -599
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +107 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +82 -21
  82. package/src/build/generate-route-types.ts +36 -752
  83. package/src/build/index.ts +6 -5
  84. package/src/build/route-trie.ts +39 -13
  85. package/src/build/route-types/ast-helpers.ts +25 -0
  86. package/src/build/route-types/ast-route-extraction.ts +98 -0
  87. package/src/build/route-types/codegen.ts +102 -0
  88. package/src/build/route-types/include-resolution.ts +411 -0
  89. package/src/build/route-types/param-extraction.ts +48 -0
  90. package/src/build/route-types/per-module-writer.ts +128 -0
  91. package/src/build/route-types/router-processing.ts +469 -0
  92. package/src/build/route-types/scan-filter.ts +78 -0
  93. package/src/build/runtime-discovery.ts +231 -0
  94. package/src/cache/background-task.ts +34 -0
  95. package/src/cache/cache-key-utils.ts +44 -0
  96. package/src/cache/cache-policy.ts +125 -0
  97. package/src/cache/cache-runtime.ts +338 -0
  98. package/src/cache/cache-scope.ts +120 -301
  99. package/src/cache/cf/cf-cache-store.ts +119 -7
  100. package/src/cache/cf/index.ts +8 -2
  101. package/src/cache/document-cache.ts +101 -72
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +0 -15
  105. package/src/cache/memory-segment-store.ts +191 -13
  106. package/src/cache/profile-registry.ts +73 -0
  107. package/src/cache/read-through-swr.ts +134 -0
  108. package/src/cache/segment-codec.ts +256 -0
  109. package/src/cache/taint.ts +98 -0
  110. package/src/cache/types.ts +72 -122
  111. package/src/client.rsc.tsx +3 -1
  112. package/src/client.tsx +84 -126
  113. package/src/component-utils.ts +4 -4
  114. package/src/components/DefaultDocument.tsx +5 -1
  115. package/src/context-var.ts +86 -0
  116. package/src/debug.ts +17 -7
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +15 -10
  119. package/src/handles/MetaTags.tsx +73 -20
  120. package/src/handles/breadcrumbs.ts +66 -0
  121. package/src/handles/index.ts +1 -0
  122. package/src/handles/meta.ts +30 -13
  123. package/src/host/cookie-handler.ts +21 -15
  124. package/src/host/errors.ts +8 -8
  125. package/src/host/index.ts +4 -7
  126. package/src/host/pattern-matcher.ts +27 -27
  127. package/src/host/router.ts +61 -39
  128. package/src/host/testing.ts +8 -8
  129. package/src/host/types.ts +15 -7
  130. package/src/host/utils.ts +1 -1
  131. package/src/href-client.ts +65 -45
  132. package/src/index.rsc.ts +133 -21
  133. package/src/index.ts +164 -52
  134. package/src/internal-debug.ts +11 -0
  135. package/src/loader.rsc.ts +25 -143
  136. package/src/loader.ts +27 -10
  137. package/src/network-error-thrower.tsx +3 -1
  138. package/src/outlet-provider.tsx +45 -0
  139. package/src/prerender/param-hash.ts +4 -2
  140. package/src/prerender/store.ts +158 -13
  141. package/src/prerender.ts +333 -26
  142. package/src/reverse.ts +184 -121
  143. package/src/root-error-boundary.tsx +41 -29
  144. package/src/route-content-wrapper.tsx +7 -4
  145. package/src/route-definition/dsl-helpers.ts +934 -0
  146. package/src/route-definition/helper-factories.ts +200 -0
  147. package/src/route-definition/helpers-types.ts +430 -0
  148. package/src/route-definition/index.ts +52 -0
  149. package/src/route-definition/redirect.ts +93 -0
  150. package/src/route-definition.ts +1 -1431
  151. package/src/route-map-builder.ts +156 -123
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +48 -9
  154. package/src/router/content-negotiation.ts +116 -0
  155. package/src/router/debug-manifest.ts +72 -0
  156. package/src/router/error-handling.ts +9 -9
  157. package/src/router/find-match.ts +158 -0
  158. package/src/router/handler-context.ts +374 -81
  159. package/src/router/intercept-resolution.ts +24 -16
  160. package/src/router/lazy-includes.ts +234 -0
  161. package/src/router/loader-resolution.ts +215 -122
  162. package/src/router/logging.ts +248 -0
  163. package/src/router/manifest.ts +83 -32
  164. package/src/router/match-api.ts +118 -119
  165. package/src/router/match-context.ts +4 -2
  166. package/src/router/match-handlers.ts +440 -0
  167. package/src/router/match-middleware/background-revalidation.ts +80 -93
  168. package/src/router/match-middleware/cache-lookup.ts +336 -84
  169. package/src/router/match-middleware/cache-store.ts +43 -24
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -20
  171. package/src/router/match-middleware/segment-resolution.ts +16 -8
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +34 -28
  174. package/src/router/metrics.ts +235 -15
  175. package/src/router/middleware-cookies.ts +55 -0
  176. package/src/router/middleware-types.ts +222 -0
  177. package/src/router/middleware.ts +324 -367
  178. package/src/router/pattern-matching.ts +197 -41
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +137 -38
  182. package/src/router/router-context.ts +36 -21
  183. package/src/router/router-interfaces.ts +452 -0
  184. package/src/router/router-options.ts +592 -0
  185. package/src/router/router-registry.ts +24 -0
  186. package/src/router/segment-resolution/fresh.ts +570 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +198 -0
  189. package/src/router/segment-resolution/revalidation.ts +1239 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1315
  192. package/src/router/segment-wrappers.ts +289 -0
  193. package/src/router/telemetry-otel.ts +299 -0
  194. package/src/router/telemetry.ts +300 -0
  195. package/src/router/timeout.ts +148 -0
  196. package/src/router/trie-matching.ts +96 -29
  197. package/src/router/types.ts +16 -9
  198. package/src/router.ts +590 -1983
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +661 -1015
  201. package/src/rsc/helpers.ts +140 -6
  202. package/src/rsc/index.ts +0 -20
  203. package/src/rsc/loader-fetch.ts +209 -0
  204. package/src/rsc/manifest-init.ts +86 -0
  205. package/src/rsc/nonce.ts +14 -0
  206. package/src/rsc/origin-guard.ts +141 -0
  207. package/src/rsc/progressive-enhancement.ts +379 -0
  208. package/src/rsc/response-error.ts +37 -0
  209. package/src/rsc/response-route-handler.ts +347 -0
  210. package/src/rsc/rsc-rendering.ts +237 -0
  211. package/src/rsc/runtime-warnings.ts +42 -0
  212. package/src/rsc/server-action.ts +348 -0
  213. package/src/rsc/ssr-setup.ts +128 -0
  214. package/src/rsc/types.ts +38 -11
  215. package/src/search-params.ts +230 -0
  216. package/src/segment-system.tsx +25 -13
  217. package/src/server/context.ts +173 -48
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +37 -0
  220. package/src/server/handle-store.ts +94 -15
  221. package/src/server/loader-registry.ts +15 -56
  222. package/src/server/request-context.ts +430 -70
  223. package/src/server.ts +35 -155
  224. package/src/ssr/index.tsx +100 -31
  225. package/src/static-handler.ts +114 -0
  226. package/src/theme/ThemeProvider.tsx +21 -15
  227. package/src/theme/ThemeScript.tsx +5 -5
  228. package/src/theme/constants.ts +5 -2
  229. package/src/theme/index.ts +4 -14
  230. package/src/theme/theme-context.ts +4 -30
  231. package/src/theme/theme-script.ts +21 -18
  232. package/src/types/boundaries.ts +158 -0
  233. package/src/types/cache-types.ts +198 -0
  234. package/src/types/error-types.ts +192 -0
  235. package/src/types/global-namespace.ts +100 -0
  236. package/src/types/handler-context.ts +687 -0
  237. package/src/types/index.ts +88 -0
  238. package/src/types/loader-types.ts +183 -0
  239. package/src/types/route-config.ts +170 -0
  240. package/src/types/route-entry.ts +102 -0
  241. package/src/types/segments.ts +148 -0
  242. package/src/types.ts +1 -1757
  243. package/src/urls/include-helper.ts +197 -0
  244. package/src/urls/index.ts +53 -0
  245. package/src/urls/path-helper-types.ts +339 -0
  246. package/src/urls/path-helper.ts +329 -0
  247. package/src/urls/pattern-types.ts +95 -0
  248. package/src/urls/response-types.ts +106 -0
  249. package/src/urls/type-extraction.ts +372 -0
  250. package/src/urls/urls-function.ts +98 -0
  251. package/src/urls.ts +1 -1282
  252. package/src/use-loader.tsx +85 -77
  253. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  254. package/src/vite/discovery/discover-routers.ts +344 -0
  255. package/src/vite/discovery/prerender-collection.ts +385 -0
  256. package/src/vite/discovery/route-types-writer.ts +258 -0
  257. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  258. package/src/vite/discovery/state.ts +110 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -1963
  261. package/src/vite/plugin-types.ts +131 -0
  262. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  263. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  264. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  265. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -51
  266. package/src/vite/plugins/expose-id-utils.ts +287 -0
  267. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  268. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  269. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  270. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  271. package/src/vite/plugins/expose-ids/types.ts +45 -0
  272. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  273. package/src/vite/plugins/refresh-cmd.ts +65 -0
  274. package/src/vite/plugins/use-cache-transform.ts +323 -0
  275. package/src/vite/plugins/version-injector.ts +83 -0
  276. package/src/vite/plugins/version-plugin.ts +254 -0
  277. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  278. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  279. package/src/vite/rango.ts +510 -0
  280. package/src/vite/router-discovery.ts +785 -0
  281. package/src/vite/utils/ast-handler-extract.ts +517 -0
  282. package/src/vite/utils/banner.ts +36 -0
  283. package/src/vite/utils/bundle-analysis.ts +137 -0
  284. package/src/vite/utils/manifest-utils.ts +70 -0
  285. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  286. package/src/vite/utils/prerender-utils.ts +189 -0
  287. package/src/vite/utils/shared-utils.ts +169 -0
  288. package/CLAUDE.md +0 -43
  289. package/src/browser/lru-cache.ts +0 -69
  290. package/src/browser/request-controller.ts +0 -164
  291. package/src/cache/memory-store.ts +0 -253
  292. package/src/href-context.ts +0 -33
  293. package/src/router.gen.ts +0 -6
  294. package/src/urls.gen.ts +0 -8
  295. package/src/vite/expose-handle-id.ts +0 -209
  296. package/src/vite/expose-loader-id.ts +0 -426
  297. package/src/vite/expose-location-state-id.ts +0 -177
  298. package/src/vite/expose-prerender-handler-id.ts +0 -429
  299. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,248 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
3
+
4
+ // -- Revalidation trace types --
5
+
6
+ export interface RevalidationTraceEntry {
7
+ segmentId: string;
8
+ segmentType: string;
9
+ belongsToRoute: boolean;
10
+ source:
11
+ | "segment-resolution"
12
+ | "cache-hit"
13
+ | "loader"
14
+ | "parallel"
15
+ | "orphan-layout";
16
+ defaultShouldRevalidate: boolean;
17
+ finalShouldRevalidate: boolean;
18
+ reason: string;
19
+ customRevalidators?: number;
20
+ }
21
+
22
+ export interface RevalidationTraceMeta {
23
+ method: string;
24
+ prevUrl: string;
25
+ nextUrl: string;
26
+ routeKey: string;
27
+ isAction: boolean;
28
+ stale?: boolean;
29
+ }
30
+
31
+ export interface RevalidationTrace {
32
+ meta: RevalidationTraceMeta;
33
+ entries: RevalidationTraceEntry[];
34
+ }
35
+
36
+ // -- Log context --
37
+
38
+ interface RouterLogContext {
39
+ requestId: string;
40
+ transactionId: string;
41
+ depth: number;
42
+ revalidationTrace?: RevalidationTrace;
43
+ }
44
+
45
+ interface RouterLogOptions {
46
+ request: Request;
47
+ transaction: string;
48
+ }
49
+
50
+ interface LogDetails {
51
+ [key: string]: unknown;
52
+ }
53
+
54
+ const routerLogContext = new AsyncLocalStorage<RouterLogContext>();
55
+ const requestIds = new WeakMap<Request, string>();
56
+
57
+ let requestCounter = 0;
58
+ let transactionCounter = 0;
59
+
60
+ function nextId(prefix: string, counter: number): string {
61
+ return `${prefix}${counter.toString(36)}`;
62
+ }
63
+
64
+ function getHeaderRequestId(request: Request): string | null {
65
+ const candidate =
66
+ request.headers.get("x-rsc-router-request-id") ??
67
+ request.headers.get("x-request-id") ??
68
+ request.headers.get("cf-ray");
69
+ if (!candidate) return null;
70
+ const trimmed = candidate.trim();
71
+ return trimmed.length > 0 ? trimmed : null;
72
+ }
73
+
74
+ function getOrCreateRequestId(request: Request): string {
75
+ const existing = requestIds.get(request);
76
+ if (existing) return existing;
77
+
78
+ const fromHeaders = getHeaderRequestId(request);
79
+ if (fromHeaders) {
80
+ requestIds.set(request, fromHeaders);
81
+ return fromHeaders;
82
+ }
83
+
84
+ requestCounter += 1;
85
+ const generated = nextId("req-", requestCounter);
86
+ requestIds.set(request, generated);
87
+ return generated;
88
+ }
89
+
90
+ export function runWithRouterLogContext<T>(
91
+ options: RouterLogOptions,
92
+ fn: () => T,
93
+ ): T {
94
+ if (!INTERNAL_RANGO_DEBUG) {
95
+ return fn();
96
+ }
97
+
98
+ const requestId = getOrCreateRequestId(options.request);
99
+ transactionCounter += 1;
100
+ const transactionId = `${options.transaction}-${nextId("tx-", transactionCounter)}`;
101
+
102
+ return routerLogContext.run(
103
+ {
104
+ requestId,
105
+ transactionId,
106
+ depth: 0,
107
+ },
108
+ fn,
109
+ );
110
+ }
111
+
112
+ export function withRouterLogScope<T>(
113
+ label: string,
114
+ fn: () => Promise<T>,
115
+ ): Promise<T>;
116
+ export function withRouterLogScope<T>(label: string, fn: () => T): T;
117
+ export function withRouterLogScope<T>(
118
+ label: string,
119
+ fn: () => Promise<T> | T,
120
+ ): Promise<T> | T {
121
+ const ctx = routerLogContext.getStore();
122
+ if (!INTERNAL_RANGO_DEBUG || !ctx) {
123
+ return fn();
124
+ }
125
+
126
+ debugLog(label, "start");
127
+
128
+ return routerLogContext.run({ ...ctx, depth: ctx.depth + 1 }, () => {
129
+ try {
130
+ const result = fn();
131
+ if (result && typeof (result as Promise<T>).then === "function") {
132
+ return (result as Promise<T>).then(
133
+ (value) => {
134
+ debugLog(label, "end");
135
+ return value;
136
+ },
137
+ (error) => {
138
+ debugLog(label, "error", { error: String(error) });
139
+ throw error;
140
+ },
141
+ );
142
+ }
143
+ debugLog(label, "end");
144
+ return result;
145
+ } catch (error) {
146
+ debugLog(label, "error", { error: String(error) });
147
+ throw error;
148
+ }
149
+ });
150
+ }
151
+
152
+ export function isRouterDebugEnabled(): boolean {
153
+ return INTERNAL_RANGO_DEBUG && !!routerLogContext.getStore();
154
+ }
155
+
156
+ function formatPrefix(scope: string): string {
157
+ const ctx = routerLogContext.getStore();
158
+ if (!ctx) return `[Router][${scope}]`;
159
+ const indent = " ".repeat(ctx.depth);
160
+ return `[Router][req:${ctx.requestId}][tx:${ctx.transactionId}] ${indent}[${scope}]`;
161
+ }
162
+
163
+ export function debugLog(
164
+ scope: string,
165
+ message: string,
166
+ details?: LogDetails,
167
+ ): void {
168
+ if (!isRouterDebugEnabled()) return;
169
+
170
+ const prefix = formatPrefix(scope);
171
+ if (details) {
172
+ console.log(`${prefix} ${message}`, details);
173
+ return;
174
+ }
175
+
176
+ console.log(`${prefix} ${message}`);
177
+ }
178
+
179
+ export function debugWarn(
180
+ scope: string,
181
+ message: string,
182
+ details?: LogDetails,
183
+ ): void {
184
+ if (!isRouterDebugEnabled()) return;
185
+
186
+ const prefix = formatPrefix(scope);
187
+ if (details) {
188
+ console.warn(`${prefix} ${message}`, details);
189
+ return;
190
+ }
191
+
192
+ console.warn(`${prefix} ${message}`);
193
+ }
194
+
195
+ // -- Revalidation trace helpers --
196
+
197
+ export function isTraceActive(): boolean {
198
+ if (!INTERNAL_RANGO_DEBUG) return false;
199
+ const ctx = routerLogContext.getStore();
200
+ return !!ctx?.revalidationTrace;
201
+ }
202
+
203
+ export function startRevalidationTrace(meta: RevalidationTraceMeta): void {
204
+ const ctx = routerLogContext.getStore();
205
+ if (!ctx || !INTERNAL_RANGO_DEBUG) return;
206
+ ctx.revalidationTrace = { meta, entries: [] };
207
+ }
208
+
209
+ export function pushRevalidationTraceEntry(
210
+ entry: RevalidationTraceEntry,
211
+ ): void {
212
+ const ctx = routerLogContext.getStore();
213
+ if (!ctx?.revalidationTrace) return;
214
+ ctx.revalidationTrace.entries.push(entry);
215
+ }
216
+
217
+ export function flushRevalidationTrace(): RevalidationTrace | null {
218
+ const ctx = routerLogContext.getStore();
219
+ if (!ctx?.revalidationTrace) return null;
220
+ const trace = ctx.revalidationTrace;
221
+ ctx.revalidationTrace = undefined;
222
+
223
+ if (trace.entries.length === 0) return trace;
224
+
225
+ const revalidated = trace.entries.filter((e) => e.finalShouldRevalidate);
226
+ const skipped = trace.entries.filter((e) => !e.finalShouldRevalidate);
227
+
228
+ debugLog("revalidation-trace", "flush", {
229
+ method: trace.meta.method,
230
+ routeKey: trace.meta.routeKey,
231
+ isAction: trace.meta.isAction,
232
+ stale: trace.meta.stale,
233
+ prevUrl: trace.meta.prevUrl,
234
+ nextUrl: trace.meta.nextUrl,
235
+ total: trace.entries.length,
236
+ revalidated: revalidated.length,
237
+ skipped: skipped.length,
238
+ entries: trace.entries.map((e) => ({
239
+ segmentId: e.segmentId,
240
+ type: e.segmentType,
241
+ source: e.source,
242
+ revalidate: e.finalShouldRevalidate,
243
+ reason: e.reason,
244
+ })),
245
+ });
246
+
247
+ return trace;
248
+ }
@@ -6,7 +6,12 @@
6
6
 
7
7
  import { invariant, RouteNotFoundError } from "../errors";
8
8
  import { createRouteHelpers } from "../route-definition";
9
- import { getContext, runWithPrefixes, type EntryData, type MetricsStore } from "../server/context";
9
+ import {
10
+ getContext,
11
+ runWithPrefixes,
12
+ type EntryData,
13
+ type MetricsStore,
14
+ } from "../server/context";
10
15
  import MapRootLayout from "../server/root-layout";
11
16
  import type { RouteEntry } from "../types";
12
17
  import type { UrlPatterns } from "../urls";
@@ -60,7 +65,7 @@ export async function loadManifest(
60
65
  const mountIndex = entry.mountIndex;
61
66
 
62
67
  // Check module-level cache (persists across requests within same isolate)
63
- const cacheKey = `${VERSION}:${mountIndex ?? ''}:${routeKey}:${isSSR ? 1 : 0}`;
68
+ const cacheKey = `${VERSION}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
64
69
  const cached = manifestModuleCache.get(cacheKey);
65
70
  if (cached) {
66
71
  const cacheStart = performance.now();
@@ -100,14 +105,43 @@ export async function loadManifest(
100
105
 
101
106
  try {
102
107
  // Include mountIndex in namespace to ensure unique cache keys per mount
103
- const namespaceWithMount = mountIndex !== undefined
104
- ? `#router.M${mountIndex}`
105
- : "#router";
108
+ const namespaceWithMount =
109
+ mountIndex !== undefined ? `#router.M${mountIndex}` : "#router";
106
110
 
107
111
  // For lazy entries, use the captured parent from include() context
108
112
  // This ensures routes are registered under the correct layout hierarchy
109
- const lazyContext = entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
110
- const parentForContext = lazyContext?.parent as EntryData | null ?? Store.parent;
113
+ const lazyContext =
114
+ entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
115
+ const parentForContext =
116
+ (lazyContext?.parent as EntryData | null) ?? Store.parent;
117
+
118
+ // For lazy entries, merge captured counters from include() so the
119
+ // handler's entries get shortCode indices after sibling entries that
120
+ // were created during pattern extraction. This prevents shortCode
121
+ // collisions between lazy and non-lazy entries under the same parent
122
+ // (e.g., ArticlesLayout and BlogLayout both under NavLayout).
123
+ if (lazyContext && (lazyContext as any).counters) {
124
+ const captured = (lazyContext as any).counters as Record<string, number>;
125
+ for (const [key, value] of Object.entries(captured)) {
126
+ Store.counters[key] = Math.max(Store.counters[key] ?? 0, value);
127
+ }
128
+ }
129
+
130
+ // Propagate cache profiles for DSL-time cache("profileName") resolution.
131
+ // Non-lazy entries carry profiles directly; lazy entries carry them
132
+ // in the captured lazyContext from include() time.
133
+ const entryProfiles =
134
+ entry.cacheProfiles ?? (lazyContext as any)?.cacheProfiles;
135
+ if (entryProfiles) {
136
+ Store.cacheProfiles = entryProfiles;
137
+ }
138
+
139
+ // Propagate rootScoped from lazyContext so that routes inside
140
+ // nested { name: "sub" } under { name: "" } keep inherited root scope
141
+ // when the manifest is rebuilt on each request.
142
+ if (lazyContext && (lazyContext as any).rootScoped !== undefined) {
143
+ Store.rootScoped = (lazyContext as any).rootScoped;
144
+ }
111
145
 
112
146
  const handlerExecStart = performance.now();
113
147
  const useItems = await getContext().runWithStore(
@@ -118,25 +152,23 @@ export async function loadManifest(
118
152
  // Create helpers for lazy-loaded handlers that need them
119
153
  const helpers = createRouteHelpers();
120
154
 
121
- // For lazy entries, use lazyPatterns.handler() with proper prefixes
155
+ // For lazy entries, use lazyPatterns.handler() with proper prefixes.
156
+ // Do NOT wrap in MapRootLayout here: the captured parent chain from
157
+ // pattern extraction already includes the synthetic MapRootLayout
158
+ // parent, so adding another would create an extra level that does
159
+ // not exist in the non-lazy (root handler) path and would produce
160
+ // mismatched shortCodes.
122
161
  if (entry.lazy && entry.lazyPatterns) {
123
162
  const lazyPatterns = entry.lazyPatterns as UrlPatterns<any>;
124
163
  const includePrefix = (entry as any)._lazyPrefix || "";
125
164
  const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
126
165
 
127
- // Wrap in root layout and run with prefixes
128
- const wrappedItems = helpers.layout(MapRootLayout, () => {
129
- if (fullPrefix || lazyContext?.namePrefix) {
130
- return runWithPrefixes(
131
- fullPrefix,
132
- lazyContext?.namePrefix,
133
- () => lazyPatterns.handler()
134
- );
135
- }
136
- return lazyPatterns.handler();
137
- });
138
-
139
- return [wrappedItems].flat(3);
166
+ if (fullPrefix || lazyContext?.namePrefix) {
167
+ return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () =>
168
+ lazyPatterns.handler(),
169
+ );
170
+ }
171
+ return lazyPatterns.handler();
140
172
  }
141
173
 
142
174
  // Wrap handler execution in root layout so routes get correct parent
@@ -162,36 +194,55 @@ export async function loadManifest(
162
194
  "default" in load
163
195
  ) {
164
196
  // Promise<{ default: () => Array }> - e.g., dynamic import
165
- // Lazy-loaded handlers may need helpers (passed as optional arg)
197
+ if (typeof load.default !== "function") {
198
+ throw new Error(
199
+ `[@rangojs/router] Unsupported async handler: { default } must be a function, ` +
200
+ `got ${typeof load.default}. Use () => import('./urls') for lazy loading.`,
201
+ );
202
+ }
166
203
  return (load.default as (h?: any) => any)(helpers);
167
204
  }
168
205
  if (typeof load === "function") {
169
206
  // Promise<() => Array>
170
207
  return (load as (h?: any) => any)(helpers);
171
208
  }
172
- // Promise<Array> - direct array from async handler
173
- return load;
209
+ // Reject unsupported async handler results. Supported shapes are:
210
+ // Promise<{ default: fn }> — dynamic import
211
+ // Promise<fn> — lazy function
212
+ // Direct Promise<Array> is not supported; use a function wrapper.
213
+ throw new Error(
214
+ `[@rangojs/router] Unsupported async handler result (${typeof load}). ` +
215
+ `Lazy route handlers must resolve to a function or { default: fn }, ` +
216
+ `not a direct array. Wrap your handler: () => import('./urls') or ` +
217
+ `() => Promise.resolve((h) => [...])`,
218
+ );
174
219
  }
175
220
 
176
221
  // Inline handler - routes were registered with correct parent inside layout
177
222
  return [wrappedItems].flat(3);
178
- }
223
+ },
179
224
  );
180
225
  pushMetric?.("manifest:handler-exec", handlerExecStart);
181
226
 
182
227
  const validationStart = performance.now();
183
228
  invariant(
184
229
  useItems && useItems.length > 0,
185
- "Did not receive any handler from router.map()"
186
- );
187
- invariant(
188
- useItems.some((item: { type: string }) => item.type === "layout"),
189
- "Top-level handler must be a layout"
230
+ "Did not receive any handler from router.map()",
190
231
  );
232
+ // For non-lazy entries the root handler is wrapped in MapRootLayout,
233
+ // so the result always contains a layout item. Lazy entries run the
234
+ // included patterns handler directly (no MapRootLayout wrapper) so
235
+ // we skip this check -- the layout is in the captured parent chain.
236
+ if (!lazyContext) {
237
+ invariant(
238
+ useItems.some((item: { type: string }) => item.type === "layout"),
239
+ "Top-level handler must be a layout",
240
+ );
241
+ }
191
242
 
192
243
  invariant(
193
244
  Store.manifest.has(routeKey),
194
- `Route must be registered for ${routeKey}`
245
+ `Route must be registered for ${routeKey}`,
195
246
  );
196
247
  pushMetric?.("manifest:validation", validationStart);
197
248
 
@@ -210,7 +261,7 @@ export async function loadManifest(
210
261
  routeKey,
211
262
  },
212
263
  },
213
- }
264
+ },
214
265
  );
215
266
  }
216
267
  }