@rangojs/router 0.0.0-experimental.10 → 0.0.0-experimental.100

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 (329) hide show
  1. package/AGENTS.md +9 -0
  2. package/README.md +1037 -4
  3. package/dist/bin/rango.js +1619 -157
  4. package/dist/vite/index.js +5762 -2301
  5. package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  6. package/package.json +71 -63
  7. package/skills/breadcrumbs/SKILL.md +252 -0
  8. package/skills/cache-guide/SKILL.md +294 -0
  9. package/skills/caching/SKILL.md +93 -23
  10. package/skills/composability/SKILL.md +172 -0
  11. package/skills/debug-manifest/SKILL.md +12 -8
  12. package/skills/document-cache/SKILL.md +18 -16
  13. package/skills/fonts/SKILL.md +6 -4
  14. package/skills/handler-use/SKILL.md +364 -0
  15. package/skills/hooks/SKILL.md +367 -71
  16. package/skills/host-router/SKILL.md +218 -0
  17. package/skills/i18n/SKILL.md +276 -0
  18. package/skills/intercept/SKILL.md +176 -8
  19. package/skills/layout/SKILL.md +124 -3
  20. package/skills/links/SKILL.md +304 -25
  21. package/skills/loader/SKILL.md +474 -47
  22. package/skills/middleware/SKILL.md +207 -37
  23. package/skills/migrate-nextjs/SKILL.md +562 -0
  24. package/skills/migrate-react-router/SKILL.md +769 -0
  25. package/skills/mime-routes/SKILL.md +15 -11
  26. package/skills/parallel/SKILL.md +272 -1
  27. package/skills/prerender/SKILL.md +467 -65
  28. package/skills/rango/SKILL.md +89 -21
  29. package/skills/response-routes/SKILL.md +152 -91
  30. package/skills/route/SKILL.md +305 -14
  31. package/skills/router-setup/SKILL.md +210 -32
  32. package/skills/server-actions/SKILL.md +739 -0
  33. package/skills/streams-and-websockets/SKILL.md +283 -0
  34. package/skills/theme/SKILL.md +9 -8
  35. package/skills/typesafety/SKILL.md +333 -86
  36. package/skills/use-cache/SKILL.md +324 -0
  37. package/skills/view-transitions/SKILL.md +212 -0
  38. package/src/__internal.ts +102 -4
  39. package/src/bin/rango.ts +312 -15
  40. package/src/browser/action-coordinator.ts +97 -0
  41. package/src/browser/action-response-classifier.ts +99 -0
  42. package/src/browser/app-shell.ts +52 -0
  43. package/src/browser/app-version.ts +14 -0
  44. package/src/browser/event-controller.ts +136 -68
  45. package/src/browser/history-state.ts +80 -0
  46. package/src/browser/intercept-utils.ts +52 -0
  47. package/src/browser/link-interceptor.ts +24 -4
  48. package/src/browser/logging.ts +55 -0
  49. package/src/browser/merge-segment-loaders.ts +20 -12
  50. package/src/browser/navigation-bridge.ts +374 -561
  51. package/src/browser/navigation-client.ts +228 -70
  52. package/src/browser/navigation-store.ts +97 -55
  53. package/src/browser/navigation-transaction.ts +297 -0
  54. package/src/browser/network-error-handler.ts +61 -0
  55. package/src/browser/partial-update.ts +376 -315
  56. package/src/browser/prefetch/cache.ts +314 -0
  57. package/src/browser/prefetch/fetch.ts +282 -0
  58. package/src/browser/prefetch/observer.ts +65 -0
  59. package/src/browser/prefetch/policy.ts +48 -0
  60. package/src/browser/prefetch/queue.ts +191 -0
  61. package/src/browser/prefetch/resource-ready.ts +77 -0
  62. package/src/browser/rango-state.ts +152 -0
  63. package/src/browser/react/Link.tsx +255 -71
  64. package/src/browser/react/NavigationProvider.tsx +152 -24
  65. package/src/browser/react/context.ts +11 -0
  66. package/src/browser/react/filter-segment-order.ts +55 -0
  67. package/src/browser/react/index.ts +15 -12
  68. package/src/browser/react/location-state-shared.ts +95 -53
  69. package/src/browser/react/location-state.ts +60 -15
  70. package/src/browser/react/mount-context.ts +6 -1
  71. package/src/browser/react/nonce-context.ts +23 -0
  72. package/src/browser/react/shallow-equal.ts +27 -0
  73. package/src/browser/react/use-action.ts +29 -51
  74. package/src/browser/react/use-client-cache.ts +5 -3
  75. package/src/browser/react/use-handle.ts +30 -120
  76. package/src/browser/react/use-link-status.ts +6 -5
  77. package/src/browser/react/use-navigation.ts +44 -65
  78. package/src/browser/react/use-params.ts +78 -0
  79. package/src/browser/react/use-pathname.ts +47 -0
  80. package/src/browser/react/use-reverse.ts +99 -0
  81. package/src/browser/react/use-router.ts +83 -0
  82. package/src/browser/react/use-search-params.ts +56 -0
  83. package/src/browser/react/use-segments.ts +85 -99
  84. package/src/browser/response-adapter.ts +73 -0
  85. package/src/browser/rsc-router.tsx +246 -64
  86. package/src/browser/scroll-restoration.ts +127 -52
  87. package/src/browser/segment-reconciler.ts +243 -0
  88. package/src/browser/segment-structure-assert.ts +16 -0
  89. package/src/browser/server-action-bridge.ts +510 -603
  90. package/src/browser/shallow.ts +6 -1
  91. package/src/browser/types.ts +158 -48
  92. package/src/browser/validate-redirect-origin.ts +29 -0
  93. package/src/build/generate-manifest.ts +84 -23
  94. package/src/build/generate-route-types.ts +39 -828
  95. package/src/build/index.ts +4 -5
  96. package/src/build/route-trie.ts +85 -32
  97. package/src/build/route-types/ast-helpers.ts +25 -0
  98. package/src/build/route-types/ast-route-extraction.ts +98 -0
  99. package/src/build/route-types/codegen.ts +102 -0
  100. package/src/build/route-types/include-resolution.ts +418 -0
  101. package/src/build/route-types/param-extraction.ts +48 -0
  102. package/src/build/route-types/per-module-writer.ts +128 -0
  103. package/src/build/route-types/router-processing.ts +618 -0
  104. package/src/build/route-types/scan-filter.ts +85 -0
  105. package/src/build/runtime-discovery.ts +231 -0
  106. package/src/cache/background-task.ts +34 -0
  107. package/src/cache/cache-key-utils.ts +44 -0
  108. package/src/cache/cache-policy.ts +125 -0
  109. package/src/cache/cache-runtime.ts +342 -0
  110. package/src/cache/cache-scope.ts +167 -307
  111. package/src/cache/cf/cf-cache-store.ts +573 -21
  112. package/src/cache/cf/index.ts +13 -3
  113. package/src/cache/document-cache.ts +116 -77
  114. package/src/cache/handle-capture.ts +81 -0
  115. package/src/cache/handle-snapshot.ts +41 -0
  116. package/src/cache/index.ts +1 -15
  117. package/src/cache/memory-segment-store.ts +191 -13
  118. package/src/cache/profile-registry.ts +73 -0
  119. package/src/cache/read-through-swr.ts +134 -0
  120. package/src/cache/segment-codec.ts +256 -0
  121. package/src/cache/taint.ts +153 -0
  122. package/src/cache/types.ts +72 -122
  123. package/src/client.rsc.tsx +6 -1
  124. package/src/client.tsx +118 -302
  125. package/src/component-utils.ts +4 -4
  126. package/src/components/DefaultDocument.tsx +5 -1
  127. package/src/context-var.ts +156 -0
  128. package/src/debug.ts +19 -9
  129. package/src/errors.ts +77 -7
  130. package/src/handle.ts +55 -10
  131. package/src/handles/MetaTags.tsx +73 -20
  132. package/src/handles/breadcrumbs.ts +66 -0
  133. package/src/handles/index.ts +1 -0
  134. package/src/handles/meta.ts +30 -13
  135. package/src/host/cookie-handler.ts +21 -15
  136. package/src/host/errors.ts +8 -8
  137. package/src/host/index.ts +4 -7
  138. package/src/host/pattern-matcher.ts +27 -27
  139. package/src/host/router.ts +61 -39
  140. package/src/host/testing.ts +8 -8
  141. package/src/host/types.ts +15 -7
  142. package/src/host/utils.ts +1 -1
  143. package/src/href-client.ts +65 -45
  144. package/src/index.rsc.ts +138 -21
  145. package/src/index.ts +206 -51
  146. package/src/internal-debug.ts +11 -0
  147. package/src/loader.rsc.ts +25 -143
  148. package/src/loader.ts +27 -10
  149. package/src/network-error-thrower.tsx +3 -1
  150. package/src/outlet-context.ts +1 -1
  151. package/src/outlet-provider.tsx +45 -0
  152. package/src/prerender/param-hash.ts +4 -2
  153. package/src/prerender/store.ts +159 -13
  154. package/src/prerender.ts +397 -29
  155. package/src/response-utils.ts +28 -0
  156. package/src/reverse.ts +231 -121
  157. package/src/root-error-boundary.tsx +41 -29
  158. package/src/route-content-wrapper.tsx +7 -4
  159. package/src/route-definition/dsl-helpers.ts +1134 -0
  160. package/src/route-definition/helper-factories.ts +200 -0
  161. package/src/route-definition/helpers-types.ts +483 -0
  162. package/src/route-definition/index.ts +55 -0
  163. package/src/route-definition/redirect.ts +101 -0
  164. package/src/route-definition/resolve-handler-use.ts +155 -0
  165. package/src/route-definition.ts +1 -1431
  166. package/src/route-map-builder.ts +162 -123
  167. package/src/route-name.ts +53 -0
  168. package/src/route-types.ts +66 -9
  169. package/src/router/content-negotiation.ts +215 -0
  170. package/src/router/debug-manifest.ts +72 -0
  171. package/src/router/error-handling.ts +9 -9
  172. package/src/router/find-match.ts +160 -0
  173. package/src/router/handler-context.ts +418 -86
  174. package/src/router/intercept-resolution.ts +35 -20
  175. package/src/router/lazy-includes.ts +237 -0
  176. package/src/router/loader-resolution.ts +359 -128
  177. package/src/router/logging.ts +251 -0
  178. package/src/router/manifest.ts +98 -32
  179. package/src/router/match-api.ts +196 -261
  180. package/src/router/match-context.ts +4 -2
  181. package/src/router/match-handlers.ts +441 -0
  182. package/src/router/match-middleware/background-revalidation.ts +108 -93
  183. package/src/router/match-middleware/cache-lookup.ts +415 -86
  184. package/src/router/match-middleware/cache-store.ts +91 -29
  185. package/src/router/match-middleware/intercept-resolution.ts +48 -21
  186. package/src/router/match-middleware/segment-resolution.ts +73 -9
  187. package/src/router/match-pipelines.ts +10 -45
  188. package/src/router/match-result.ts +154 -35
  189. package/src/router/metrics.ts +240 -15
  190. package/src/router/middleware-cookies.ts +55 -0
  191. package/src/router/middleware-types.ts +209 -0
  192. package/src/router/middleware.ts +373 -371
  193. package/src/router/navigation-snapshot.ts +182 -0
  194. package/src/router/pattern-matching.ts +292 -52
  195. package/src/router/prerender-match.ts +502 -0
  196. package/src/router/preview-match.ts +98 -0
  197. package/src/router/request-classification.ts +310 -0
  198. package/src/router/revalidation.ts +152 -39
  199. package/src/router/route-snapshot.ts +245 -0
  200. package/src/router/router-context.ts +41 -21
  201. package/src/router/router-interfaces.ts +484 -0
  202. package/src/router/router-options.ts +618 -0
  203. package/src/router/router-registry.ts +24 -0
  204. package/src/router/segment-resolution/fresh.ts +756 -0
  205. package/src/router/segment-resolution/helpers.ts +268 -0
  206. package/src/router/segment-resolution/loader-cache.ts +199 -0
  207. package/src/router/segment-resolution/revalidation.ts +1407 -0
  208. package/src/router/segment-resolution/static-store.ts +67 -0
  209. package/src/router/segment-resolution.ts +21 -1315
  210. package/src/router/segment-wrappers.ts +291 -0
  211. package/src/router/substitute-pattern-params.ts +56 -0
  212. package/src/router/telemetry-otel.ts +299 -0
  213. package/src/router/telemetry.ts +300 -0
  214. package/src/router/timeout.ts +148 -0
  215. package/src/router/trie-matching.ts +111 -39
  216. package/src/router/types.ts +17 -9
  217. package/src/router/url-params.ts +49 -0
  218. package/src/router.ts +642 -2011
  219. package/src/rsc/handler-context.ts +45 -0
  220. package/src/rsc/handler.ts +864 -1114
  221. package/src/rsc/helpers.ts +181 -19
  222. package/src/rsc/index.ts +0 -20
  223. package/src/rsc/loader-fetch.ts +229 -0
  224. package/src/rsc/manifest-init.ts +90 -0
  225. package/src/rsc/nonce.ts +14 -0
  226. package/src/rsc/origin-guard.ts +141 -0
  227. package/src/rsc/progressive-enhancement.ts +395 -0
  228. package/src/rsc/response-error.ts +37 -0
  229. package/src/rsc/response-route-handler.ts +360 -0
  230. package/src/rsc/rsc-rendering.ts +256 -0
  231. package/src/rsc/runtime-warnings.ts +42 -0
  232. package/src/rsc/server-action.ts +360 -0
  233. package/src/rsc/ssr-setup.ts +128 -0
  234. package/src/rsc/types.ts +52 -11
  235. package/src/search-params.ts +230 -0
  236. package/src/segment-content-promise.ts +67 -0
  237. package/src/segment-loader-promise.ts +122 -0
  238. package/src/segment-system.tsx +187 -38
  239. package/src/server/context.ts +333 -59
  240. package/src/server/cookie-store.ts +190 -0
  241. package/src/server/fetchable-loader-store.ts +37 -0
  242. package/src/server/handle-store.ts +113 -15
  243. package/src/server/loader-registry.ts +24 -64
  244. package/src/server/request-context.ts +603 -109
  245. package/src/server.ts +35 -155
  246. package/src/ssr/index.tsx +107 -30
  247. package/src/static-handler.ts +126 -0
  248. package/src/theme/ThemeProvider.tsx +21 -15
  249. package/src/theme/ThemeScript.tsx +5 -5
  250. package/src/theme/constants.ts +5 -2
  251. package/src/theme/index.ts +4 -14
  252. package/src/theme/theme-context.ts +4 -30
  253. package/src/theme/theme-script.ts +21 -18
  254. package/src/types/boundaries.ts +158 -0
  255. package/src/types/cache-types.ts +198 -0
  256. package/src/types/error-types.ts +192 -0
  257. package/src/types/global-namespace.ts +100 -0
  258. package/src/types/handler-context.ts +764 -0
  259. package/src/types/index.ts +88 -0
  260. package/src/types/loader-types.ts +209 -0
  261. package/src/types/request-scope.ts +126 -0
  262. package/src/types/route-config.ts +170 -0
  263. package/src/types/route-entry.ts +120 -0
  264. package/src/types/segments.ts +167 -0
  265. package/src/types.ts +1 -1757
  266. package/src/urls/include-helper.ts +207 -0
  267. package/src/urls/index.ts +53 -0
  268. package/src/urls/path-helper-types.ts +372 -0
  269. package/src/urls/path-helper.ts +364 -0
  270. package/src/urls/pattern-types.ts +107 -0
  271. package/src/urls/response-types.ts +108 -0
  272. package/src/urls/type-extraction.ts +372 -0
  273. package/src/urls/urls-function.ts +98 -0
  274. package/src/urls.ts +1 -1282
  275. package/src/use-loader.tsx +161 -81
  276. package/src/vite/debug.ts +184 -0
  277. package/src/vite/discovery/bundle-postprocess.ts +181 -0
  278. package/src/vite/discovery/discover-routers.ts +376 -0
  279. package/src/vite/discovery/gate-state.ts +171 -0
  280. package/src/vite/discovery/prerender-collection.ts +486 -0
  281. package/src/vite/discovery/route-types-writer.ts +258 -0
  282. package/src/vite/discovery/self-gen-tracking.ts +73 -0
  283. package/src/vite/discovery/state.ts +117 -0
  284. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  285. package/src/vite/index.ts +15 -2063
  286. package/src/vite/plugin-types.ts +103 -0
  287. package/src/vite/plugins/cjs-to-esm.ts +98 -0
  288. package/src/vite/plugins/client-ref-dedup.ts +131 -0
  289. package/src/vite/plugins/client-ref-hashing.ts +117 -0
  290. package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
  291. package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
  292. package/src/vite/plugins/cloudflare-protocol-stub.ts +214 -0
  293. package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +107 -64
  294. package/src/vite/plugins/expose-id-utils.ts +299 -0
  295. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  296. package/src/vite/plugins/expose-ids/handler-transform.ts +209 -0
  297. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  298. package/src/vite/plugins/expose-ids/router-transform.ts +127 -0
  299. package/src/vite/plugins/expose-ids/types.ts +45 -0
  300. package/src/vite/plugins/expose-internal-ids.ts +816 -0
  301. package/src/vite/plugins/performance-tracks.ts +96 -0
  302. package/src/vite/plugins/refresh-cmd.ts +127 -0
  303. package/src/vite/plugins/use-cache-transform.ts +336 -0
  304. package/src/vite/plugins/version-injector.ts +109 -0
  305. package/src/vite/plugins/version-plugin.ts +266 -0
  306. package/src/vite/{virtual-entries.ts → plugins/virtual-entries.ts} +23 -14
  307. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  308. package/src/vite/rango.ts +497 -0
  309. package/src/vite/router-discovery.ts +1423 -0
  310. package/src/vite/utils/ast-handler-extract.ts +517 -0
  311. package/src/vite/utils/banner.ts +36 -0
  312. package/src/vite/utils/bundle-analysis.ts +137 -0
  313. package/src/vite/utils/manifest-utils.ts +70 -0
  314. package/src/vite/utils/package-resolution.ts +161 -0
  315. package/src/vite/utils/prerender-utils.ts +222 -0
  316. package/src/vite/utils/shared-utils.ts +170 -0
  317. package/CLAUDE.md +0 -43
  318. package/src/browser/lru-cache.ts +0 -69
  319. package/src/browser/request-controller.ts +0 -164
  320. package/src/cache/memory-store.ts +0 -253
  321. package/src/href-context.ts +0 -33
  322. package/src/router.gen.ts +0 -6
  323. package/src/urls.gen.ts +0 -8
  324. package/src/vite/expose-handle-id.ts +0 -209
  325. package/src/vite/expose-loader-id.ts +0 -426
  326. package/src/vite/expose-location-state-id.ts +0 -177
  327. package/src/vite/expose-prerender-handler-id.ts +0 -429
  328. package/src/vite/package-resolution.ts +0 -125
  329. /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,56 @@
1
+ import { encodePathSegment } from "./url-params.js";
2
+
3
+ /**
4
+ * Substitute `:param` placeholders in a route pattern with values from
5
+ * `params`. Two-pass: optional params (`:name?`) first so absent values
6
+ * collapse cleanly, then required params (throws on missing). Constraint
7
+ * syntax (`:name(en|gb)`) is stripped from the result. Trailing-slash
8
+ * patterns like `/blog/` are preserved unless an optional segment was
9
+ * actually omitted.
10
+ *
11
+ * Shared by `ctx.reverse()` (server), `createReverse()` (typed runtime
12
+ * helper), and `useReverse()` (client hook). The behavior must stay
13
+ * identical across all three call sites.
14
+ */
15
+ export function substitutePatternParams(
16
+ pattern: string,
17
+ params: Record<string, string | undefined>,
18
+ routeName: string,
19
+ ): string {
20
+ let result = pattern;
21
+ let hadOmittedOptional = false;
22
+
23
+ result = result.replace(
24
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(\?)/g,
25
+ (_match, key) => {
26
+ const value = params[key as string];
27
+ // The matcher omits absent optional params (so `value` is `undefined`
28
+ // here), but caller-supplied params or `getParams()` shapes may still
29
+ // pass `""` explicitly. Treat both as the absent form.
30
+ if (value === undefined || value === "") {
31
+ hadOmittedOptional = true;
32
+ return "";
33
+ }
34
+ return encodePathSegment(value);
35
+ },
36
+ );
37
+
38
+ result = result.replace(
39
+ /:([a-zA-Z_][a-zA-Z0-9_]*)(\([^)]*\))?(?!\?)/g,
40
+ (_match, key) => {
41
+ const value = params[key as string];
42
+ if (value === undefined) {
43
+ throw new Error(`Missing param "${key}" for route "${routeName}"`);
44
+ }
45
+ return encodePathSegment(value);
46
+ },
47
+ );
48
+
49
+ if (hadOmittedOptional) {
50
+ const hadTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
51
+ result = result.replace(/\/\/+/g, "/").replace(/\/+$/, "") || "/";
52
+ if (hadTrailingSlash && !result.endsWith("/")) result += "/";
53
+ }
54
+
55
+ return result;
56
+ }
@@ -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
+ }