@rangojs/router 0.0.0-experimental.13 → 0.0.0-experimental.13221847

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 (298) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +884 -4
  3. package/dist/bin/rango.js +1531 -212
  4. package/dist/vite/index.js +3995 -2489
  5. package/package.json +57 -52
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +85 -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 +6 -4
  13. package/skills/hooks/SKILL.md +328 -70
  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 +62 -15
  18. package/skills/loader/SKILL.md +368 -42
  19. package/skills/middleware/SKILL.md +171 -34
  20. package/skills/mime-routes/SKILL.md +14 -10
  21. package/skills/parallel/SKILL.md +137 -1
  22. package/skills/prerender/SKILL.md +366 -28
  23. package/skills/rango/SKILL.md +85 -21
  24. package/skills/response-routes/SKILL.md +136 -83
  25. package/skills/route/SKILL.md +195 -21
  26. package/skills/router-setup/SKILL.md +123 -30
  27. package/skills/theme/SKILL.md +9 -8
  28. package/skills/typesafety/SKILL.md +240 -102
  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 +92 -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 +11 -0
  39. package/src/browser/merge-segment-loaders.ts +20 -12
  40. package/src/browser/navigation-bridge.ts +266 -558
  41. package/src/browser/navigation-client.ts +132 -75
  42. package/src/browser/navigation-store.ts +33 -50
  43. package/src/browser/navigation-transaction.ts +297 -0
  44. package/src/browser/network-error-handler.ts +61 -0
  45. package/src/browser/partial-update.ts +303 -309
  46. package/src/browser/prefetch/cache.ts +206 -0
  47. package/src/browser/prefetch/fetch.ts +144 -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 +128 -0
  51. package/src/browser/rango-state.ts +112 -0
  52. package/src/browser/react/Link.tsx +190 -70
  53. package/src/browser/react/NavigationProvider.tsx +78 -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 +188 -57
  74. package/src/browser/scroll-restoration.ts +117 -44
  75. package/src/browser/segment-reconciler.ts +221 -0
  76. package/src/browser/segment-structure-assert.ts +16 -0
  77. package/src/browser/server-action-bridge.ts +488 -606
  78. package/src/browser/shallow.ts +6 -1
  79. package/src/browser/types.ts +116 -47
  80. package/src/browser/validate-redirect-origin.ts +29 -0
  81. package/src/build/generate-manifest.ts +63 -21
  82. package/src/build/generate-route-types.ts +36 -1038
  83. package/src/build/index.ts +2 -5
  84. package/src/build/route-trie.ts +38 -12
  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 +479 -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 +342 -0
  98. package/src/cache/cache-scope.ts +122 -303
  99. package/src/cache/cf/cf-cache-store.ts +571 -17
  100. package/src/cache/cf/index.ts +13 -3
  101. package/src/cache/document-cache.ts +116 -77
  102. package/src/cache/handle-capture.ts +81 -0
  103. package/src/cache/handle-snapshot.ts +41 -0
  104. package/src/cache/index.ts +1 -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 +19 -9
  117. package/src/errors.ts +77 -7
  118. package/src/handle.ts +12 -7
  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 +104 -40
  133. package/src/index.ts +122 -67
  134. package/src/internal-debug.ts +9 -3
  135. package/src/loader.rsc.ts +18 -93
  136. package/src/loader.ts +26 -9
  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 +121 -17
  141. package/src/prerender.ts +325 -20
  142. package/src/reverse.ts +144 -124
  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 +959 -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 -1450
  151. package/src/route-map-builder.ts +87 -133
  152. package/src/route-name.ts +53 -0
  153. package/src/route-types.ts +41 -6
  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 +160 -0
  158. package/src/router/handler-context.ts +324 -116
  159. package/src/router/intercept-resolution.ts +11 -4
  160. package/src/router/lazy-includes.ts +237 -0
  161. package/src/router/loader-resolution.ts +179 -133
  162. package/src/router/logging.ts +112 -6
  163. package/src/router/manifest.ts +58 -19
  164. package/src/router/match-api.ts +89 -88
  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 +86 -89
  168. package/src/router/match-middleware/cache-lookup.ts +295 -49
  169. package/src/router/match-middleware/cache-store.ts +56 -13
  170. package/src/router/match-middleware/intercept-resolution.ts +45 -22
  171. package/src/router/match-middleware/segment-resolution.ts +20 -9
  172. package/src/router/match-pipelines.ts +10 -45
  173. package/src/router/match-result.ts +44 -21
  174. package/src/router/metrics.ts +240 -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 +327 -369
  178. package/src/router/pattern-matching.ts +169 -31
  179. package/src/router/prerender-match.ts +402 -0
  180. package/src/router/preview-match.ts +170 -0
  181. package/src/router/revalidation.ts +105 -14
  182. package/src/router/router-context.ts +40 -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 +677 -0
  187. package/src/router/segment-resolution/helpers.ts +263 -0
  188. package/src/router/segment-resolution/loader-cache.ts +199 -0
  189. package/src/router/segment-resolution/revalidation.ts +1296 -0
  190. package/src/router/segment-resolution/static-store.ts +67 -0
  191. package/src/router/segment-resolution.ts +21 -1354
  192. package/src/router/segment-wrappers.ts +291 -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 +15 -9
  198. package/src/router.ts +642 -2366
  199. package/src/rsc/handler-context.ts +45 -0
  200. package/src/rsc/handler.ts +639 -1027
  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 +66 -54
  216. package/src/segment-system.tsx +165 -17
  217. package/src/server/context.ts +237 -54
  218. package/src/server/cookie-store.ts +190 -0
  219. package/src/server/fetchable-loader-store.ts +11 -6
  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 +438 -71
  223. package/src/server.ts +26 -164
  224. package/src/ssr/index.tsx +101 -31
  225. package/src/static-handler.ts +22 -4
  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 +773 -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 +109 -0
  241. package/src/types/segments.ts +150 -0
  242. package/src/types.ts +1 -1795
  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 -1323
  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 +108 -0
  259. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  260. package/src/vite/index.ts +11 -2259
  261. package/src/vite/plugin-types.ts +48 -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 -47
  266. package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +8 -43
  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 +266 -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 +445 -0
  280. package/src/vite/router-discovery.ts +777 -0
  281. package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +181 -9
  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/dist/vite/index.named-routes.gen.ts +0 -103
  290. package/src/browser/lru-cache.ts +0 -69
  291. package/src/browser/request-controller.ts +0 -164
  292. package/src/cache/memory-store.ts +0 -253
  293. package/src/href-context.ts +0 -33
  294. package/src/router.gen.ts +0 -6
  295. package/src/static-handler.gen.ts +0 -5
  296. package/src/urls.gen.ts +0 -8
  297. package/src/vite/expose-internal-ids.ts +0 -1167
  298. /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
@@ -0,0 +1,291 @@
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
+ stale?: boolean,
208
+ ): ReturnType<typeof _resolveAllSegmentsWithRevalidation> {
209
+ return _resolveAllSegmentsWithRevalidation(
210
+ entries,
211
+ routeKey,
212
+ params,
213
+ context,
214
+ clientSegmentSet,
215
+ prevParams,
216
+ request,
217
+ prevUrl,
218
+ nextUrl,
219
+ loaderPromises,
220
+ actionContext,
221
+ interceptResult,
222
+ localRouteName,
223
+ pathname,
224
+ segmentDeps,
225
+ stale,
226
+ );
227
+ }
228
+
229
+ function findInterceptForRoute(
230
+ targetRouteKey: string,
231
+ fromEntry: EntryData | null,
232
+ selectorContext: InterceptSelectorContext | null = null,
233
+ isAction: boolean = false,
234
+ ): ReturnType<typeof _findInterceptForRoute> {
235
+ return _findInterceptForRoute(
236
+ targetRouteKey,
237
+ fromEntry,
238
+ selectorContext,
239
+ isAction,
240
+ );
241
+ }
242
+
243
+ function resolveInterceptEntry(
244
+ interceptEntry: InterceptEntry,
245
+ parentEntry: EntryData,
246
+ params: Record<string, string>,
247
+ context: HandlerContext<any, TEnv>,
248
+ belongsToRoute: boolean = true,
249
+ revalidationContext?: any,
250
+ ): ReturnType<typeof _resolveInterceptEntry> {
251
+ return _resolveInterceptEntry(
252
+ interceptEntry,
253
+ parentEntry,
254
+ params,
255
+ context,
256
+ belongsToRoute,
257
+ segmentDeps,
258
+ revalidationContext,
259
+ );
260
+ }
261
+
262
+ function resolveInterceptLoadersOnly(
263
+ interceptEntry: InterceptEntry,
264
+ parentEntry: EntryData,
265
+ params: Record<string, string>,
266
+ context: HandlerContext<any, TEnv>,
267
+ belongsToRoute: boolean = true,
268
+ revalidationContext: any,
269
+ ): ReturnType<typeof _resolveInterceptLoadersOnly> {
270
+ return _resolveInterceptLoadersOnly(
271
+ interceptEntry,
272
+ parentEntry,
273
+ params,
274
+ context,
275
+ belongsToRoute,
276
+ segmentDeps,
277
+ revalidationContext,
278
+ );
279
+ }
280
+
281
+ return {
282
+ resolveAllSegments: resolveAllSegments,
283
+ resolveLoadersOnly: resolveLoadersOnly,
284
+ resolveLoadersOnlyWithRevalidation: resolveLoadersOnlyWithRevalidation,
285
+ buildEntryRevalidateMap: buildEntryRevalidateMap,
286
+ resolveAllSegmentsWithRevalidation: resolveAllSegmentsWithRevalidation,
287
+ findInterceptForRoute: findInterceptForRoute,
288
+ resolveInterceptEntry: resolveInterceptEntry,
289
+ resolveInterceptLoadersOnly: resolveInterceptLoadersOnly,
290
+ };
291
+ }
@@ -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
+ }