@rangojs/router 0.0.0-experimental.8 → 0.0.0-experimental.80

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 (312) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +942 -4
  3. package/dist/bin/rango.js +1689 -0
  4. package/dist/vite/index.js +4960 -935
  5. package/package.json +70 -60
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +294 -0
  8. package/skills/caching/SKILL.md +93 -23
  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 +167 -0
  13. package/skills/handler-use/SKILL.md +362 -0
  14. package/skills/hooks/SKILL.md +334 -72
  15. package/skills/host-router/SKILL.md +218 -0
  16. package/skills/intercept/SKILL.md +151 -8
  17. package/skills/layout/SKILL.md +122 -3
  18. package/skills/links/SKILL.md +92 -31
  19. package/skills/loader/SKILL.md +404 -44
  20. package/skills/middleware/SKILL.md +205 -37
  21. package/skills/migrate-nextjs/SKILL.md +560 -0
  22. package/skills/migrate-react-router/SKILL.md +764 -0
  23. package/skills/mime-routes/SKILL.md +128 -0
  24. package/skills/parallel/SKILL.md +263 -1
  25. package/skills/prerender/SKILL.md +685 -0
  26. package/skills/rango/SKILL.md +87 -16
  27. package/skills/response-routes/SKILL.md +411 -0
  28. package/skills/route/SKILL.md +281 -14
  29. package/skills/router-setup/SKILL.md +210 -32
  30. package/skills/tailwind/SKILL.md +129 -0
  31. package/skills/theme/SKILL.md +9 -8
  32. package/skills/typesafety/SKILL.md +328 -89
  33. package/skills/use-cache/SKILL.md +324 -0
  34. package/src/__internal.ts +102 -4
  35. package/src/bin/rango.ts +321 -0
  36. package/src/browser/action-coordinator.ts +97 -0
  37. package/src/browser/action-response-classifier.ts +99 -0
  38. package/src/browser/app-version.ts +14 -0
  39. package/src/browser/event-controller.ts +92 -64
  40. package/src/browser/history-state.ts +80 -0
  41. package/src/browser/intercept-utils.ts +52 -0
  42. package/src/browser/link-interceptor.ts +24 -4
  43. package/src/browser/logging.ts +55 -0
  44. package/src/browser/merge-segment-loaders.ts +20 -12
  45. package/src/browser/navigation-bridge.ts +317 -560
  46. package/src/browser/navigation-client.ts +206 -68
  47. package/src/browser/navigation-store.ts +73 -55
  48. package/src/browser/navigation-transaction.ts +297 -0
  49. package/src/browser/network-error-handler.ts +61 -0
  50. package/src/browser/partial-update.ts +343 -316
  51. package/src/browser/prefetch/cache.ts +216 -0
  52. package/src/browser/prefetch/fetch.ts +206 -0
  53. package/src/browser/prefetch/observer.ts +65 -0
  54. package/src/browser/prefetch/policy.ts +48 -0
  55. package/src/browser/prefetch/queue.ts +160 -0
  56. package/src/browser/prefetch/resource-ready.ts +77 -0
  57. package/src/browser/rango-state.ts +112 -0
  58. package/src/browser/react/Link.tsx +253 -74
  59. package/src/browser/react/NavigationProvider.tsx +87 -11
  60. package/src/browser/react/context.ts +11 -0
  61. package/src/browser/react/filter-segment-order.ts +11 -0
  62. package/src/browser/react/index.ts +12 -12
  63. package/src/browser/react/location-state-shared.ts +95 -53
  64. package/src/browser/react/location-state.ts +60 -15
  65. package/src/browser/react/mount-context.ts +6 -1
  66. package/src/browser/react/nonce-context.ts +23 -0
  67. package/src/browser/react/shallow-equal.ts +27 -0
  68. package/src/browser/react/use-action.ts +29 -51
  69. package/src/browser/react/use-client-cache.ts +5 -3
  70. package/src/browser/react/use-handle.ts +30 -126
  71. package/src/browser/react/use-href.tsx +2 -2
  72. package/src/browser/react/use-link-status.ts +6 -5
  73. package/src/browser/react/use-navigation.ts +44 -65
  74. package/src/browser/react/use-params.ts +65 -0
  75. package/src/browser/react/use-pathname.ts +47 -0
  76. package/src/browser/react/use-router.ts +76 -0
  77. package/src/browser/react/use-search-params.ts +56 -0
  78. package/src/browser/react/use-segments.ts +80 -97
  79. package/src/browser/response-adapter.ts +73 -0
  80. package/src/browser/rsc-router.tsx +214 -58
  81. package/src/browser/scroll-restoration.ts +127 -52
  82. package/src/browser/segment-reconciler.ts +243 -0
  83. package/src/browser/segment-structure-assert.ts +16 -0
  84. package/src/browser/server-action-bridge.ts +510 -603
  85. package/src/browser/shallow.ts +6 -1
  86. package/src/browser/types.ts +141 -48
  87. package/src/browser/validate-redirect-origin.ts +29 -0
  88. package/src/build/generate-manifest.ts +235 -24
  89. package/src/build/generate-route-types.ts +39 -0
  90. package/src/build/index.ts +13 -0
  91. package/src/build/route-trie.ts +291 -0
  92. package/src/build/route-types/ast-helpers.ts +25 -0
  93. package/src/build/route-types/ast-route-extraction.ts +98 -0
  94. package/src/build/route-types/codegen.ts +102 -0
  95. package/src/build/route-types/include-resolution.ts +418 -0
  96. package/src/build/route-types/param-extraction.ts +48 -0
  97. package/src/build/route-types/per-module-writer.ts +128 -0
  98. package/src/build/route-types/router-processing.ts +618 -0
  99. package/src/build/route-types/scan-filter.ts +85 -0
  100. package/src/build/runtime-discovery.ts +231 -0
  101. package/src/cache/background-task.ts +34 -0
  102. package/src/cache/cache-key-utils.ts +44 -0
  103. package/src/cache/cache-policy.ts +125 -0
  104. package/src/cache/cache-runtime.ts +342 -0
  105. package/src/cache/cache-scope.ts +167 -309
  106. package/src/cache/cf/cf-cache-store.ts +571 -17
  107. package/src/cache/cf/index.ts +13 -3
  108. package/src/cache/document-cache.ts +116 -77
  109. package/src/cache/handle-capture.ts +81 -0
  110. package/src/cache/handle-snapshot.ts +41 -0
  111. package/src/cache/index.ts +1 -15
  112. package/src/cache/memory-segment-store.ts +191 -13
  113. package/src/cache/profile-registry.ts +73 -0
  114. package/src/cache/read-through-swr.ts +134 -0
  115. package/src/cache/segment-codec.ts +256 -0
  116. package/src/cache/taint.ts +153 -0
  117. package/src/cache/types.ts +72 -122
  118. package/src/client.rsc.tsx +3 -1
  119. package/src/client.tsx +135 -301
  120. package/src/component-utils.ts +4 -4
  121. package/src/components/DefaultDocument.tsx +5 -1
  122. package/src/context-var.ts +156 -0
  123. package/src/debug.ts +19 -9
  124. package/src/errors.ts +108 -2
  125. package/src/handle.ts +55 -29
  126. package/src/handles/MetaTags.tsx +73 -20
  127. package/src/handles/breadcrumbs.ts +66 -0
  128. package/src/handles/index.ts +1 -0
  129. package/src/handles/meta.ts +30 -13
  130. package/src/host/cookie-handler.ts +21 -15
  131. package/src/host/errors.ts +8 -8
  132. package/src/host/index.ts +4 -7
  133. package/src/host/pattern-matcher.ts +27 -27
  134. package/src/host/router.ts +61 -39
  135. package/src/host/testing.ts +8 -8
  136. package/src/host/types.ts +15 -7
  137. package/src/host/utils.ts +1 -1
  138. package/src/href-client.ts +119 -29
  139. package/src/index.rsc.ts +155 -19
  140. package/src/index.ts +251 -30
  141. package/src/internal-debug.ts +11 -0
  142. package/src/loader.rsc.ts +26 -157
  143. package/src/loader.ts +27 -10
  144. package/src/network-error-thrower.tsx +3 -1
  145. package/src/outlet-provider.tsx +45 -0
  146. package/src/prerender/param-hash.ts +37 -0
  147. package/src/prerender/store.ts +186 -0
  148. package/src/prerender.ts +524 -0
  149. package/src/reverse.ts +354 -0
  150. package/src/root-error-boundary.tsx +41 -29
  151. package/src/route-content-wrapper.tsx +7 -4
  152. package/src/route-definition/dsl-helpers.ts +1121 -0
  153. package/src/route-definition/helper-factories.ts +200 -0
  154. package/src/route-definition/helpers-types.ts +478 -0
  155. package/src/route-definition/index.ts +55 -0
  156. package/src/route-definition/redirect.ts +101 -0
  157. package/src/route-definition/resolve-handler-use.ts +149 -0
  158. package/src/route-definition.ts +1 -1428
  159. package/src/route-map-builder.ts +217 -123
  160. package/src/route-name.ts +53 -0
  161. package/src/route-types.ts +77 -8
  162. package/src/router/content-negotiation.ts +215 -0
  163. package/src/router/debug-manifest.ts +72 -0
  164. package/src/router/error-handling.ts +9 -9
  165. package/src/router/find-match.ts +160 -0
  166. package/src/router/handler-context.ts +438 -86
  167. package/src/router/intercept-resolution.ts +402 -0
  168. package/src/router/lazy-includes.ts +237 -0
  169. package/src/router/loader-resolution.ts +356 -128
  170. package/src/router/logging.ts +251 -0
  171. package/src/router/manifest.ts +163 -35
  172. package/src/router/match-api.ts +555 -0
  173. package/src/router/match-context.ts +5 -3
  174. package/src/router/match-handlers.ts +440 -0
  175. package/src/router/match-middleware/background-revalidation.ts +108 -93
  176. package/src/router/match-middleware/cache-lookup.ts +460 -10
  177. package/src/router/match-middleware/cache-store.ts +98 -26
  178. package/src/router/match-middleware/intercept-resolution.ts +57 -17
  179. package/src/router/match-middleware/segment-resolution.ts +80 -6
  180. package/src/router/match-pipelines.ts +10 -45
  181. package/src/router/match-result.ts +135 -35
  182. package/src/router/metrics.ts +240 -15
  183. package/src/router/middleware-cookies.ts +55 -0
  184. package/src/router/middleware-types.ts +220 -0
  185. package/src/router/middleware.ts +324 -369
  186. package/src/router/navigation-snapshot.ts +182 -0
  187. package/src/router/pattern-matching.ts +211 -43
  188. package/src/router/prerender-match.ts +502 -0
  189. package/src/router/preview-match.ts +98 -0
  190. package/src/router/request-classification.ts +310 -0
  191. package/src/router/revalidation.ts +137 -38
  192. package/src/router/route-snapshot.ts +245 -0
  193. package/src/router/router-context.ts +41 -21
  194. package/src/router/router-interfaces.ts +484 -0
  195. package/src/router/router-options.ts +618 -0
  196. package/src/router/router-registry.ts +24 -0
  197. package/src/router/segment-resolution/fresh.ts +748 -0
  198. package/src/router/segment-resolution/helpers.ts +268 -0
  199. package/src/router/segment-resolution/loader-cache.ts +199 -0
  200. package/src/router/segment-resolution/revalidation.ts +1379 -0
  201. package/src/router/segment-resolution/static-store.ts +67 -0
  202. package/src/router/segment-resolution.ts +21 -0
  203. package/src/router/segment-wrappers.ts +291 -0
  204. package/src/router/telemetry-otel.ts +299 -0
  205. package/src/router/telemetry.ts +300 -0
  206. package/src/router/timeout.ts +148 -0
  207. package/src/router/trie-matching.ts +239 -0
  208. package/src/router/types.ts +78 -3
  209. package/src/router.ts +740 -4252
  210. package/src/rsc/handler-context.ts +45 -0
  211. package/src/rsc/handler.ts +907 -797
  212. package/src/rsc/helpers.ts +140 -6
  213. package/src/rsc/index.ts +0 -20
  214. package/src/rsc/loader-fetch.ts +229 -0
  215. package/src/rsc/manifest-init.ts +90 -0
  216. package/src/rsc/nonce.ts +14 -0
  217. package/src/rsc/origin-guard.ts +141 -0
  218. package/src/rsc/progressive-enhancement.ts +391 -0
  219. package/src/rsc/response-error.ts +37 -0
  220. package/src/rsc/response-route-handler.ts +347 -0
  221. package/src/rsc/rsc-rendering.ts +246 -0
  222. package/src/rsc/runtime-warnings.ts +42 -0
  223. package/src/rsc/server-action.ts +356 -0
  224. package/src/rsc/ssr-setup.ts +128 -0
  225. package/src/rsc/types.ts +46 -11
  226. package/src/search-params.ts +230 -0
  227. package/src/segment-content-promise.ts +67 -0
  228. package/src/segment-loader-promise.ts +122 -0
  229. package/src/segment-system.tsx +134 -36
  230. package/src/server/context.ts +341 -61
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +113 -15
  234. package/src/server/loader-registry.ts +24 -64
  235. package/src/server/request-context.ts +607 -81
  236. package/src/server.ts +35 -130
  237. package/src/ssr/index.tsx +103 -30
  238. package/src/static-handler.ts +126 -0
  239. package/src/theme/ThemeProvider.tsx +21 -15
  240. package/src/theme/ThemeScript.tsx +5 -5
  241. package/src/theme/constants.ts +5 -2
  242. package/src/theme/index.ts +4 -14
  243. package/src/theme/theme-context.ts +4 -30
  244. package/src/theme/theme-script.ts +21 -18
  245. package/src/types/boundaries.ts +158 -0
  246. package/src/types/cache-types.ts +198 -0
  247. package/src/types/error-types.ts +192 -0
  248. package/src/types/global-namespace.ts +100 -0
  249. package/src/types/handler-context.ts +791 -0
  250. package/src/types/index.ts +88 -0
  251. package/src/types/loader-types.ts +210 -0
  252. package/src/types/route-config.ts +170 -0
  253. package/src/types/route-entry.ts +120 -0
  254. package/src/types/segments.ts +150 -0
  255. package/src/types.ts +1 -1623
  256. package/src/urls/include-helper.ts +207 -0
  257. package/src/urls/index.ts +53 -0
  258. package/src/urls/path-helper-types.ts +372 -0
  259. package/src/urls/path-helper.ts +364 -0
  260. package/src/urls/pattern-types.ts +107 -0
  261. package/src/urls/response-types.ts +116 -0
  262. package/src/urls/type-extraction.ts +372 -0
  263. package/src/urls/urls-function.ts +98 -0
  264. package/src/urls.ts +1 -802
  265. package/src/use-loader.tsx +161 -81
  266. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  267. package/src/vite/discovery/discover-routers.ts +348 -0
  268. package/src/vite/discovery/prerender-collection.ts +439 -0
  269. package/src/vite/discovery/route-types-writer.ts +258 -0
  270. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  271. package/src/vite/discovery/state.ts +117 -0
  272. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  273. package/src/vite/index.ts +15 -1133
  274. package/src/vite/plugin-types.ts +103 -0
  275. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  276. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  277. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  278. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +72 -53
  279. package/src/vite/plugins/expose-id-utils.ts +299 -0
  280. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  281. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  282. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  283. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  284. package/src/vite/plugins/expose-ids/types.ts +45 -0
  285. package/src/vite/plugins/expose-internal-ids.ts +786 -0
  286. package/src/vite/plugins/performance-tracks.ts +88 -0
  287. package/src/vite/plugins/refresh-cmd.ts +127 -0
  288. package/src/vite/plugins/use-cache-transform.ts +323 -0
  289. package/src/vite/plugins/version-injector.ts +83 -0
  290. package/src/vite/plugins/version-plugin.ts +266 -0
  291. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  292. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  293. package/src/vite/rango.ts +462 -0
  294. package/src/vite/router-discovery.ts +918 -0
  295. package/src/vite/utils/ast-handler-extract.ts +517 -0
  296. package/src/vite/utils/banner.ts +36 -0
  297. package/src/vite/utils/bundle-analysis.ts +137 -0
  298. package/src/vite/utils/manifest-utils.ts +70 -0
  299. package/src/vite/{package-resolution.ts → utils/package-resolution.ts} +25 -29
  300. package/src/vite/utils/prerender-utils.ts +221 -0
  301. package/src/vite/utils/shared-utils.ts +170 -0
  302. package/CLAUDE.md +0 -43
  303. package/src/browser/lru-cache.ts +0 -69
  304. package/src/browser/request-controller.ts +0 -164
  305. package/src/cache/memory-store.ts +0 -253
  306. package/src/href-context.ts +0 -33
  307. package/src/href.ts +0 -255
  308. package/src/server/route-manifest-cache.ts +0 -173
  309. package/src/vite/expose-handle-id.ts +0 -209
  310. package/src/vite/expose-loader-id.ts +0 -426
  311. package/src/vite/expose-location-state-id.ts +0 -177
  312. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,251 @@
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
+ | "route-handler"
17
+ | "layout-handler"
18
+ | "intercept-loader";
19
+ defaultShouldRevalidate: boolean;
20
+ finalShouldRevalidate: boolean;
21
+ reason: string;
22
+ customRevalidators?: number;
23
+ }
24
+
25
+ export interface RevalidationTraceMeta {
26
+ method: string;
27
+ prevUrl: string;
28
+ nextUrl: string;
29
+ routeKey: string;
30
+ isAction: boolean;
31
+ stale?: boolean;
32
+ }
33
+
34
+ export interface RevalidationTrace {
35
+ meta: RevalidationTraceMeta;
36
+ entries: RevalidationTraceEntry[];
37
+ }
38
+
39
+ // -- Log context --
40
+
41
+ interface RouterLogContext {
42
+ requestId: string;
43
+ transactionId: string;
44
+ depth: number;
45
+ revalidationTrace?: RevalidationTrace;
46
+ }
47
+
48
+ interface RouterLogOptions {
49
+ request: Request;
50
+ transaction: string;
51
+ }
52
+
53
+ interface LogDetails {
54
+ [key: string]: unknown;
55
+ }
56
+
57
+ const routerLogContext = new AsyncLocalStorage<RouterLogContext>();
58
+ const requestIds = new WeakMap<Request, string>();
59
+
60
+ let requestCounter = 0;
61
+ let transactionCounter = 0;
62
+
63
+ function nextId(prefix: string, counter: number): string {
64
+ return `${prefix}${counter.toString(36)}`;
65
+ }
66
+
67
+ function getHeaderRequestId(request: Request): string | null {
68
+ const candidate =
69
+ request.headers.get("x-rsc-router-request-id") ??
70
+ request.headers.get("x-request-id") ??
71
+ request.headers.get("cf-ray");
72
+ if (!candidate) return null;
73
+ const trimmed = candidate.trim();
74
+ return trimmed.length > 0 ? trimmed : null;
75
+ }
76
+
77
+ export function getOrCreateRequestId(request: Request): string {
78
+ const existing = requestIds.get(request);
79
+ if (existing) return existing;
80
+
81
+ const fromHeaders = getHeaderRequestId(request);
82
+ if (fromHeaders) {
83
+ requestIds.set(request, fromHeaders);
84
+ return fromHeaders;
85
+ }
86
+
87
+ requestCounter += 1;
88
+ const generated = nextId("req-", requestCounter);
89
+ requestIds.set(request, generated);
90
+ return generated;
91
+ }
92
+
93
+ export function runWithRouterLogContext<T>(
94
+ options: RouterLogOptions,
95
+ fn: () => T,
96
+ ): T {
97
+ if (!INTERNAL_RANGO_DEBUG) {
98
+ return fn();
99
+ }
100
+
101
+ const requestId = getOrCreateRequestId(options.request);
102
+ transactionCounter += 1;
103
+ const transactionId = `${options.transaction}-${nextId("tx-", transactionCounter)}`;
104
+
105
+ return routerLogContext.run(
106
+ {
107
+ requestId,
108
+ transactionId,
109
+ depth: 0,
110
+ },
111
+ fn,
112
+ );
113
+ }
114
+
115
+ export function withRouterLogScope<T>(
116
+ label: string,
117
+ fn: () => Promise<T>,
118
+ ): Promise<T>;
119
+ export function withRouterLogScope<T>(label: string, fn: () => T): T;
120
+ export function withRouterLogScope<T>(
121
+ label: string,
122
+ fn: () => Promise<T> | T,
123
+ ): Promise<T> | T {
124
+ const ctx = routerLogContext.getStore();
125
+ if (!INTERNAL_RANGO_DEBUG || !ctx) {
126
+ return fn();
127
+ }
128
+
129
+ debugLog(label, "start");
130
+
131
+ return routerLogContext.run({ ...ctx, depth: ctx.depth + 1 }, () => {
132
+ try {
133
+ const result = fn();
134
+ if (result && typeof (result as Promise<T>).then === "function") {
135
+ return (result as Promise<T>).then(
136
+ (value) => {
137
+ debugLog(label, "end");
138
+ return value;
139
+ },
140
+ (error) => {
141
+ debugLog(label, "error", { error: String(error) });
142
+ throw error;
143
+ },
144
+ );
145
+ }
146
+ debugLog(label, "end");
147
+ return result;
148
+ } catch (error) {
149
+ debugLog(label, "error", { error: String(error) });
150
+ throw error;
151
+ }
152
+ });
153
+ }
154
+
155
+ export function isRouterDebugEnabled(): boolean {
156
+ return INTERNAL_RANGO_DEBUG && !!routerLogContext.getStore();
157
+ }
158
+
159
+ function formatPrefix(scope: string): string {
160
+ const ctx = routerLogContext.getStore();
161
+ if (!ctx) return `[Router][${scope}]`;
162
+ const indent = " ".repeat(ctx.depth);
163
+ return `[Router][req:${ctx.requestId}][tx:${ctx.transactionId}] ${indent}[${scope}]`;
164
+ }
165
+
166
+ export function debugLog(
167
+ scope: string,
168
+ message: string,
169
+ details?: LogDetails,
170
+ ): void {
171
+ if (!isRouterDebugEnabled()) return;
172
+
173
+ const prefix = formatPrefix(scope);
174
+ if (details) {
175
+ console.log(`${prefix} ${message}`, details);
176
+ return;
177
+ }
178
+
179
+ console.log(`${prefix} ${message}`);
180
+ }
181
+
182
+ export function debugWarn(
183
+ scope: string,
184
+ message: string,
185
+ details?: LogDetails,
186
+ ): void {
187
+ if (!isRouterDebugEnabled()) return;
188
+
189
+ const prefix = formatPrefix(scope);
190
+ if (details) {
191
+ console.warn(`${prefix} ${message}`, details);
192
+ return;
193
+ }
194
+
195
+ console.warn(`${prefix} ${message}`);
196
+ }
197
+
198
+ // -- Revalidation trace helpers --
199
+
200
+ export function isTraceActive(): boolean {
201
+ if (!INTERNAL_RANGO_DEBUG) return false;
202
+ const ctx = routerLogContext.getStore();
203
+ return !!ctx?.revalidationTrace;
204
+ }
205
+
206
+ export function startRevalidationTrace(meta: RevalidationTraceMeta): void {
207
+ const ctx = routerLogContext.getStore();
208
+ if (!ctx || !INTERNAL_RANGO_DEBUG) return;
209
+ ctx.revalidationTrace = { meta, entries: [] };
210
+ }
211
+
212
+ export function pushRevalidationTraceEntry(
213
+ entry: RevalidationTraceEntry,
214
+ ): void {
215
+ const ctx = routerLogContext.getStore();
216
+ if (!ctx?.revalidationTrace) return;
217
+ ctx.revalidationTrace.entries.push(entry);
218
+ }
219
+
220
+ export function flushRevalidationTrace(): RevalidationTrace | null {
221
+ const ctx = routerLogContext.getStore();
222
+ if (!ctx?.revalidationTrace) return null;
223
+ const trace = ctx.revalidationTrace;
224
+ ctx.revalidationTrace = undefined;
225
+
226
+ if (trace.entries.length === 0) return trace;
227
+
228
+ const revalidated = trace.entries.filter((e) => e.finalShouldRevalidate);
229
+ const skipped = trace.entries.filter((e) => !e.finalShouldRevalidate);
230
+
231
+ debugLog("revalidation-trace", "flush", {
232
+ method: trace.meta.method,
233
+ routeKey: trace.meta.routeKey,
234
+ isAction: trace.meta.isAction,
235
+ stale: trace.meta.stale,
236
+ prevUrl: trace.meta.prevUrl,
237
+ nextUrl: trace.meta.nextUrl,
238
+ total: trace.entries.length,
239
+ revalidated: revalidated.length,
240
+ skipped: skipped.length,
241
+ entries: trace.entries.map((e) => ({
242
+ segmentId: e.segmentId,
243
+ type: e.segmentType,
244
+ source: e.source,
245
+ revalidate: e.finalShouldRevalidate,
246
+ reason: e.reason,
247
+ })),
248
+ });
249
+
250
+ return trace;
251
+ }
@@ -6,27 +6,86 @@
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
+ getIsolatedLazyParent,
13
+ type EntryData,
14
+ type MetricsStore,
15
+ } from "../server/context";
10
16
  import MapRootLayout from "../server/root-layout";
11
17
  import type { RouteEntry } from "../types";
12
18
  import type { UrlPatterns } from "../urls";
19
+ import { VERSION } from "@rangojs/router:version";
20
+
21
+ // Module-level manifest cache: avoids re-executing DSL handler on every request.
22
+ // Handler execution is deterministic (components, loaders, middleware are module-level
23
+ // stable references), so the resulting EntryData tree can be safely cached and reused
24
+ // across requests within the same isolate.
25
+ //
26
+ // Cache is keyed by (VERSION, mountIndex, routeKey, isSSR). VERSION comes from the
27
+ // @rangojs/router:version virtual module which Vite invalidates on RSC module HMR.
28
+ // When VERSION changes, this module re-evaluates and the cache is recreated empty.
29
+ // Including VERSION in the key is additional defense against stale entries.
30
+ const manifestModuleCache = new Map<string, Map<string, EntryData>>();
13
31
 
14
32
  /**
15
33
  * Load manifest from route entry with AsyncLocalStorage context
16
34
  * Handles lazy imports, unwrapping, and validation
17
35
  *
18
- * Note: We don't cache manifests at the module level because includes are
19
- * lazily evaluated. Caching an incomplete manifest would cause cache misses
20
- * for routes in not-yet-evaluated includes.
36
+ * Results are cached at module level after first execution. Subsequent calls
37
+ * for the same (routeKey, isSSR) within the same isolate return cached data
38
+ * without re-executing the DSL handler.
39
+ */
40
+ /**
41
+ * Clear the module-level manifest cache.
42
+ * Called on HMR to ensure stale handler references are discarded.
21
43
  */
44
+ export function clearManifestCache(): void {
45
+ manifestModuleCache.clear();
46
+ }
47
+
22
48
  export async function loadManifest(
23
49
  entry: RouteEntry<any>,
24
50
  routeKey: string,
25
51
  path: string,
26
52
  metricsStore?: MetricsStore,
27
- isSSR?: boolean
53
+ isSSR?: boolean,
28
54
  ): Promise<EntryData> {
55
+ // Helper to push a metric entry
56
+ const pushMetric = metricsStore
57
+ ? (label: string, start: number) => {
58
+ metricsStore.metrics.push({
59
+ label,
60
+ duration: performance.now() - start,
61
+ startTime: start - metricsStore.requestStart,
62
+ });
63
+ }
64
+ : undefined;
65
+
29
66
  const mountIndex = entry.mountIndex;
67
+
68
+ // Check module-level cache (persists across requests within same isolate)
69
+ // Include routerId so multi-router setups (host routing) don't share cached
70
+ // EntryData across routers with overlapping mountIndex + routeKey combinations.
71
+ const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
72
+ const cached = manifestModuleCache.get(cacheKey);
73
+ if (cached) {
74
+ const cacheStart = performance.now();
75
+ // Set up Store for downstream consumers (segment resolution reads Store.manifest)
76
+ const Store = getContext().getOrCreateStore(routeKey);
77
+ Store.mountIndex = mountIndex;
78
+ Store.isSSR = isSSR;
79
+ if (metricsStore) Store.metrics = metricsStore;
80
+ // Restore cached manifest into Store
81
+ for (const [k, v] of cached) {
82
+ Store.manifest.set(k, v);
83
+ }
84
+ pushMetric?.("manifest:cache-hit", cacheStart);
85
+ return cached.get(routeKey)!;
86
+ }
87
+
88
+ const storeSetupStart = performance.now();
30
89
  const Store = getContext().getOrCreateStore(routeKey);
31
90
 
32
91
  // Set mount index in store for unique shortCode prefixes
@@ -40,20 +99,66 @@ export async function loadManifest(
40
99
  Store.metrics = metricsStore;
41
100
  }
42
101
 
102
+ pushMetric?.("manifest:store-setup", storeSetupStart);
103
+
43
104
  // Clear manifest before rebuilding to prevent stale entry mutations
105
+ const clearStart = performance.now();
44
106
  Store.manifest.clear();
107
+ pushMetric?.("manifest:clear", clearStart);
45
108
 
46
109
  try {
47
110
  // Include mountIndex in namespace to ensure unique cache keys per mount
48
- const namespaceWithMount = mountIndex !== undefined
49
- ? `#router.M${mountIndex}`
50
- : "#router";
111
+ const namespaceWithMount =
112
+ mountIndex !== undefined ? `#router.M${mountIndex}` : "#router";
51
113
 
52
114
  // For lazy entries, use the captured parent from include() context
53
115
  // This ensures routes are registered under the correct layout hierarchy
54
- const lazyContext = entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
55
- const parentForContext = lazyContext?.parent as EntryData | null ?? Store.parent;
116
+ const lazyContext =
117
+ entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
118
+ const parentForContext = lazyContext
119
+ ? getIsolatedLazyParent(
120
+ (lazyContext.parent as EntryData | null) ?? Store.parent,
121
+ )
122
+ : Store.parent;
123
+
124
+ // For lazy entries, merge captured counters from include() so the
125
+ // handler's entries get shortCode indices after sibling entries that
126
+ // were created during pattern extraction. This prevents shortCode
127
+ // collisions between lazy and non-lazy entries under the same parent
128
+ // (e.g., ArticlesLayout and BlogLayout both under NavLayout).
129
+ if (lazyContext?.counters) {
130
+ for (const [key, value] of Object.entries(lazyContext.counters)) {
131
+ Store.counters[key] = Math.max(Store.counters[key] ?? 0, value);
132
+ }
133
+ }
56
134
 
135
+ // Propagate cache profiles for DSL-time cache("profileName") resolution.
136
+ // Non-lazy entries carry profiles directly; lazy entries carry them
137
+ // in the captured lazyContext from include() time. Always write
138
+ // (including clearing to undefined) so a prior lazy build's profile
139
+ // map cannot leak into a later non-lazy build on the same ALS-backed
140
+ // Store — which would otherwise let cache("name") resolve a profile
141
+ // from an unrelated entry.
142
+ Store.cacheProfiles = entry.cacheProfiles ?? lazyContext?.cacheProfiles;
143
+
144
+ // Propagate rootScoped from lazyContext so that routes inside
145
+ // nested { name: "sub" } under { name: "" } keep inherited root scope
146
+ // when the manifest is rebuilt on each request. Always write
147
+ // (including clearing to undefined, which makes getRootScoped()
148
+ // return its true default) so a prior lazy build's scope cannot leak
149
+ // into a later non-lazy build on the same ALS-backed Store — which
150
+ // would otherwise mis-register plain routes as non-root-scoped and
151
+ // break dot-local reverse resolution.
152
+ Store.rootScoped = lazyContext?.rootScoped;
153
+
154
+ // Propagate includeScope from lazyContext so that direct-descendant
155
+ // shortCodes of this include use the correct scoped counter namespace
156
+ // on every manifest rebuild. Always write (including clearing to
157
+ // undefined) so a prior lazy build's scope cannot leak into a later
158
+ // non-lazy build on the same ALS-backed Store.
159
+ Store.includeScope = lazyContext?.includeScope;
160
+
161
+ const handlerExecStart = performance.now();
57
162
  const useItems = await getContext().runWithStore(
58
163
  Store,
59
164
  Store.namespace || namespaceWithMount,
@@ -62,25 +167,23 @@ export async function loadManifest(
62
167
  // Create helpers for lazy-loaded handlers that need them
63
168
  const helpers = createRouteHelpers();
64
169
 
65
- // For lazy entries, use lazyPatterns.handler() with proper prefixes
170
+ // For lazy entries, use lazyPatterns.handler() with proper prefixes.
171
+ // Do NOT wrap in MapRootLayout here: the captured parent chain from
172
+ // pattern extraction already includes the synthetic MapRootLayout
173
+ // parent, so adding another would create an extra level that does
174
+ // not exist in the non-lazy (root handler) path and would produce
175
+ // mismatched shortCodes.
66
176
  if (entry.lazy && entry.lazyPatterns) {
67
177
  const lazyPatterns = entry.lazyPatterns as UrlPatterns<any>;
68
178
  const includePrefix = (entry as any)._lazyPrefix || "";
69
179
  const fullPrefix = (lazyContext?.urlPrefix || "") + includePrefix;
70
180
 
71
- // Wrap in root layout and run with prefixes
72
- const wrappedItems = helpers.layout(MapRootLayout, () => {
73
- if (fullPrefix || lazyContext?.namePrefix) {
74
- return runWithPrefixes(
75
- fullPrefix,
76
- lazyContext?.namePrefix,
77
- () => lazyPatterns.handler()
78
- );
79
- }
80
- return lazyPatterns.handler();
81
- });
82
-
83
- return [wrappedItems].flat(3);
181
+ if (fullPrefix || lazyContext?.namePrefix) {
182
+ return runWithPrefixes(fullPrefix, lazyContext?.namePrefix, () =>
183
+ lazyPatterns.handler(),
184
+ );
185
+ }
186
+ return lazyPatterns.handler();
84
187
  }
85
188
 
86
189
  // Wrap handler execution in root layout so routes get correct parent
@@ -106,35 +209,60 @@ export async function loadManifest(
106
209
  "default" in load
107
210
  ) {
108
211
  // Promise<{ default: () => Array }> - e.g., dynamic import
109
- // Lazy-loaded handlers may need helpers (passed as optional arg)
212
+ if (typeof load.default !== "function") {
213
+ throw new Error(
214
+ `[@rangojs/router] Unsupported async handler: { default } must be a function, ` +
215
+ `got ${typeof load.default}. Use () => import('./urls') for lazy loading.`,
216
+ );
217
+ }
110
218
  return (load.default as (h?: any) => any)(helpers);
111
219
  }
112
220
  if (typeof load === "function") {
113
221
  // Promise<() => Array>
114
222
  return (load as (h?: any) => any)(helpers);
115
223
  }
116
- // Promise<Array> - direct array from async handler
117
- return load;
224
+ // Reject unsupported async handler results. Supported shapes are:
225
+ // Promise<{ default: fn }> — dynamic import
226
+ // Promise<fn> — lazy function
227
+ // Direct Promise<Array> is not supported; use a function wrapper.
228
+ throw new Error(
229
+ `[@rangojs/router] Unsupported async handler result (${typeof load}). ` +
230
+ `Lazy route handlers must resolve to a function or { default: fn }, ` +
231
+ `not a direct array. Wrap your handler: () => import('./urls') or ` +
232
+ `() => Promise.resolve((h) => [...])`,
233
+ );
118
234
  }
119
235
 
120
236
  // Inline handler - routes were registered with correct parent inside layout
121
237
  return [wrappedItems].flat(3);
122
- }
238
+ },
123
239
  );
240
+ pushMetric?.("manifest:handler-exec", handlerExecStart);
124
241
 
242
+ const validationStart = performance.now();
125
243
  invariant(
126
244
  useItems && useItems.length > 0,
127
- "Did not receive any handler from router.map()"
128
- );
129
- invariant(
130
- useItems.some((item: { type: string }) => item.type === "layout"),
131
- "Top-level handler must be a layout"
245
+ "Did not receive any handler from router.map()",
132
246
  );
247
+ // For non-lazy entries the root handler is wrapped in MapRootLayout,
248
+ // so the result always contains a layout item. Lazy entries run the
249
+ // included patterns handler directly (no MapRootLayout wrapper) so
250
+ // we skip this check -- the layout is in the captured parent chain.
251
+ if (!lazyContext) {
252
+ invariant(
253
+ useItems.some((item: { type: string }) => item.type === "layout"),
254
+ "Top-level handler must be a layout",
255
+ );
256
+ }
133
257
 
134
258
  invariant(
135
259
  Store.manifest.has(routeKey),
136
- `Route must be registered for ${routeKey}`
260
+ `Route must be registered for ${routeKey}`,
137
261
  );
262
+ pushMetric?.("manifest:validation", validationStart);
263
+
264
+ // Cache manifest for future requests in this isolate
265
+ manifestModuleCache.set(cacheKey, new Map(Store.manifest));
138
266
 
139
267
  return Store.manifest.get(routeKey)!;
140
268
  } catch (e) {
@@ -148,7 +276,7 @@ export async function loadManifest(
148
276
  routeKey,
149
277
  },
150
278
  },
151
- }
279
+ },
152
280
  );
153
281
  }
154
282
  }