@rangojs/router 0.0.0-experimental.0f44aca1

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 (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  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 +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,591 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import type { ReactNode } from "react";
3
+ import type {
4
+ PartialCacheOptions,
5
+ ErrorBoundaryHandler,
6
+ Handler,
7
+ LoaderDefinition,
8
+ MiddlewareFn,
9
+ NotFoundBoundaryHandler,
10
+ ShouldRevalidateFn,
11
+ TransitionConfig,
12
+ } from "../types";
13
+ import { invariant } from "../errors";
14
+ import type { DefaultRouteName } from "../types/global-namespace.js";
15
+
16
+ // ============================================================================
17
+ // Performance Metrics Types
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Performance metric entry for a single measured operation
22
+ *
23
+ * @internal This type is an implementation detail and may change without notice.
24
+ */
25
+ export interface PerformanceMetric {
26
+ label: string; // e.g., "route-matching", "loader:UserLoader"
27
+ duration: number; // milliseconds
28
+ startTime: number; // relative to request start
29
+ depth?: number; // nesting level for hierarchical display (0 = top-level)
30
+ }
31
+
32
+ /**
33
+ * Request-scoped metrics store
34
+ *
35
+ * @internal This type is an implementation detail and may change without notice.
36
+ */
37
+ export interface MetricsStore {
38
+ enabled: boolean;
39
+ requestStart: number;
40
+ metrics: PerformanceMetric[];
41
+ }
42
+ // ============================================================================
43
+ // RSC Router Context
44
+ // ============================================================================
45
+
46
+ /**
47
+ * Cache configuration for an entry
48
+ * When set, this entry and its children will use this cache config
49
+ * unless overridden by a nested cache() call.
50
+ *
51
+ * @internal This type is an implementation detail and may change without notice.
52
+ */
53
+ export type EntryCacheConfig = {
54
+ /** Cache options (false means caching disabled for this entry) - ttl is optional, uses defaults */
55
+ options: PartialCacheOptions | false;
56
+ };
57
+
58
+ /**
59
+ * Entry data structure for manifest
60
+ *
61
+ * @internal This type is an implementation detail and may change without notice.
62
+ */
63
+ export type EntryPropCommon = {
64
+ id: string;
65
+ shortCode: string; // Short identifier for network efficiency (e.g., "L0", "P1", "R2")
66
+ parent: EntryData | null;
67
+ /** Cache configuration for this entry (set by cache() DSL) */
68
+ cache?: EntryCacheConfig;
69
+ /** URL prefix from include() scope, used for MountContext on client */
70
+ mountPath?: string;
71
+ };
72
+
73
+ /**
74
+ * @internal This type is an implementation detail and may change without notice.
75
+ */
76
+ export type EntryPropDatas = {
77
+ middleware: MiddlewareFn<any, any>[];
78
+ revalidate: ShouldRevalidateFn<any, any>[];
79
+ errorBoundary: (ReactNode | ErrorBoundaryHandler)[];
80
+ notFoundBoundary: (ReactNode | NotFoundBoundaryHandler)[];
81
+ };
82
+
83
+ /**
84
+ * Loader entry stored in EntryData
85
+ * Contains the loader definition and its revalidation rules
86
+ *
87
+ * @internal This type is an implementation detail and may change without notice.
88
+ */
89
+ export type LoaderEntry = {
90
+ loader: LoaderDefinition<any>;
91
+ revalidate: ShouldRevalidateFn<any, any>[];
92
+ /** Cache config for this specific loader (loaders are NOT cached by default) */
93
+ cache?: EntryCacheConfig;
94
+ };
95
+
96
+ /**
97
+ * Segments state for intercept context
98
+ * Matches the structure from useSegments() for consistency
99
+ *
100
+ * @internal This type is an implementation detail and may change without notice.
101
+ */
102
+ export type InterceptSegmentsState = {
103
+ /** URL path segments (e.g., /shop/products/123 → ["shop", "products", "123"]) */
104
+ path: readonly string[];
105
+ /** Matched segment IDs in order (layouts and routes only, e.g., ["L0", "L0L1", "L0L1R0"]) */
106
+ ids: readonly string[];
107
+ };
108
+
109
+ /**
110
+ * Context passed to intercept selector functions (when())
111
+ * Contains navigation context to determine if interception should occur.
112
+ *
113
+ * Note: when() is evaluated during route matching, BEFORE middleware runs.
114
+ * So ctx.get()/ctx.use() are not available, but env (platform bindings) is.
115
+ *
116
+ * @internal This type is an implementation detail and may change without notice.
117
+ */
118
+ export type InterceptSelectorContext<TEnv = any> = {
119
+ from: URL; // Source URL (where user is coming from)
120
+ to: URL; // Destination URL (where user is navigating to)
121
+ params: Record<string, string>; // Matched route params
122
+ request: Request; // The HTTP request object
123
+ env: TEnv; // Platform bindings (Cloudflare env, etc.)
124
+ segments: InterceptSegmentsState; // Client's current segments (where navigating FROM)
125
+ fromRouteName?: DefaultRouteName; // Named route being navigated away from (undefined for unnamed routes)
126
+ toRouteName?: DefaultRouteName; // Named route being navigated to (undefined for unnamed routes)
127
+ };
128
+
129
+ /**
130
+ * Selector function for conditional interception
131
+ * Returns true to intercept, false to skip and fall through to route handler
132
+ *
133
+ * @internal This type is an implementation detail and may change without notice.
134
+ */
135
+ export type InterceptWhenFn<TEnv = any> = (
136
+ ctx: InterceptSelectorContext<TEnv>,
137
+ ) => boolean;
138
+
139
+ /**
140
+ * Intercept entry stored in EntryData
141
+ * Contains the slot name, route to intercept, and handler
142
+ *
143
+ * @internal This type is an implementation detail and may change without notice.
144
+ */
145
+ export type InterceptEntry = {
146
+ slotName: `@${string}`; // e.g., "@modal"
147
+ routeName: string; // e.g., "card"
148
+ handler: ReactNode | Handler<any, any, any>;
149
+ middleware: MiddlewareFn<any, any>[];
150
+ revalidate: ShouldRevalidateFn<any, any>[];
151
+ errorBoundary: (ReactNode | ErrorBoundaryHandler)[];
152
+ notFoundBoundary: (ReactNode | NotFoundBoundaryHandler)[];
153
+ loader: LoaderEntry[];
154
+ loading?: ReactNode | false;
155
+ transition?: TransitionConfig;
156
+ layout?: ReactNode | Handler<any, any, any>; // Wrapper layout with <Outlet /> for content
157
+ when: InterceptWhenFn[]; // Selector conditions - all must return true to intercept
158
+ };
159
+
160
+ export type EntryPropSegments = {
161
+ loader: LoaderEntry[];
162
+ layout: EntryData[];
163
+ parallel: EntryData[]; // type: "parallel" entries with their own loaders/revalidate/loading
164
+ intercept: InterceptEntry[]; // intercept definitions for soft navigation
165
+ };
166
+
167
+ export type EntryData =
168
+ | ({
169
+ type: "route";
170
+ handler: Handler<any, any, any>;
171
+ loading?: ReactNode | false;
172
+ transition?: TransitionConfig;
173
+ /** URL pattern for this route (used by path() in urls()) */
174
+ pattern?: string;
175
+ /** Set when handler is a Prerender definition */
176
+ isPrerender?: true;
177
+ /** Original PrerenderHandlerDefinition (for build-time getParams access) */
178
+ prerenderDef?: {
179
+ getParams?: (ctx: any) => Promise<any[]> | any[];
180
+ options?: { passthrough?: boolean };
181
+ };
182
+ /** Set when handler is a Static definition (build-time only) */
183
+ isStaticPrerender?: true;
184
+ /** Static handler $$id for build-time store lookup */
185
+ staticHandlerId?: string;
186
+ /** Response type for non-RSC routes (json, text, image, any) */
187
+ responseType?: string;
188
+ } & EntryPropCommon &
189
+ EntryPropDatas &
190
+ EntryPropSegments)
191
+ | ({
192
+ type: "layout";
193
+ handler: ReactNode | Handler<any, any, any>;
194
+ loading?: ReactNode | false;
195
+ transition?: TransitionConfig;
196
+ /** Set when handler is a Static definition (build-time only) */
197
+ isStaticPrerender?: true;
198
+ /** Static handler $$id for build-time store lookup */
199
+ staticHandlerId?: string;
200
+ } & EntryPropCommon &
201
+ EntryPropDatas &
202
+ EntryPropSegments)
203
+ | ({
204
+ type: "parallel";
205
+ handler: Record<`@${string}`, Handler<any, any, any> | ReactNode>;
206
+ loading?: ReactNode | false;
207
+ transition?: TransitionConfig;
208
+ /** Set when any parallel slot is a Static definition */
209
+ isStaticPrerender?: true;
210
+ /** Per-slot static handler $$ids for build-time store lookup */
211
+ staticHandlerIds?: Record<string, string>;
212
+ } & EntryPropCommon &
213
+ EntryPropDatas &
214
+ EntryPropSegments)
215
+ | ({
216
+ type: "cache";
217
+ /** Cache entries create cache boundaries and render like layouts (with Outlet) */
218
+ handler: ReactNode | Handler<any, any, any>;
219
+ loading?: ReactNode | false;
220
+ transition?: TransitionConfig;
221
+ } & EntryPropCommon &
222
+ EntryPropDatas &
223
+ EntryPropSegments);
224
+
225
+ /**
226
+ * Tracked include info for build-time manifest generation
227
+ */
228
+ export interface TrackedInclude {
229
+ prefix: string;
230
+ fullPrefix: string;
231
+ namePrefix?: string;
232
+ patterns: unknown; // UrlPatterns
233
+ lazy: boolean;
234
+ }
235
+
236
+ /**
237
+ * Context stored in AsyncLocalStorage
238
+ */
239
+ interface HelperContext {
240
+ manifest: Map<string, EntryData>;
241
+ namespace: string;
242
+ parent: EntryData | null;
243
+ counters: Record<string, number>;
244
+ forRoute?: string;
245
+ mountIndex?: number;
246
+ metrics?: MetricsStore;
247
+ /** True when rendering for SSR (document requests) */
248
+ isSSR?: boolean;
249
+ /** URL patterns map for path() routes (route name -> pattern) */
250
+ patterns?: Map<string, string>;
251
+ /** URL patterns grouped by include prefix for separate entry creation */
252
+ patternsByPrefix?: Map<string, Map<string, string>>;
253
+ /** Trailing slash config per route name */
254
+ trailingSlash?: Map<string, "never" | "always" | "ignore">;
255
+ /** Search param schemas per route name */
256
+ searchSchemas?: Map<string, Record<string, string>>;
257
+ /** URL prefix from include() - applied to all path() patterns */
258
+ urlPrefix?: string;
259
+ /** Name prefix from include() - applied to all named routes */
260
+ namePrefix?: string;
261
+ /** True when this scope is at root level (no named include boundary above).
262
+ * Routes at root scope allow dot-local reverse to fall back to bare names. */
263
+ rootScoped?: boolean;
264
+ /** Run helper for cleaner middleware code */
265
+ run?: <T>(fn: () => T | Promise<T>) => T | Promise<T>;
266
+ /** Tracked includes for build-time manifest generation */
267
+ trackedIncludes?: TrackedInclude[];
268
+ /** Cache profiles for DSL-time cache("profileName") resolution */
269
+ cacheProfiles?: Record<
270
+ string,
271
+ import("../cache/profile-registry.js").CacheProfile
272
+ >;
273
+ }
274
+ // Use a global symbol key so the AsyncLocalStorage instance survives HMR
275
+ // module re-evaluation. Without this, Vite's RSC module runner may create
276
+ // a new instance when context.ts is re-evaluated, while other modules still
277
+ // hold references to the old instance — causing getStore() to return
278
+ // undefined even inside a run() callback.
279
+ const RSC_CONTEXT_KEY = Symbol.for("rangojs-router:rsc-context");
280
+ export const RSCRouterContext: AsyncLocalStorage<HelperContext> = ((
281
+ globalThis as any
282
+ )[RSC_CONTEXT_KEY] ??= new AsyncLocalStorage<HelperContext>());
283
+
284
+ export const getContext = (): {
285
+ context: AsyncLocalStorage<HelperContext>;
286
+ getStore: () => HelperContext;
287
+ getParent: () => EntryData | null;
288
+ getOrCreateStore: (forRoute?: string) => HelperContext;
289
+ getNextIndex: (
290
+ type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
291
+ ) => string;
292
+ getShortCode: (
293
+ type: "layout" | "parallel" | "route" | "loader" | "cache",
294
+ ) => string;
295
+ run: <T>(
296
+ namespace: string,
297
+ parent: EntryData | null,
298
+ callback: (...args: any[]) => T,
299
+ ) => T;
300
+ runWithStore: <T>(
301
+ store: HelperContext,
302
+ namespace: string,
303
+ parent: EntryData | null,
304
+ callback: (...args: any[]) => T,
305
+ ) => T;
306
+ } => {
307
+ const context = RSCRouterContext;
308
+
309
+ return {
310
+ context,
311
+ getOrCreateStore: (forRoute?: string): HelperContext => {
312
+ let store = RSCRouterContext.getStore();
313
+ if (!store) {
314
+ store = {
315
+ manifest: new Map<string, EntryData>(),
316
+ namespace: "",
317
+ parent: null,
318
+ forRoute,
319
+ counters: {},
320
+ patterns: new Map<string, string>(),
321
+ patternsByPrefix: new Map<string, Map<string, string>>(),
322
+ trailingSlash: new Map<string, "never" | "always" | "ignore">(),
323
+ searchSchemas: new Map<string, Record<string, string>>(),
324
+ } satisfies HelperContext;
325
+ }
326
+ return store;
327
+ },
328
+ getStore: (): HelperContext => {
329
+ const store = context.getStore();
330
+ if (!store) {
331
+ throw new Error(
332
+ "RSC Router context store is not available. Make sure to run within RSC Router context.",
333
+ );
334
+ }
335
+ return store;
336
+ },
337
+ getParent: (): EntryData | null => {
338
+ const store = context.getStore();
339
+ if (!store) {
340
+ return null;
341
+ }
342
+
343
+ return store.parent;
344
+ },
345
+ getNextIndex: (
346
+ type: (string & {}) | "layout" | "parallel" | "middleware" | "revalidate",
347
+ ) => {
348
+ const store = context.getStore();
349
+ invariant(store, "No context RSCRouterContext available");
350
+ store.counters[type] ??= 0;
351
+ const index = store.counters[type];
352
+ store.counters[type] = index + 1;
353
+ return `$${type}.${index}`;
354
+ },
355
+ getShortCode: (
356
+ type: "layout" | "parallel" | "route" | "loader" | "cache",
357
+ ) => {
358
+ const store = context.getStore();
359
+ invariant(store, "No context RSCRouterContext available");
360
+
361
+ const parent = store.parent;
362
+ const prefix =
363
+ type === "layout"
364
+ ? "L"
365
+ : type === "parallel"
366
+ ? "P"
367
+ : type === "loader"
368
+ ? "D"
369
+ : type === "cache"
370
+ ? "C"
371
+ : "R";
372
+ const mountPrefix =
373
+ store.mountIndex !== undefined ? `M${store.mountIndex}` : "";
374
+
375
+ if (!parent) {
376
+ // Root entry: prefix with mount index and use mount-scoped counter
377
+ const counterKey = mountPrefix
378
+ ? `${mountPrefix}_root_${type}`
379
+ : `root_${type}`;
380
+ store.counters[counterKey] ??= 0;
381
+ const index = store.counters[counterKey];
382
+ store.counters[counterKey] = index + 1;
383
+ return `${mountPrefix}${prefix}${index}`;
384
+ } else {
385
+ // Child entry: use parent-scoped counter (parent already has M prefix)
386
+ const counterKey = `${parent.shortCode}_${type}`;
387
+ store.counters[counterKey] ??= 0;
388
+ const index = store.counters[counterKey];
389
+ store.counters[counterKey] = index + 1;
390
+ return `${parent.shortCode}${prefix}${index}`;
391
+ }
392
+ },
393
+ runWithStore: <T>(
394
+ store: HelperContext,
395
+ namespace: string,
396
+ parent: EntryData | null,
397
+ callback: (...args: any[]) => T,
398
+ ): T => {
399
+ return context.run(
400
+ {
401
+ manifest: store.manifest,
402
+ namespace,
403
+ parent: parent || null,
404
+ counters: store.counters,
405
+ forRoute: store.forRoute,
406
+ mountIndex: store.mountIndex,
407
+ metrics: store.metrics,
408
+ isSSR: store.isSSR,
409
+ patterns: store.patterns,
410
+ trailingSlash: store.trailingSlash,
411
+ searchSchemas: store.searchSchemas,
412
+ urlPrefix: store.urlPrefix,
413
+ namePrefix: store.namePrefix,
414
+ rootScoped: store.rootScoped,
415
+ trackedIncludes: store.trackedIncludes,
416
+ cacheProfiles: store.cacheProfiles,
417
+ },
418
+ callback,
419
+ );
420
+ },
421
+ run: <T>(
422
+ namespace: string,
423
+ parent: EntryData | null,
424
+ callback: (...args: any[]) => T,
425
+ ) => {
426
+ const store = context.getStore();
427
+ // Preserve parent counters to ensure globally unique shortCodes
428
+ const counters = store?.counters || {};
429
+ const manifest = store ? store.manifest : new Map<string, EntryData>();
430
+ const patterns = store?.patterns || new Map<string, string>();
431
+ const patternsByPrefix = store?.patternsByPrefix;
432
+ const trailingSlash =
433
+ store?.trailingSlash ||
434
+ new Map<string, "never" | "always" | "ignore">();
435
+ const searchSchemas =
436
+ store?.searchSchemas || new Map<string, Record<string, string>>();
437
+ return context.run(
438
+ {
439
+ manifest,
440
+ namespace,
441
+ parent: parent || null,
442
+ counters,
443
+ forRoute: store?.forRoute,
444
+ mountIndex: store?.mountIndex,
445
+ metrics: store?.metrics,
446
+ isSSR: store?.isSSR,
447
+ patterns,
448
+ patternsByPrefix,
449
+ trailingSlash,
450
+ searchSchemas,
451
+ urlPrefix: store?.urlPrefix,
452
+ namePrefix: store?.namePrefix,
453
+ rootScoped: store?.rootScoped,
454
+ trackedIncludes: store?.trackedIncludes,
455
+ cacheProfiles: store?.cacheProfiles,
456
+ },
457
+ callback,
458
+ );
459
+ },
460
+ };
461
+ };
462
+
463
+ /**
464
+ * Run a callback with specific URL and name prefixes
465
+ * Used by include() to apply prefixes to nested patterns
466
+ */
467
+ export function runWithPrefixes<T>(
468
+ urlPrefix: string,
469
+ namePrefix: string | undefined,
470
+ callback: () => T,
471
+ ): T {
472
+ const store = RSCRouterContext.getStore();
473
+ if (!store) {
474
+ throw new Error("runWithPrefixes must be called within router context");
475
+ }
476
+
477
+ // Combine prefixes if there are existing ones, avoiding double slashes
478
+ let combinedUrlPrefix: string;
479
+ if (store.urlPrefix) {
480
+ if (store.urlPrefix.endsWith("/") && urlPrefix.startsWith("/")) {
481
+ combinedUrlPrefix = store.urlPrefix + urlPrefix.slice(1);
482
+ } else {
483
+ combinedUrlPrefix = store.urlPrefix + urlPrefix;
484
+ }
485
+ } else {
486
+ combinedUrlPrefix = urlPrefix;
487
+ }
488
+ const combinedNamePrefix =
489
+ namePrefix !== undefined
490
+ ? namePrefix === ""
491
+ ? store.namePrefix
492
+ : store.namePrefix
493
+ ? `${store.namePrefix}.${namePrefix}`
494
+ : namePrefix
495
+ : store.namePrefix;
496
+
497
+ // Track root scope for dot-local reverse resolution.
498
+ //
499
+ // The flag answers: "can this route reach bare names at root scope?"
500
+ // It propagates through the include chain:
501
+ //
502
+ // { name: "" } — transparent: inherit parent, default true
503
+ // { name: "foo" } — inherit parent if already set, else create boundary (false)
504
+ // no name — inherit parent unchanged
505
+ //
506
+ // This means { name: "" } + nested { name: "sub" } keeps rootScoped=true
507
+ // (the outer transparent include establishes root access, and the inner
508
+ // named include inherits it). But a direct { name: "sub" } at root gets
509
+ // rootScoped=false (no prior root-access grant, so it creates a boundary).
510
+ const combinedRootScoped =
511
+ namePrefix === ""
512
+ ? (store.rootScoped ?? true)
513
+ : namePrefix !== undefined
514
+ ? (store.rootScoped ?? false)
515
+ : store.rootScoped;
516
+
517
+ return RSCRouterContext.run(
518
+ {
519
+ ...store,
520
+ urlPrefix: combinedUrlPrefix,
521
+ namePrefix: combinedNamePrefix,
522
+ rootScoped: combinedRootScoped,
523
+ },
524
+ callback,
525
+ );
526
+ }
527
+
528
+ /**
529
+ * Get current URL prefix from context
530
+ */
531
+ export function getUrlPrefix(): string {
532
+ const store = RSCRouterContext.getStore();
533
+ return store?.urlPrefix || "";
534
+ }
535
+
536
+ /**
537
+ * Get current name prefix from context
538
+ */
539
+ export function getNamePrefix(): string | undefined {
540
+ const store = RSCRouterContext.getStore();
541
+ return store?.namePrefix;
542
+ }
543
+
544
+ /**
545
+ * Get whether the current scope is at root level (no named include boundary above).
546
+ * Returns true at root or inside { name: "" } includes, false inside named includes.
547
+ */
548
+ export function getRootScoped(): boolean {
549
+ const store = RSCRouterContext.getStore();
550
+ return store?.rootScoped ?? true;
551
+ }
552
+
553
+ // Export HelperContext type for use in other modules
554
+ export type { HelperContext };
555
+
556
+ // ============================================================================
557
+ // Performance Metrics Helpers
558
+ // ============================================================================
559
+
560
+ /**
561
+ * Track performance of a code block (no-op if metrics not enabled)
562
+ * Returns a done() callback to mark completion and record duration
563
+ *
564
+ * @example
565
+ * ```typescript
566
+ * const done = track("route-matching");
567
+ * // ... do work ...
568
+ * done(); // Records duration
569
+ * ```
570
+ */
571
+ export function track(label: string, depth?: number): () => void {
572
+ const store = RSCRouterContext.getStore();
573
+
574
+ // No-op if context unavailable or metrics not enabled
575
+ if (!store?.metrics?.enabled) {
576
+ return () => {};
577
+ }
578
+
579
+ const startTime = performance.now() - store.metrics.requestStart;
580
+
581
+ return () => {
582
+ const duration =
583
+ performance.now() - store.metrics!.requestStart - startTime;
584
+ store.metrics!.metrics.push({
585
+ label,
586
+ duration,
587
+ startTime,
588
+ ...(depth != null ? { depth } : {}),
589
+ });
590
+ };
591
+ }