@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,289 @@
1
+ import type { EntryData, InterceptEntry } from "../server/context";
2
+ import type {
3
+ HandlerContext,
4
+ ResolvedSegment,
5
+ ShouldRevalidateFn,
6
+ } from "../types";
7
+ import type { SegmentResolutionDeps } from "./types.js";
8
+
9
+ import {
10
+ resolveAllSegments as _resolveAllSegments,
11
+ resolveLoadersOnly as _resolveLoadersOnly,
12
+ resolveLoadersOnlyWithRevalidation as _resolveLoadersOnlyWithRevalidation,
13
+ buildEntryRevalidateMap as _buildEntryRevalidateMap,
14
+ resolveAllSegmentsWithRevalidation as _resolveAllSegmentsWithRevalidation,
15
+ } from "./segment-resolution.js";
16
+
17
+ import {
18
+ findInterceptForRoute as _findInterceptForRoute,
19
+ resolveInterceptEntry as _resolveInterceptEntry,
20
+ resolveInterceptLoadersOnly as _resolveInterceptLoadersOnly,
21
+ } from "./intercept-resolution.js";
22
+
23
+ import type { InterceptSelectorContext } from "../server/context";
24
+
25
+ export interface SegmentWrappers<TEnv = any> {
26
+ resolveAllSegments: (
27
+ entries: EntryData[],
28
+ routeKey: string,
29
+ params: Record<string, string>,
30
+ context: HandlerContext<any, TEnv>,
31
+ loaderPromises: Map<string, Promise<any>>,
32
+ options?: { skipLoaders?: boolean },
33
+ ) => Promise<ResolvedSegment[]>;
34
+ resolveLoadersOnly: (
35
+ entries: EntryData[],
36
+ context: HandlerContext<any, TEnv>,
37
+ ) => Promise<ResolvedSegment[]>;
38
+ resolveLoadersOnlyWithRevalidation: (
39
+ entries: EntryData[],
40
+ context: HandlerContext<any, TEnv>,
41
+ clientSegmentIds: Set<string>,
42
+ prevParams: Record<string, string>,
43
+ request: Request,
44
+ prevUrl: URL,
45
+ nextUrl: URL,
46
+ routeKey: string,
47
+ actionContext?: {
48
+ actionId?: string;
49
+ actionUrl?: URL;
50
+ actionResult?: any;
51
+ formData?: FormData;
52
+ },
53
+ stale?: boolean,
54
+ ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
55
+ buildEntryRevalidateMap: (
56
+ entries: EntryData[],
57
+ ) => Map<
58
+ string,
59
+ { entry: EntryData; revalidate: ShouldRevalidateFn<any, any>[] }
60
+ >;
61
+ resolveAllSegmentsWithRevalidation: (
62
+ entries: EntryData[],
63
+ routeKey: string,
64
+ params: Record<string, string>,
65
+ context: HandlerContext<any, TEnv>,
66
+ clientSegmentSet: Set<string>,
67
+ prevParams: Record<string, string>,
68
+ request: Request,
69
+ prevUrl: URL,
70
+ nextUrl: URL,
71
+ loaderPromises: Map<string, Promise<any>>,
72
+ actionContext:
73
+ | {
74
+ actionId?: string;
75
+ actionUrl?: URL;
76
+ actionResult?: any;
77
+ formData?: FormData;
78
+ }
79
+ | undefined,
80
+ interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
81
+ localRouteName: string,
82
+ pathname: string,
83
+ ) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
84
+ findInterceptForRoute: (
85
+ targetRouteKey: string,
86
+ fromEntry: EntryData | null,
87
+ selectorContext?: InterceptSelectorContext | null,
88
+ isAction?: boolean,
89
+ ) => { intercept: InterceptEntry; entry: EntryData } | null;
90
+ resolveInterceptEntry: (
91
+ interceptEntry: InterceptEntry,
92
+ parentEntry: EntryData,
93
+ params: Record<string, string>,
94
+ context: HandlerContext<any, TEnv>,
95
+ belongsToRoute?: boolean,
96
+ revalidationContext?: any,
97
+ ) => Promise<ResolvedSegment[]>;
98
+ resolveInterceptLoadersOnly: (
99
+ interceptEntry: InterceptEntry,
100
+ parentEntry: EntryData,
101
+ params: Record<string, string>,
102
+ context: HandlerContext<any, TEnv>,
103
+ belongsToRoute?: boolean,
104
+ revalidationContext?: any,
105
+ ) => Promise<{
106
+ loaderDataPromise: Promise<any[]> | any[];
107
+ loaderIds: string[];
108
+ } | null>;
109
+ }
110
+
111
+ /**
112
+ * Create thin wrapper functions that bind segmentDeps to extracted
113
+ * segment resolution and intercept resolution functions.
114
+ *
115
+ * These maintain the same signatures as the original inline functions
116
+ * so that RouterContext and call sites don't need to change.
117
+ */
118
+ export function createSegmentWrappers<TEnv = any>(
119
+ segmentDeps: SegmentResolutionDeps<TEnv>,
120
+ ): SegmentWrappers<TEnv> {
121
+ function resolveAllSegments(
122
+ entries: EntryData[],
123
+ routeKey: string,
124
+ params: Record<string, string>,
125
+ context: HandlerContext<any, TEnv>,
126
+ loaderPromises: Map<string, Promise<any>>,
127
+ options?: { skipLoaders?: boolean },
128
+ ): ReturnType<typeof _resolveAllSegments> {
129
+ return _resolveAllSegments(
130
+ entries,
131
+ routeKey,
132
+ params,
133
+ context,
134
+ loaderPromises,
135
+ segmentDeps,
136
+ options,
137
+ );
138
+ }
139
+
140
+ function resolveLoadersOnly(
141
+ entries: EntryData[],
142
+ context: HandlerContext<any, TEnv>,
143
+ ): ReturnType<typeof _resolveLoadersOnly> {
144
+ return _resolveLoadersOnly(entries, context, segmentDeps);
145
+ }
146
+
147
+ function resolveLoadersOnlyWithRevalidation(
148
+ entries: EntryData[],
149
+ context: HandlerContext<any, TEnv>,
150
+ clientSegmentIds: Set<string>,
151
+ prevParams: Record<string, string>,
152
+ request: Request,
153
+ prevUrl: URL,
154
+ nextUrl: URL,
155
+ routeKey: string,
156
+ actionContext?: {
157
+ actionId?: string;
158
+ actionUrl?: URL;
159
+ actionResult?: any;
160
+ formData?: FormData;
161
+ },
162
+ stale?: boolean,
163
+ ): ReturnType<typeof _resolveLoadersOnlyWithRevalidation> {
164
+ return _resolveLoadersOnlyWithRevalidation(
165
+ entries,
166
+ context,
167
+ clientSegmentIds,
168
+ prevParams,
169
+ request,
170
+ prevUrl,
171
+ nextUrl,
172
+ routeKey,
173
+ segmentDeps,
174
+ actionContext,
175
+ stale,
176
+ );
177
+ }
178
+
179
+ function buildEntryRevalidateMap(
180
+ entries: EntryData[],
181
+ ): ReturnType<typeof _buildEntryRevalidateMap> {
182
+ return _buildEntryRevalidateMap(entries);
183
+ }
184
+
185
+ function resolveAllSegmentsWithRevalidation(
186
+ entries: EntryData[],
187
+ routeKey: string,
188
+ params: Record<string, string>,
189
+ context: HandlerContext<any, TEnv>,
190
+ clientSegmentSet: Set<string>,
191
+ prevParams: Record<string, string>,
192
+ request: Request,
193
+ prevUrl: URL,
194
+ nextUrl: URL,
195
+ loaderPromises: Map<string, Promise<any>>,
196
+ actionContext:
197
+ | {
198
+ actionId?: string;
199
+ actionUrl?: URL;
200
+ actionResult?: any;
201
+ formData?: FormData;
202
+ }
203
+ | undefined,
204
+ interceptResult: { intercept: InterceptEntry; entry: EntryData } | null,
205
+ localRouteName: string,
206
+ pathname: string,
207
+ ): ReturnType<typeof _resolveAllSegmentsWithRevalidation> {
208
+ return _resolveAllSegmentsWithRevalidation(
209
+ entries,
210
+ routeKey,
211
+ params,
212
+ context,
213
+ clientSegmentSet,
214
+ prevParams,
215
+ request,
216
+ prevUrl,
217
+ nextUrl,
218
+ loaderPromises,
219
+ actionContext,
220
+ interceptResult,
221
+ localRouteName,
222
+ pathname,
223
+ segmentDeps,
224
+ );
225
+ }
226
+
227
+ function findInterceptForRoute(
228
+ targetRouteKey: string,
229
+ fromEntry: EntryData | null,
230
+ selectorContext: InterceptSelectorContext | null = null,
231
+ isAction: boolean = false,
232
+ ): ReturnType<typeof _findInterceptForRoute> {
233
+ return _findInterceptForRoute(
234
+ targetRouteKey,
235
+ fromEntry,
236
+ selectorContext,
237
+ isAction,
238
+ );
239
+ }
240
+
241
+ function resolveInterceptEntry(
242
+ interceptEntry: InterceptEntry,
243
+ parentEntry: EntryData,
244
+ params: Record<string, string>,
245
+ context: HandlerContext<any, TEnv>,
246
+ belongsToRoute: boolean = true,
247
+ revalidationContext?: any,
248
+ ): ReturnType<typeof _resolveInterceptEntry> {
249
+ return _resolveInterceptEntry(
250
+ interceptEntry,
251
+ parentEntry,
252
+ params,
253
+ context,
254
+ belongsToRoute,
255
+ segmentDeps,
256
+ revalidationContext,
257
+ );
258
+ }
259
+
260
+ function resolveInterceptLoadersOnly(
261
+ interceptEntry: InterceptEntry,
262
+ parentEntry: EntryData,
263
+ params: Record<string, string>,
264
+ context: HandlerContext<any, TEnv>,
265
+ belongsToRoute: boolean = true,
266
+ revalidationContext: any,
267
+ ): ReturnType<typeof _resolveInterceptLoadersOnly> {
268
+ return _resolveInterceptLoadersOnly(
269
+ interceptEntry,
270
+ parentEntry,
271
+ params,
272
+ context,
273
+ belongsToRoute,
274
+ segmentDeps,
275
+ revalidationContext,
276
+ );
277
+ }
278
+
279
+ return {
280
+ resolveAllSegments: resolveAllSegments,
281
+ resolveLoadersOnly: resolveLoadersOnly,
282
+ resolveLoadersOnlyWithRevalidation: resolveLoadersOnlyWithRevalidation,
283
+ buildEntryRevalidateMap: buildEntryRevalidateMap,
284
+ resolveAllSegmentsWithRevalidation: resolveAllSegmentsWithRevalidation,
285
+ findInterceptForRoute: findInterceptForRoute,
286
+ resolveInterceptEntry: resolveInterceptEntry,
287
+ resolveInterceptLoadersOnly: resolveInterceptLoadersOnly,
288
+ };
289
+ }
@@ -0,0 +1,299 @@
1
+ /**
2
+ * OpenTelemetry Adapter for Router Telemetry
3
+ *
4
+ * Maps internal TelemetrySink events to OTel spans. The core router
5
+ * remains OTel-agnostic — this adapter bridges the gap by accepting
6
+ * a standard OTel Tracer and producing spans/events from it.
7
+ *
8
+ * Usage:
9
+ * import { trace } from "@opentelemetry/api";
10
+ * import { createOTelSink } from "@rangojs/router";
11
+ *
12
+ * const router = createRouter({
13
+ * telemetry: createOTelSink(trace.getTracer("my-app")),
14
+ * });
15
+ */
16
+
17
+ import type { TelemetrySink, TelemetryEvent } from "./telemetry.js";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Minimal OTel-compatible types (structurally typed, no import needed)
21
+ // ---------------------------------------------------------------------------
22
+
23
+ /**
24
+ * Minimal Span interface compatible with @opentelemetry/api Span.
25
+ * Only the methods used by the adapter are declared.
26
+ */
27
+ export interface OTelSpan {
28
+ setAttribute(key: string, value: string | number | boolean): OTelSpan | void;
29
+ addEvent(
30
+ name: string,
31
+ attributes?: Record<string, string | number | boolean>,
32
+ ): OTelSpan | void;
33
+ setStatus(status: { code: number; message?: string }): OTelSpan | void;
34
+ recordException(exception: Error): void;
35
+ end(): void;
36
+ }
37
+
38
+ /**
39
+ * Minimal Tracer interface compatible with @opentelemetry/api Tracer.
40
+ */
41
+ export interface OTelTracer {
42
+ startSpan(
43
+ name: string,
44
+ options?: {
45
+ attributes?: Record<string, string | number | boolean>;
46
+ },
47
+ ): OTelSpan;
48
+ }
49
+
50
+ // OTel SpanStatusCode constants (mirrors @opentelemetry/api values)
51
+ const STATUS_OK = 1;
52
+ const STATUS_ERROR = 2;
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Span correlation helpers
56
+ // ---------------------------------------------------------------------------
57
+
58
+ // Build correlation keys using requestId.
59
+ // getRequestId() always returns a value (generated internally when no
60
+ // header is present), so concurrent requests to the same path each get
61
+ // their own correlation key and never mis-correlate.
62
+
63
+ function requestKey(event: {
64
+ requestId?: string;
65
+ pathname: string;
66
+ transaction: string;
67
+ }): string {
68
+ return `${event.requestId ?? ""}:${event.pathname}:${event.transaction}`;
69
+ }
70
+
71
+ function loaderKey(event: {
72
+ requestId?: string;
73
+ segmentId: string;
74
+ loaderName: string;
75
+ pathname: string;
76
+ }): string {
77
+ return `${event.requestId ?? ""}:${event.segmentId}:${event.loaderName}:${event.pathname}`;
78
+ }
79
+
80
+ function pushSpan(
81
+ map: Map<string, OTelSpan[]>,
82
+ key: string,
83
+ span: OTelSpan,
84
+ ): void {
85
+ let stack = map.get(key);
86
+ if (!stack) {
87
+ stack = [];
88
+ map.set(key, stack);
89
+ }
90
+ stack.push(span);
91
+ }
92
+
93
+ function popSpan(
94
+ map: Map<string, OTelSpan[]>,
95
+ key: string,
96
+ ): OTelSpan | undefined {
97
+ const stack = map.get(key);
98
+ if (!stack || stack.length === 0) return undefined;
99
+ const span = stack.pop()!;
100
+ if (stack.length === 0) map.delete(key);
101
+ return span;
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Adapter factory
106
+ // ---------------------------------------------------------------------------
107
+
108
+ /**
109
+ * Create a TelemetrySink that maps router lifecycle events to OTel spans.
110
+ *
111
+ * Span mapping:
112
+ * - request.start / request.end / request.error → "rango.request" span
113
+ * - loader.start / loader.end / loader.error → "rango.loader" span
114
+ * - handler.error → "rango.handler.error" instant span
115
+ * - cache.decision → "rango.cache.decision" instant span
116
+ * - revalidation.decision → "rango.revalidation.decision" instant span
117
+ *
118
+ * Attributes use the `rango.*` namespace for router-specific data and
119
+ * `http.method` / `http.route` for HTTP semantics.
120
+ */
121
+ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
122
+ const requestSpans = new Map<string, OTelSpan[]>();
123
+ const loaderSpans = new Map<string, OTelSpan[]>();
124
+
125
+ return {
126
+ emit(event: TelemetryEvent): void {
127
+ switch (event.type) {
128
+ // -----------------------------------------------------------------
129
+ // Request lifecycle
130
+ // -----------------------------------------------------------------
131
+
132
+ case "request.start": {
133
+ const span = tracer.startSpan("rango.request", {
134
+ attributes: {
135
+ "http.method": event.method,
136
+ "http.route": event.pathname,
137
+ "rango.transaction": event.transaction,
138
+ "rango.is_partial": event.isPartial,
139
+ },
140
+ });
141
+ pushSpan(requestSpans, requestKey(event), span);
142
+ break;
143
+ }
144
+
145
+ case "request.end": {
146
+ const span = popSpan(requestSpans, requestKey(event));
147
+ if (span) {
148
+ span.setAttribute("rango.duration_ms", event.durationMs);
149
+ span.setAttribute("rango.segment_count", event.segmentCount);
150
+ span.setAttribute("rango.cache.hit", event.cacheHit);
151
+ span.setStatus({ code: STATUS_OK });
152
+ span.end();
153
+ }
154
+ break;
155
+ }
156
+
157
+ case "request.error": {
158
+ const span = popSpan(requestSpans, requestKey(event));
159
+ if (span) {
160
+ span.setAttribute("rango.duration_ms", event.durationMs);
161
+ span.setAttribute("rango.phase", event.phase);
162
+ span.recordException(event.error);
163
+ span.setStatus({
164
+ code: STATUS_ERROR,
165
+ message: event.error.message,
166
+ });
167
+ span.end();
168
+ }
169
+ break;
170
+ }
171
+
172
+ // -----------------------------------------------------------------
173
+ // Loader lifecycle
174
+ // -----------------------------------------------------------------
175
+
176
+ case "loader.start": {
177
+ const span = tracer.startSpan("rango.loader", {
178
+ attributes: {
179
+ "rango.segment_id": event.segmentId,
180
+ "rango.loader_name": event.loaderName,
181
+ "http.route": event.pathname,
182
+ },
183
+ });
184
+ pushSpan(loaderSpans, loaderKey(event), span);
185
+ break;
186
+ }
187
+
188
+ case "loader.end": {
189
+ const key = loaderKey(event);
190
+ const span = popSpan(loaderSpans, key);
191
+ if (span) {
192
+ span.setAttribute("rango.duration_ms", event.durationMs);
193
+ span.setAttribute("rango.loader.ok", event.ok);
194
+ span.setStatus({ code: event.ok ? STATUS_OK : STATUS_ERROR });
195
+ span.end();
196
+ }
197
+ break;
198
+ }
199
+
200
+ case "loader.error": {
201
+ const key = loaderKey(event);
202
+ const span = popSpan(loaderSpans, key);
203
+ if (span) {
204
+ span.setAttribute(
205
+ "rango.handled_by_boundary",
206
+ event.handledByBoundary,
207
+ );
208
+ span.recordException(event.error);
209
+ span.setStatus({
210
+ code: STATUS_ERROR,
211
+ message: event.error.message,
212
+ });
213
+ span.end();
214
+ } else {
215
+ // No matching start — create a standalone error span
216
+ const errorSpan = tracer.startSpan("rango.loader", {
217
+ attributes: {
218
+ "rango.segment_id": event.segmentId,
219
+ "rango.loader_name": event.loaderName,
220
+ "http.route": event.pathname,
221
+ "rango.handled_by_boundary": event.handledByBoundary,
222
+ },
223
+ });
224
+ errorSpan.recordException(event.error);
225
+ errorSpan.setStatus({
226
+ code: STATUS_ERROR,
227
+ message: event.error.message,
228
+ });
229
+ errorSpan.end();
230
+ }
231
+ break;
232
+ }
233
+
234
+ // -----------------------------------------------------------------
235
+ // Handler errors (instant span)
236
+ // -----------------------------------------------------------------
237
+
238
+ case "handler.error": {
239
+ const attrs: Record<string, string | number | boolean> = {
240
+ "rango.handled_by_boundary": event.handledByBoundary,
241
+ };
242
+ if (event.segmentId) attrs["rango.segment_id"] = event.segmentId;
243
+ if (event.segmentType)
244
+ attrs["rango.segment_type"] = event.segmentType;
245
+ if (event.pathname) attrs["http.route"] = event.pathname;
246
+ if (event.routeKey) attrs["rango.route_key"] = event.routeKey;
247
+ if (event.params) {
248
+ attrs["rango.params"] = JSON.stringify(event.params);
249
+ }
250
+
251
+ const span = tracer.startSpan("rango.handler.error", {
252
+ attributes: attrs,
253
+ });
254
+ span.recordException(event.error);
255
+ span.setStatus({ code: STATUS_ERROR, message: event.error.message });
256
+ span.end();
257
+ break;
258
+ }
259
+
260
+ // -----------------------------------------------------------------
261
+ // Cache decision (instant span)
262
+ // -----------------------------------------------------------------
263
+
264
+ case "cache.decision": {
265
+ const attrs: Record<string, string | number | boolean> = {
266
+ "http.route": event.pathname,
267
+ "rango.route_key": event.routeKey,
268
+ "rango.cache.hit": event.hit,
269
+ "rango.cache.should_revalidate": event.shouldRevalidate,
270
+ };
271
+ if (event.source) attrs["rango.cache.source"] = event.source;
272
+
273
+ const span = tracer.startSpan("rango.cache.decision", {
274
+ attributes: attrs,
275
+ });
276
+ span.end();
277
+ break;
278
+ }
279
+
280
+ // -----------------------------------------------------------------
281
+ // Revalidation decision (instant span)
282
+ // -----------------------------------------------------------------
283
+
284
+ case "revalidation.decision": {
285
+ const span = tracer.startSpan("rango.revalidation.decision", {
286
+ attributes: {
287
+ "rango.segment_id": event.segmentId,
288
+ "http.route": event.pathname,
289
+ "rango.route_key": event.routeKey,
290
+ "rango.revalidate": event.shouldRevalidate,
291
+ },
292
+ });
293
+ span.end();
294
+ break;
295
+ }
296
+ }
297
+ },
298
+ };
299
+ }