@rangojs/router 0.0.0-experimental.0f44aca1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/AGENTS.md +5 -0
  2. package/README.md +899 -0
  3. package/dist/bin/rango.js +1601 -0
  4. package/dist/vite/index.js +5214 -0
  5. package/package.json +176 -0
  6. package/skills/breadcrumbs/SKILL.md +250 -0
  7. package/skills/cache-guide/SKILL.md +262 -0
  8. package/skills/caching/SKILL.md +220 -0
  9. package/skills/composability/SKILL.md +172 -0
  10. package/skills/debug-manifest/SKILL.md +112 -0
  11. package/skills/document-cache/SKILL.md +182 -0
  12. package/skills/fonts/SKILL.md +167 -0
  13. package/skills/hooks/SKILL.md +704 -0
  14. package/skills/host-router/SKILL.md +218 -0
  15. package/skills/intercept/SKILL.md +313 -0
  16. package/skills/layout/SKILL.md +310 -0
  17. package/skills/links/SKILL.md +239 -0
  18. package/skills/loader/SKILL.md +596 -0
  19. package/skills/middleware/SKILL.md +339 -0
  20. package/skills/mime-routes/SKILL.md +128 -0
  21. package/skills/parallel/SKILL.md +305 -0
  22. package/skills/prerender/SKILL.md +643 -0
  23. package/skills/rango/SKILL.md +118 -0
  24. package/skills/response-routes/SKILL.md +411 -0
  25. package/skills/route/SKILL.md +385 -0
  26. package/skills/router-setup/SKILL.md +439 -0
  27. package/skills/tailwind/SKILL.md +129 -0
  28. package/skills/theme/SKILL.md +79 -0
  29. package/skills/typesafety/SKILL.md +623 -0
  30. package/skills/use-cache/SKILL.md +324 -0
  31. package/src/__internal.ts +273 -0
  32. package/src/bin/rango.ts +321 -0
  33. package/src/browser/action-coordinator.ts +97 -0
  34. package/src/browser/action-response-classifier.ts +99 -0
  35. package/src/browser/event-controller.ts +899 -0
  36. package/src/browser/history-state.ts +80 -0
  37. package/src/browser/index.ts +18 -0
  38. package/src/browser/intercept-utils.ts +52 -0
  39. package/src/browser/link-interceptor.ts +141 -0
  40. package/src/browser/logging.ts +55 -0
  41. package/src/browser/merge-segment-loaders.ts +134 -0
  42. package/src/browser/navigation-bridge.ts +645 -0
  43. package/src/browser/navigation-client.ts +215 -0
  44. package/src/browser/navigation-store.ts +806 -0
  45. package/src/browser/navigation-transaction.ts +295 -0
  46. package/src/browser/network-error-handler.ts +61 -0
  47. package/src/browser/partial-update.ts +550 -0
  48. package/src/browser/prefetch/cache.ts +146 -0
  49. package/src/browser/prefetch/fetch.ts +135 -0
  50. package/src/browser/prefetch/observer.ts +65 -0
  51. package/src/browser/prefetch/policy.ts +42 -0
  52. package/src/browser/prefetch/queue.ts +88 -0
  53. package/src/browser/rango-state.ts +112 -0
  54. package/src/browser/react/Link.tsx +360 -0
  55. package/src/browser/react/NavigationProvider.tsx +386 -0
  56. package/src/browser/react/ScrollRestoration.tsx +94 -0
  57. package/src/browser/react/context.ts +59 -0
  58. package/src/browser/react/filter-segment-order.ts +11 -0
  59. package/src/browser/react/index.ts +52 -0
  60. package/src/browser/react/location-state-shared.ts +162 -0
  61. package/src/browser/react/location-state.ts +107 -0
  62. package/src/browser/react/mount-context.ts +37 -0
  63. package/src/browser/react/nonce-context.ts +23 -0
  64. package/src/browser/react/shallow-equal.ts +27 -0
  65. package/src/browser/react/use-action.ts +218 -0
  66. package/src/browser/react/use-client-cache.ts +58 -0
  67. package/src/browser/react/use-handle.ts +162 -0
  68. package/src/browser/react/use-href.tsx +40 -0
  69. package/src/browser/react/use-link-status.ts +135 -0
  70. package/src/browser/react/use-mount.ts +31 -0
  71. package/src/browser/react/use-navigation.ts +99 -0
  72. package/src/browser/react/use-params.ts +65 -0
  73. package/src/browser/react/use-pathname.ts +47 -0
  74. package/src/browser/react/use-router.ts +63 -0
  75. package/src/browser/react/use-search-params.ts +56 -0
  76. package/src/browser/react/use-segments.ts +171 -0
  77. package/src/browser/response-adapter.ts +73 -0
  78. package/src/browser/rsc-router.tsx +431 -0
  79. package/src/browser/scroll-restoration.ts +400 -0
  80. package/src/browser/segment-reconciler.ts +216 -0
  81. package/src/browser/segment-structure-assert.ts +83 -0
  82. package/src/browser/server-action-bridge.ts +667 -0
  83. package/src/browser/shallow.ts +40 -0
  84. package/src/browser/types.ts +538 -0
  85. package/src/browser/validate-redirect-origin.ts +29 -0
  86. package/src/build/generate-manifest.ts +438 -0
  87. package/src/build/generate-route-types.ts +36 -0
  88. package/src/build/index.ts +35 -0
  89. package/src/build/route-trie.ts +265 -0
  90. package/src/build/route-types/ast-helpers.ts +25 -0
  91. package/src/build/route-types/ast-route-extraction.ts +98 -0
  92. package/src/build/route-types/codegen.ts +102 -0
  93. package/src/build/route-types/include-resolution.ts +411 -0
  94. package/src/build/route-types/param-extraction.ts +48 -0
  95. package/src/build/route-types/per-module-writer.ts +128 -0
  96. package/src/build/route-types/router-processing.ts +469 -0
  97. package/src/build/route-types/scan-filter.ts +78 -0
  98. package/src/build/runtime-discovery.ts +231 -0
  99. package/src/cache/background-task.ts +34 -0
  100. package/src/cache/cache-key-utils.ts +44 -0
  101. package/src/cache/cache-policy.ts +125 -0
  102. package/src/cache/cache-runtime.ts +338 -0
  103. package/src/cache/cache-scope.ts +382 -0
  104. package/src/cache/cf/cf-cache-store.ts +540 -0
  105. package/src/cache/cf/index.ts +25 -0
  106. package/src/cache/document-cache.ts +369 -0
  107. package/src/cache/handle-capture.ts +81 -0
  108. package/src/cache/handle-snapshot.ts +41 -0
  109. package/src/cache/index.ts +43 -0
  110. package/src/cache/memory-segment-store.ts +328 -0
  111. package/src/cache/profile-registry.ts +73 -0
  112. package/src/cache/read-through-swr.ts +134 -0
  113. package/src/cache/segment-codec.ts +256 -0
  114. package/src/cache/taint.ts +98 -0
  115. package/src/cache/types.ts +342 -0
  116. package/src/client.rsc.tsx +85 -0
  117. package/src/client.tsx +601 -0
  118. package/src/component-utils.ts +76 -0
  119. package/src/components/DefaultDocument.tsx +27 -0
  120. package/src/context-var.ts +86 -0
  121. package/src/debug.ts +243 -0
  122. package/src/default-error-boundary.tsx +88 -0
  123. package/src/deps/browser.ts +8 -0
  124. package/src/deps/html-stream-client.ts +2 -0
  125. package/src/deps/html-stream-server.ts +2 -0
  126. package/src/deps/rsc.ts +10 -0
  127. package/src/deps/ssr.ts +2 -0
  128. package/src/errors.ts +365 -0
  129. package/src/handle.ts +135 -0
  130. package/src/handles/MetaTags.tsx +246 -0
  131. package/src/handles/breadcrumbs.ts +66 -0
  132. package/src/handles/index.ts +7 -0
  133. package/src/handles/meta.ts +264 -0
  134. package/src/host/cookie-handler.ts +165 -0
  135. package/src/host/errors.ts +97 -0
  136. package/src/host/index.ts +53 -0
  137. package/src/host/pattern-matcher.ts +214 -0
  138. package/src/host/router.ts +352 -0
  139. package/src/host/testing.ts +79 -0
  140. package/src/host/types.ts +146 -0
  141. package/src/host/utils.ts +25 -0
  142. package/src/href-client.ts +222 -0
  143. package/src/index.rsc.ts +233 -0
  144. package/src/index.ts +277 -0
  145. package/src/internal-debug.ts +11 -0
  146. package/src/loader.rsc.ts +89 -0
  147. package/src/loader.ts +64 -0
  148. package/src/network-error-thrower.tsx +23 -0
  149. package/src/outlet-context.ts +15 -0
  150. package/src/outlet-provider.tsx +45 -0
  151. package/src/prerender/param-hash.ts +37 -0
  152. package/src/prerender/store.ts +185 -0
  153. package/src/prerender.ts +463 -0
  154. package/src/reverse.ts +330 -0
  155. package/src/root-error-boundary.tsx +289 -0
  156. package/src/route-content-wrapper.tsx +196 -0
  157. package/src/route-definition/dsl-helpers.ts +934 -0
  158. package/src/route-definition/helper-factories.ts +200 -0
  159. package/src/route-definition/helpers-types.ts +430 -0
  160. package/src/route-definition/index.ts +52 -0
  161. package/src/route-definition/redirect.ts +93 -0
  162. package/src/route-definition.ts +1 -0
  163. package/src/route-map-builder.ts +275 -0
  164. package/src/route-name.ts +53 -0
  165. package/src/route-types.ts +259 -0
  166. package/src/router/content-negotiation.ts +116 -0
  167. package/src/router/debug-manifest.ts +72 -0
  168. package/src/router/error-handling.ts +287 -0
  169. package/src/router/find-match.ts +158 -0
  170. package/src/router/handler-context.ts +451 -0
  171. package/src/router/intercept-resolution.ts +395 -0
  172. package/src/router/lazy-includes.ts +234 -0
  173. package/src/router/loader-resolution.ts +420 -0
  174. package/src/router/logging.ts +248 -0
  175. package/src/router/manifest.ts +267 -0
  176. package/src/router/match-api.ts +620 -0
  177. package/src/router/match-context.ts +266 -0
  178. package/src/router/match-handlers.ts +440 -0
  179. package/src/router/match-middleware/background-revalidation.ts +223 -0
  180. package/src/router/match-middleware/cache-lookup.ts +634 -0
  181. package/src/router/match-middleware/cache-store.ts +295 -0
  182. package/src/router/match-middleware/index.ts +81 -0
  183. package/src/router/match-middleware/intercept-resolution.ts +306 -0
  184. package/src/router/match-middleware/segment-resolution.ts +192 -0
  185. package/src/router/match-pipelines.ts +179 -0
  186. package/src/router/match-result.ts +219 -0
  187. package/src/router/metrics.ts +282 -0
  188. package/src/router/middleware-cookies.ts +55 -0
  189. package/src/router/middleware-types.ts +222 -0
  190. package/src/router/middleware.ts +748 -0
  191. package/src/router/pattern-matching.ts +563 -0
  192. package/src/router/prerender-match.ts +402 -0
  193. package/src/router/preview-match.ts +170 -0
  194. package/src/router/revalidation.ts +289 -0
  195. package/src/router/router-context.ts +316 -0
  196. package/src/router/router-interfaces.ts +452 -0
  197. package/src/router/router-options.ts +592 -0
  198. package/src/router/router-registry.ts +24 -0
  199. package/src/router/segment-resolution/fresh.ts +570 -0
  200. package/src/router/segment-resolution/helpers.ts +263 -0
  201. package/src/router/segment-resolution/loader-cache.ts +198 -0
  202. package/src/router/segment-resolution/revalidation.ts +1239 -0
  203. package/src/router/segment-resolution/static-store.ts +67 -0
  204. package/src/router/segment-resolution.ts +21 -0
  205. package/src/router/segment-wrappers.ts +289 -0
  206. package/src/router/telemetry-otel.ts +299 -0
  207. package/src/router/telemetry.ts +300 -0
  208. package/src/router/timeout.ts +148 -0
  209. package/src/router/trie-matching.ts +239 -0
  210. package/src/router/types.ts +170 -0
  211. package/src/router.ts +1002 -0
  212. package/src/rsc/handler-context.ts +45 -0
  213. package/src/rsc/handler.ts +1089 -0
  214. package/src/rsc/helpers.ts +198 -0
  215. package/src/rsc/index.ts +36 -0
  216. package/src/rsc/loader-fetch.ts +209 -0
  217. package/src/rsc/manifest-init.ts +86 -0
  218. package/src/rsc/nonce.ts +32 -0
  219. package/src/rsc/origin-guard.ts +141 -0
  220. package/src/rsc/progressive-enhancement.ts +379 -0
  221. package/src/rsc/response-error.ts +37 -0
  222. package/src/rsc/response-route-handler.ts +347 -0
  223. package/src/rsc/rsc-rendering.ts +235 -0
  224. package/src/rsc/runtime-warnings.ts +42 -0
  225. package/src/rsc/server-action.ts +348 -0
  226. package/src/rsc/ssr-setup.ts +128 -0
  227. package/src/rsc/types.ts +263 -0
  228. package/src/search-params.ts +230 -0
  229. package/src/segment-system.tsx +454 -0
  230. package/src/server/context.ts +591 -0
  231. package/src/server/cookie-store.ts +190 -0
  232. package/src/server/fetchable-loader-store.ts +37 -0
  233. package/src/server/handle-store.ts +308 -0
  234. package/src/server/loader-registry.ts +133 -0
  235. package/src/server/request-context.ts +914 -0
  236. package/src/server/root-layout.tsx +10 -0
  237. package/src/server/tsconfig.json +14 -0
  238. package/src/server.ts +51 -0
  239. package/src/ssr/index.tsx +365 -0
  240. package/src/static-handler.ts +114 -0
  241. package/src/theme/ThemeProvider.tsx +297 -0
  242. package/src/theme/ThemeScript.tsx +61 -0
  243. package/src/theme/constants.ts +62 -0
  244. package/src/theme/index.ts +48 -0
  245. package/src/theme/theme-context.ts +44 -0
  246. package/src/theme/theme-script.ts +155 -0
  247. package/src/theme/types.ts +182 -0
  248. package/src/theme/use-theme.ts +44 -0
  249. package/src/types/boundaries.ts +158 -0
  250. package/src/types/cache-types.ts +198 -0
  251. package/src/types/error-types.ts +192 -0
  252. package/src/types/global-namespace.ts +100 -0
  253. package/src/types/handler-context.ts +687 -0
  254. package/src/types/index.ts +88 -0
  255. package/src/types/loader-types.ts +183 -0
  256. package/src/types/route-config.ts +170 -0
  257. package/src/types/route-entry.ts +102 -0
  258. package/src/types/segments.ts +148 -0
  259. package/src/types.ts +1 -0
  260. package/src/urls/include-helper.ts +197 -0
  261. package/src/urls/index.ts +53 -0
  262. package/src/urls/path-helper-types.ts +339 -0
  263. package/src/urls/path-helper.ts +329 -0
  264. package/src/urls/pattern-types.ts +95 -0
  265. package/src/urls/response-types.ts +106 -0
  266. package/src/urls/type-extraction.ts +372 -0
  267. package/src/urls/urls-function.ts +98 -0
  268. package/src/urls.ts +1 -0
  269. package/src/use-loader.tsx +354 -0
  270. package/src/vite/discovery/bundle-postprocess.ts +184 -0
  271. package/src/vite/discovery/discover-routers.ts +344 -0
  272. package/src/vite/discovery/prerender-collection.ts +385 -0
  273. package/src/vite/discovery/route-types-writer.ts +258 -0
  274. package/src/vite/discovery/self-gen-tracking.ts +47 -0
  275. package/src/vite/discovery/state.ts +110 -0
  276. package/src/vite/discovery/virtual-module-codegen.ts +203 -0
  277. package/src/vite/index.ts +16 -0
  278. package/src/vite/plugin-types.ts +131 -0
  279. package/src/vite/plugins/cjs-to-esm.ts +93 -0
  280. package/src/vite/plugins/client-ref-dedup.ts +115 -0
  281. package/src/vite/plugins/client-ref-hashing.ts +105 -0
  282. package/src/vite/plugins/expose-action-id.ts +365 -0
  283. package/src/vite/plugins/expose-id-utils.ts +287 -0
  284. package/src/vite/plugins/expose-ids/export-analysis.ts +296 -0
  285. package/src/vite/plugins/expose-ids/handler-transform.ts +179 -0
  286. package/src/vite/plugins/expose-ids/loader-transform.ts +74 -0
  287. package/src/vite/plugins/expose-ids/router-transform.ts +110 -0
  288. package/src/vite/plugins/expose-ids/types.ts +45 -0
  289. package/src/vite/plugins/expose-internal-ids.ts +569 -0
  290. package/src/vite/plugins/refresh-cmd.ts +65 -0
  291. package/src/vite/plugins/use-cache-transform.ts +323 -0
  292. package/src/vite/plugins/version-injector.ts +83 -0
  293. package/src/vite/plugins/version-plugin.ts +254 -0
  294. package/src/vite/plugins/version.d.ts +12 -0
  295. package/src/vite/plugins/virtual-entries.ts +123 -0
  296. package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
  297. package/src/vite/rango.ts +510 -0
  298. package/src/vite/router-discovery.ts +785 -0
  299. package/src/vite/utils/ast-handler-extract.ts +517 -0
  300. package/src/vite/utils/banner.ts +36 -0
  301. package/src/vite/utils/bundle-analysis.ts +137 -0
  302. package/src/vite/utils/manifest-utils.ts +70 -0
  303. package/src/vite/utils/package-resolution.ts +121 -0
  304. package/src/vite/utils/prerender-utils.ts +189 -0
  305. package/src/vite/utils/shared-utils.ts +169 -0
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Static Store
3
+ *
4
+ * Lazy-initialized static store for production Static handler interception.
5
+ * Manages the build-time static manifest lookup for pre-rendered components.
6
+ */
7
+
8
+ import type { ReactNode } from "react";
9
+ import { _getRequestContext } from "../../server/request-context.js";
10
+ import type { StaticStore } from "../../prerender/store.js";
11
+
12
+ // Lazy-initialized static store for production Static handler interception.
13
+ // Remains undefined until first check; null means checked but no manifest.
14
+ // When no __STATIC_MANIFEST exists (dev mode), set to null eagerly to avoid
15
+ // the dynamic import() in ensureStaticDeps() which disrupts AsyncLocalStorage
16
+ // in workerd/Cloudflare runtime.
17
+ let _staticStore: StaticStore | null | undefined =
18
+ typeof globalThis !== "undefined" && globalThis.__STATIC_MANIFEST
19
+ ? undefined
20
+ : null;
21
+ let _deserializeComponent: ((encoded: string) => Promise<unknown>) | undefined;
22
+
23
+ async function ensureStaticDeps(): Promise<void> {
24
+ if (_staticStore === undefined) {
25
+ const { createStaticStore } = await import("../../prerender/store.js");
26
+ _staticStore = createStaticStore();
27
+ }
28
+ if (!_deserializeComponent && _staticStore) {
29
+ const { deserializeComponent } =
30
+ await import("../../cache/segment-codec.js");
31
+ _deserializeComponent = deserializeComponent;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Try to load a pre-rendered Static component from the build-time store.
37
+ * Returns the deserialized React element, or undefined if not available.
38
+ * Also replays any handle data captured at build time into the request's HandleStore.
39
+ *
40
+ * @param handlerId - The handler's $$id used to look up the static store entry.
41
+ * @param segmentId - The runtime segment shortCode. Handle data is replayed under
42
+ * this key so that useHandle's segmentOrder matching works correctly.
43
+ */
44
+ export async function tryStaticLookup(
45
+ handlerId: string,
46
+ segmentId: string,
47
+ ): Promise<ReactNode | undefined> {
48
+ // Fast path: already checked and no manifest available (dev mode or no Static handlers).
49
+ // Avoids await which can disrupt AsyncLocalStorage in workerd/Cloudflare runtime.
50
+ if (_staticStore === null) return undefined;
51
+ await ensureStaticDeps();
52
+ if (!_staticStore || !_deserializeComponent) return undefined;
53
+ const entry = await _staticStore.get(handlerId);
54
+ if (!entry) return undefined;
55
+
56
+ // Replay handle data captured during build-time rendering.
57
+ // The data was keyed by handlerId at build time; replay under segmentId
58
+ // so it matches the segment order used by useHandle on the client.
59
+ if (entry.handles && Object.keys(entry.handles).length > 0) {
60
+ const handleStore = _getRequestContext()?._handleStore;
61
+ if (handleStore) {
62
+ handleStore.replaySegmentData(segmentId, entry.handles);
63
+ }
64
+ }
65
+
66
+ return _deserializeComponent(entry.encoded) as Promise<ReactNode>;
67
+ }
@@ -0,0 +1,21 @@
1
+ // Barrel re-export -- see segment-resolution/ for implementations.
2
+ export { handleHandlerResult } from "./segment-resolution/helpers.js";
3
+ export {
4
+ resolveLoaders,
5
+ type ResolveSegmentOptions,
6
+ resolveSegment,
7
+ resolveOrphanLayout,
8
+ resolveParallelEntry,
9
+ resolveAllSegments,
10
+ resolveLoadersOnly,
11
+ } from "./segment-resolution/fresh.js";
12
+ export {
13
+ resolveLoadersWithRevalidation,
14
+ resolveLoadersOnlyWithRevalidation,
15
+ buildEntryRevalidateMap,
16
+ resolveParallelSegmentsWithRevalidation,
17
+ resolveEntryHandlerWithRevalidation,
18
+ resolveSegmentWithRevalidation,
19
+ resolveOrphanLayoutWithRevalidation,
20
+ resolveAllSegmentsWithRevalidation,
21
+ } from "./segment-resolution/revalidation.js";
@@ -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
+ }