@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
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.
- package/AGENTS.md +8 -0
- package/README.md +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +117 -23
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +55 -28
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +36 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +31 -23
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +96 -12
- package/src/index.ts +94 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- package/src/redirect-origin.ts +100 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +34 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +82 -55
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -11
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +109 -118
- package/src/vite/router-discovery.ts +718 -119
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -1,20 +1,48 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenTelemetry
|
|
2
|
+
* OpenTelemetry adapters for the router.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Two adapters, two surfaces — matching the split in instrument.ts:
|
|
5
|
+
*
|
|
6
|
+
* - createOTelTracing(tracer): the OTel adapter for the `tracing` SLOT — the
|
|
7
|
+
* canonical phase-span layer. It bridges observePhase's callback boundary
|
|
8
|
+
* onto OTel's callback-bound `startActiveSpan`, so the router's phase spans
|
|
9
|
+
* (rango.request/middleware/loader/render/ssr) nest by async context and the
|
|
10
|
+
* loader's own OTel spans (db/fetch) land under rango.loader. This is the
|
|
11
|
+
* OTel equivalent of createCloudflareTracing — pass it to
|
|
12
|
+
* `createRouter({ tracing })`.
|
|
13
|
+
*
|
|
14
|
+
* - createOTelSink(tracer): the OTel adapter for the `telemetry` SLOT — a
|
|
15
|
+
* TelemetrySink for the EVENT-shaped facts (handler errors, cache decisions,
|
|
16
|
+
* revalidation decisions, timeouts, origin rejections). It emits one instant
|
|
17
|
+
* OTel span per fact. It deliberately does NOT emit request/loader phase
|
|
18
|
+
* spans: those are owned by the tracing slot (createOTelTracing) so the two
|
|
19
|
+
* layers don't produce duplicate rango.request / rango.loader spans.
|
|
20
|
+
*
|
|
21
|
+
* The core router stays OTel-agnostic — these adapters accept a standard OTel
|
|
22
|
+
* Tracer (structurally typed, no import needed) and bridge the gap.
|
|
7
23
|
*
|
|
8
24
|
* Usage:
|
|
9
25
|
* import { trace } from "@opentelemetry/api";
|
|
10
|
-
* import { createOTelSink } from "@rangojs/router";
|
|
26
|
+
* import { createRouter, createOTelTracing, createOTelSink } from "@rangojs/router";
|
|
11
27
|
*
|
|
28
|
+
* const tracer = trace.getTracer("my-app");
|
|
12
29
|
* const router = createRouter({
|
|
13
|
-
*
|
|
30
|
+
* tracing: createOTelTracing(tracer), // phase spans (callback-bound)
|
|
31
|
+
* telemetry: createOTelSink(tracer), // discrete-fact instant spans
|
|
14
32
|
* });
|
|
33
|
+
*
|
|
34
|
+
* Faithful nesting requires an OTel async context manager
|
|
35
|
+
* (AsyncLocalStorageContextManager) configured in your OTel setup — standard for
|
|
36
|
+
* any startActiveSpan-based instrumentation.
|
|
15
37
|
*/
|
|
16
38
|
|
|
17
39
|
import type { TelemetrySink, TelemetryEvent } from "./telemetry.js";
|
|
40
|
+
import { runThenSettle } from "./tracing.js";
|
|
41
|
+
import type {
|
|
42
|
+
RouterTracingConfig,
|
|
43
|
+
SpanRunner,
|
|
44
|
+
TracePhaseToggles,
|
|
45
|
+
} from "./tracing.js";
|
|
18
46
|
|
|
19
47
|
// ---------------------------------------------------------------------------
|
|
20
48
|
// Minimal OTel-compatible types (structurally typed, no import needed)
|
|
@@ -22,21 +50,21 @@ import type { TelemetrySink, TelemetryEvent } from "./telemetry.js";
|
|
|
22
50
|
|
|
23
51
|
/**
|
|
24
52
|
* Minimal Span interface compatible with @opentelemetry/api Span.
|
|
25
|
-
* Only the methods used by the
|
|
53
|
+
* Only the methods used by the adapters are declared.
|
|
26
54
|
*/
|
|
27
55
|
export interface OTelSpan {
|
|
28
56
|
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
57
|
setStatus(status: { code: number; message?: string }): OTelSpan | void;
|
|
34
58
|
recordException(exception: Error): void;
|
|
35
59
|
end(): void;
|
|
36
60
|
}
|
|
37
61
|
|
|
38
62
|
/**
|
|
39
|
-
* Minimal Tracer interface
|
|
63
|
+
* Minimal Tracer interface for the EVENT sink (createOTelSink): only `startSpan`
|
|
64
|
+
* is used (one instant span per discrete fact). Kept narrow so a custom,
|
|
65
|
+
* event-only tracer that does not implement `startActiveSpan` still satisfies it.
|
|
66
|
+
* The real `@opentelemetry/api` Tracer has both methods, so it satisfies this and
|
|
67
|
+
* `OTelActiveSpanTracer` below.
|
|
40
68
|
*/
|
|
41
69
|
export interface OTelTracer {
|
|
42
70
|
startSpan(
|
|
@@ -47,194 +75,110 @@ export interface OTelTracer {
|
|
|
47
75
|
): OTelSpan;
|
|
48
76
|
}
|
|
49
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Minimal Tracer interface for the PHASE-SPAN adapter (createOTelTracing): only
|
|
80
|
+
* `startActiveSpan` is used (callback-bound, so the span is active for the work
|
|
81
|
+
* and child spans nest). Declared separately from `OTelTracer` so each factory
|
|
82
|
+
* requires exactly the method it calls.
|
|
83
|
+
*/
|
|
84
|
+
export interface OTelActiveSpanTracer {
|
|
85
|
+
startActiveSpan<T>(name: string, fn: (span: OTelSpan) => T): T;
|
|
86
|
+
}
|
|
87
|
+
|
|
50
88
|
// OTel SpanStatusCode constants (mirrors @opentelemetry/api values)
|
|
51
|
-
const STATUS_OK = 1;
|
|
52
89
|
const STATUS_ERROR = 2;
|
|
53
90
|
|
|
54
91
|
// ---------------------------------------------------------------------------
|
|
55
|
-
//
|
|
92
|
+
// Tracing adapter: phase spans via startActiveSpan (the `tracing` slot)
|
|
56
93
|
// ---------------------------------------------------------------------------
|
|
57
94
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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}`;
|
|
95
|
+
/** Options for createOTelTracing. */
|
|
96
|
+
export interface OTelTracingOptions {
|
|
97
|
+
/** Master switch. Defaults to true. */
|
|
98
|
+
enabled?: boolean;
|
|
99
|
+
/** Per-phase span toggles. Omitted phases default to enabled. */
|
|
100
|
+
spans?: TracePhaseToggles;
|
|
78
101
|
}
|
|
79
102
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Create the tracing config that maps the router's phases onto OTel spans via
|
|
105
|
+
* `tracer.startActiveSpan`, which runs the work inside the span's active context
|
|
106
|
+
* (so child spans nest) and returns the work's value unchanged. The span ends
|
|
107
|
+
* when that value — or, for async work, the returned promise — settles, matching
|
|
108
|
+
* observePhase's contract. When the work throws or rejects, the exception is
|
|
109
|
+
* recorded and the span status is set to ERROR before it ends, so a failed phase
|
|
110
|
+
* stays visible in the trace (the old createOTelSink error path is preserved here
|
|
111
|
+
* now that phase spans live in this adapter). Pass the result to
|
|
112
|
+
* `createRouter({ tracing })`.
|
|
113
|
+
*
|
|
114
|
+
* @see createCloudflareTracing (`@rangojs/router/cloudflare`) for the same slot
|
|
115
|
+
* using Cloudflare Workers native custom spans.
|
|
116
|
+
*/
|
|
117
|
+
export function createOTelTracing(
|
|
118
|
+
tracer: OTelActiveSpanTracer,
|
|
119
|
+
options: OTelTracingOptions = {},
|
|
120
|
+
): RouterTracingConfig {
|
|
121
|
+
const runner: SpanRunner = <T>(name: string, fn: (span: OTelSpan) => T): T =>
|
|
122
|
+
tracer.startActiveSpan(
|
|
123
|
+
name,
|
|
124
|
+
(span): T =>
|
|
125
|
+
runThenSettle(
|
|
126
|
+
() => fn(span),
|
|
127
|
+
(error) => {
|
|
128
|
+
// On failure record the exception + ERROR status before ending, so a
|
|
129
|
+
// failed phase stays visible in the trace.
|
|
130
|
+
if (error !== undefined) {
|
|
131
|
+
if (error instanceof Error) {
|
|
132
|
+
span.recordException(error);
|
|
133
|
+
span.setStatus({ code: STATUS_ERROR, message: error.message });
|
|
134
|
+
} else {
|
|
135
|
+
span.setStatus({ code: STATUS_ERROR });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
span.end();
|
|
139
|
+
},
|
|
140
|
+
),
|
|
141
|
+
);
|
|
92
142
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if (!stack || stack.length === 0) return undefined;
|
|
99
|
-
const span = stack.pop()!;
|
|
100
|
-
if (stack.length === 0) map.delete(key);
|
|
101
|
-
return span;
|
|
143
|
+
return {
|
|
144
|
+
runner,
|
|
145
|
+
enabled: options.enabled ?? true,
|
|
146
|
+
spans: options.spans,
|
|
147
|
+
};
|
|
102
148
|
}
|
|
103
149
|
|
|
104
150
|
// ---------------------------------------------------------------------------
|
|
105
|
-
//
|
|
151
|
+
// Telemetry sink: discrete-fact instant spans (the `telemetry` slot)
|
|
106
152
|
// ---------------------------------------------------------------------------
|
|
107
153
|
|
|
108
154
|
/**
|
|
109
|
-
* Create a TelemetrySink that maps router
|
|
155
|
+
* Create a TelemetrySink that maps the router's discrete-fact events to instant
|
|
156
|
+
* OTel spans. One span per fact; no duration spans.
|
|
157
|
+
*
|
|
158
|
+
* Fact mapping:
|
|
159
|
+
* - handler.error -> "rango.handler.error" (error)
|
|
160
|
+
* - cache.decision -> "rango.cache.decision"
|
|
161
|
+
* - revalidation.decision -> "rango.revalidation.decision"
|
|
162
|
+
* - request.timeout -> "rango.request.timeout" (error)
|
|
163
|
+
* - request.origin-rejected -> "rango.request.origin-rejected" (error)
|
|
110
164
|
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
* - cache.decision → "rango.cache.decision" instant span
|
|
116
|
-
* - revalidation.decision → "rango.revalidation.decision" instant span
|
|
165
|
+
* Request and loader PHASE spans are intentionally NOT emitted here — they are
|
|
166
|
+
* owned by the tracing slot (createOTelTracing) so the two layers cannot produce
|
|
167
|
+
* duplicate rango.request / rango.loader spans. request.start/end and
|
|
168
|
+
* loader.start/end events are no-ops for this sink.
|
|
117
169
|
*
|
|
118
170
|
* Attributes use the `rango.*` namespace for router-specific data and
|
|
119
171
|
* `http.method` / `http.route` for HTTP semantics.
|
|
120
172
|
*/
|
|
121
173
|
export function createOTelSink(tracer: OTelTracer): TelemetrySink {
|
|
122
|
-
const
|
|
123
|
-
|
|
174
|
+
const instant = (
|
|
175
|
+
name: string,
|
|
176
|
+
attributes: Record<string, string | number | boolean>,
|
|
177
|
+
): OTelSpan => tracer.startSpan(name, { attributes });
|
|
124
178
|
|
|
125
179
|
return {
|
|
126
180
|
emit(event: TelemetryEvent): void {
|
|
127
181
|
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
182
|
case "handler.error": {
|
|
239
183
|
const attrs: Record<string, string | number | boolean> = {
|
|
240
184
|
"rango.handled_by_boundary": event.handledByBoundary,
|
|
@@ -244,23 +188,16 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
|
|
|
244
188
|
attrs["rango.segment_type"] = event.segmentType;
|
|
245
189
|
if (event.pathname) attrs["http.route"] = event.pathname;
|
|
246
190
|
if (event.routeKey) attrs["rango.route_key"] = event.routeKey;
|
|
247
|
-
if (event.params)
|
|
191
|
+
if (event.params)
|
|
248
192
|
attrs["rango.params"] = JSON.stringify(event.params);
|
|
249
|
-
}
|
|
250
193
|
|
|
251
|
-
const span =
|
|
252
|
-
attributes: attrs,
|
|
253
|
-
});
|
|
194
|
+
const span = instant("rango.handler.error", attrs);
|
|
254
195
|
span.recordException(event.error);
|
|
255
196
|
span.setStatus({ code: STATUS_ERROR, message: event.error.message });
|
|
256
197
|
span.end();
|
|
257
198
|
break;
|
|
258
199
|
}
|
|
259
200
|
|
|
260
|
-
// -----------------------------------------------------------------
|
|
261
|
-
// Cache decision (instant span)
|
|
262
|
-
// -----------------------------------------------------------------
|
|
263
|
-
|
|
264
201
|
case "cache.decision": {
|
|
265
202
|
const attrs: Record<string, string | number | boolean> = {
|
|
266
203
|
"http.route": event.pathname,
|
|
@@ -269,30 +206,55 @@ export function createOTelSink(tracer: OTelTracer): TelemetrySink {
|
|
|
269
206
|
"rango.cache.should_revalidate": event.shouldRevalidate,
|
|
270
207
|
};
|
|
271
208
|
if (event.source) attrs["rango.cache.source"] = event.source;
|
|
209
|
+
instant("rango.cache.decision", attrs).end();
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
272
212
|
|
|
273
|
-
|
|
274
|
-
|
|
213
|
+
case "revalidation.decision": {
|
|
214
|
+
instant("rango.revalidation.decision", {
|
|
215
|
+
"rango.segment_id": event.segmentId,
|
|
216
|
+
"http.route": event.pathname,
|
|
217
|
+
"rango.route_key": event.routeKey,
|
|
218
|
+
"rango.revalidate": event.shouldRevalidate,
|
|
219
|
+
}).end();
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
case "request.timeout": {
|
|
224
|
+
const attrs: Record<string, string | number | boolean> = {
|
|
225
|
+
"rango.phase": event.phase,
|
|
226
|
+
"http.route": event.pathname,
|
|
227
|
+
"rango.duration_ms": event.durationMs,
|
|
228
|
+
"rango.timeout.custom_handler": event.customHandler,
|
|
229
|
+
};
|
|
230
|
+
if (event.routeKey) attrs["rango.route_key"] = event.routeKey;
|
|
231
|
+
if (event.actionId) attrs["rango.action_id"] = event.actionId;
|
|
232
|
+
const span = instant("rango.request.timeout", attrs);
|
|
233
|
+
span.setStatus({
|
|
234
|
+
code: STATUS_ERROR,
|
|
235
|
+
message: `timeout: ${event.phase}`,
|
|
275
236
|
});
|
|
276
237
|
span.end();
|
|
277
238
|
break;
|
|
278
239
|
}
|
|
279
240
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
"rango.revalidate": event.shouldRevalidate,
|
|
291
|
-
},
|
|
292
|
-
});
|
|
241
|
+
case "request.origin-rejected": {
|
|
242
|
+
const attrs: Record<string, string | number | boolean> = {
|
|
243
|
+
"http.method": event.method,
|
|
244
|
+
"http.route": event.pathname,
|
|
245
|
+
"rango.phase": event.phase,
|
|
246
|
+
};
|
|
247
|
+
if (event.origin) attrs["rango.origin"] = event.origin;
|
|
248
|
+
if (event.host) attrs["http.host"] = event.host;
|
|
249
|
+
const span = instant("rango.request.origin-rejected", attrs);
|
|
250
|
+
span.setStatus({ code: STATUS_ERROR, message: "origin rejected" });
|
|
293
251
|
span.end();
|
|
294
252
|
break;
|
|
295
253
|
}
|
|
254
|
+
|
|
255
|
+
// request.start/end/error and loader.start/end/error are phase events;
|
|
256
|
+
// their spans are owned by the tracing slot (createOTelTracing), so this
|
|
257
|
+
// sink ignores them to avoid duplicate rango.request / rango.loader spans.
|
|
296
258
|
}
|
|
297
259
|
},
|
|
298
260
|
};
|
package/src/router/telemetry.ts
CHANGED
|
@@ -14,10 +14,6 @@
|
|
|
14
14
|
* - revalidation.decision (revalidation evaluation)
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Event types
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
17
|
interface BaseEvent {
|
|
22
18
|
/** Monotonic timestamp from performance.now() */
|
|
23
19
|
timestamp: number;
|
|
@@ -90,6 +86,34 @@ export interface HandlerErrorEvent extends BaseEvent {
|
|
|
90
86
|
params?: Record<string, string>;
|
|
91
87
|
}
|
|
92
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Per-segment (or coarse route-level) cache status carried on the
|
|
91
|
+
* cache.decision telemetry event and the X-Rango-Cache debug header.
|
|
92
|
+
*
|
|
93
|
+
* v1 is COARSE: the router's pipeline tracks cache decisions at the
|
|
94
|
+
* route/entry level (cacheHit/cacheSource/shouldRevalidate), not per
|
|
95
|
+
* individual segment. The `segments` array therefore contains a single
|
|
96
|
+
* route-level entry keyed by the route key. The shape is forward-compatible
|
|
97
|
+
* with genuine per-segment status if the pipeline later exposes it.
|
|
98
|
+
*/
|
|
99
|
+
export type CacheSegmentStatus =
|
|
100
|
+
| "hit"
|
|
101
|
+
| "miss"
|
|
102
|
+
| "stale"
|
|
103
|
+
| "prerendered"
|
|
104
|
+
| "passthrough";
|
|
105
|
+
|
|
106
|
+
export interface CacheSegmentSignal {
|
|
107
|
+
/** Segment id (v1: the route key, since status is route-level). */
|
|
108
|
+
id: string;
|
|
109
|
+
/** Segment type (v1: "route" for the coarse route-level entry). */
|
|
110
|
+
type: string;
|
|
111
|
+
/** Resolved cache status for this segment. */
|
|
112
|
+
cacheStatus: CacheSegmentStatus;
|
|
113
|
+
/** Whether stale-while-revalidate was triggered for this segment. */
|
|
114
|
+
shouldRevalidate?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
93
117
|
export interface CacheDecisionEvent extends BaseEvent {
|
|
94
118
|
type: "cache.decision";
|
|
95
119
|
pathname: string;
|
|
@@ -98,6 +122,12 @@ export interface CacheDecisionEvent extends BaseEvent {
|
|
|
98
122
|
/** Whether stale-while-revalidate was triggered */
|
|
99
123
|
shouldRevalidate: boolean;
|
|
100
124
|
source?: "runtime" | "prerender";
|
|
125
|
+
/**
|
|
126
|
+
* Optional per-segment (v1: coarse route-level) cache status. Present only
|
|
127
|
+
* when telemetry or the debug cache signal is enabled. Optional so existing
|
|
128
|
+
* sinks are unaffected.
|
|
129
|
+
*/
|
|
130
|
+
segments?: CacheSegmentSignal[];
|
|
101
131
|
}
|
|
102
132
|
|
|
103
133
|
export interface RevalidationDecisionEvent extends BaseEvent {
|
|
@@ -141,9 +171,70 @@ export type TelemetryEvent =
|
|
|
141
171
|
| OriginCheckRejectedEvent;
|
|
142
172
|
|
|
143
173
|
// ---------------------------------------------------------------------------
|
|
144
|
-
//
|
|
174
|
+
// Cache signal derivation (coarse, route-level)
|
|
145
175
|
// ---------------------------------------------------------------------------
|
|
146
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Derive the coarse, route-level cache status from pipeline cache state.
|
|
179
|
+
*
|
|
180
|
+
* v1 mapping (route-level — see CacheSegmentSignal):
|
|
181
|
+
* - prerender hit -> "prerendered"
|
|
182
|
+
* - runtime hit + shouldRevalidate (SWR) -> "stale"
|
|
183
|
+
* - runtime hit -> "hit"
|
|
184
|
+
* - no hit -> "miss"
|
|
185
|
+
*
|
|
186
|
+
* Note: "passthrough" is a build-time prerender concept (a route opts out of
|
|
187
|
+
* being prerendered for some params). At runtime a passthrough route renders
|
|
188
|
+
* fresh and is indistinguishable from a normal miss in the pipeline state, so
|
|
189
|
+
* v1 reports it as "miss". The "passthrough" status remains in the type union
|
|
190
|
+
* for forward compatibility.
|
|
191
|
+
*/
|
|
192
|
+
export function deriveCacheStatus(state: {
|
|
193
|
+
cacheHit: boolean;
|
|
194
|
+
cacheSource?: "runtime" | "prerender";
|
|
195
|
+
shouldRevalidate?: boolean;
|
|
196
|
+
}): CacheSegmentStatus {
|
|
197
|
+
if (state.cacheHit) {
|
|
198
|
+
if (state.cacheSource === "prerender") return "prerendered";
|
|
199
|
+
if (state.shouldRevalidate) return "stale";
|
|
200
|
+
return "hit";
|
|
201
|
+
}
|
|
202
|
+
return "miss";
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Build the coarse route-level cache signal array (a single entry keyed by
|
|
207
|
+
* the route key). Used for both the cache.decision telemetry event and the
|
|
208
|
+
* X-Rango-Cache debug header.
|
|
209
|
+
*/
|
|
210
|
+
export function buildCacheSignalSegments(
|
|
211
|
+
routeKey: string,
|
|
212
|
+
state: {
|
|
213
|
+
cacheHit: boolean;
|
|
214
|
+
cacheSource?: "runtime" | "prerender";
|
|
215
|
+
shouldRevalidate?: boolean;
|
|
216
|
+
},
|
|
217
|
+
): CacheSegmentSignal[] {
|
|
218
|
+
return [
|
|
219
|
+
{
|
|
220
|
+
id: routeKey,
|
|
221
|
+
type: "route",
|
|
222
|
+
cacheStatus: deriveCacheStatus(state),
|
|
223
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
224
|
+
},
|
|
225
|
+
];
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Serialize cache signal segments into the X-Rango-Cache header value:
|
|
230
|
+
* `<segId>=<status>, <segId2>=<status2>`.
|
|
231
|
+
*/
|
|
232
|
+
export function formatCacheSignalHeader(
|
|
233
|
+
segments: CacheSegmentSignal[],
|
|
234
|
+
): string {
|
|
235
|
+
return segments.map((s) => `${s.id}=${s.cacheStatus}`).join(", ");
|
|
236
|
+
}
|
|
237
|
+
|
|
147
238
|
/**
|
|
148
239
|
* Telemetry sink receives structured lifecycle events from the router.
|
|
149
240
|
* Implement this interface to integrate with any observability backend.
|
|
@@ -154,10 +245,6 @@ export interface TelemetrySink {
|
|
|
154
245
|
emit(event: TelemetryEvent): void;
|
|
155
246
|
}
|
|
156
247
|
|
|
157
|
-
// ---------------------------------------------------------------------------
|
|
158
|
-
// No-op singleton (zero-cost disabled state)
|
|
159
|
-
// ---------------------------------------------------------------------------
|
|
160
|
-
|
|
161
248
|
const noopSink: TelemetrySink = {
|
|
162
249
|
emit() {},
|
|
163
250
|
};
|
|
@@ -185,12 +272,6 @@ export function safeEmit(sink: TelemetrySink, event: TelemetryEvent): void {
|
|
|
185
272
|
}
|
|
186
273
|
}
|
|
187
274
|
|
|
188
|
-
// ---------------------------------------------------------------------------
|
|
189
|
-
// Request ID extraction (for span correlation)
|
|
190
|
-
// ---------------------------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
// Per-request memoization so the same Request object always maps to the
|
|
193
|
-
// same ID. WeakMap allows GC when the Request is no longer referenced.
|
|
194
275
|
const requestIds = new WeakMap<Request, string>();
|
|
195
276
|
let telemetryRequestCounter = 0;
|
|
196
277
|
|
|
@@ -224,10 +305,6 @@ export function getRequestId(request: Request): string {
|
|
|
224
305
|
return id;
|
|
225
306
|
}
|
|
226
307
|
|
|
227
|
-
// ---------------------------------------------------------------------------
|
|
228
|
-
// Console sink (built-in, replaces ad-hoc console.log debug traces)
|
|
229
|
-
// ---------------------------------------------------------------------------
|
|
230
|
-
|
|
231
308
|
/**
|
|
232
309
|
* Built-in console sink that logs events in a structured format.
|
|
233
310
|
* Designed as the default sink for development / debugging.
|
package/src/router/timeout.ts
CHANGED
|
@@ -6,10 +6,6 @@
|
|
|
6
6
|
* a Promise.race mechanism, returning 504 on expiry.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Public types
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
9
|
export interface RouterTimeouts {
|
|
14
10
|
/** Timeout for server action execution (ms). */
|
|
15
11
|
actionMs?: number;
|
|
@@ -35,10 +31,6 @@ export type OnTimeoutCallback<TEnv = any> = (
|
|
|
35
31
|
ctx: TimeoutContext<TEnv>,
|
|
36
32
|
) => Response | Promise<Response>;
|
|
37
33
|
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
// Internal resolved form
|
|
40
|
-
// ---------------------------------------------------------------------------
|
|
41
|
-
|
|
42
34
|
export interface ResolvedTimeouts {
|
|
43
35
|
actionMs: number | undefined;
|
|
44
36
|
renderStartMs: number | undefined;
|
|
@@ -63,10 +55,6 @@ export function resolveTimeouts(
|
|
|
63
55
|
};
|
|
64
56
|
}
|
|
65
57
|
|
|
66
|
-
// ---------------------------------------------------------------------------
|
|
67
|
-
// Error class
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
|
|
70
58
|
export class RouterTimeoutError extends Error {
|
|
71
59
|
override name = "RouterTimeoutError" as const;
|
|
72
60
|
phase: TimeoutPhase;
|
|
@@ -81,10 +69,6 @@ export class RouterTimeoutError extends Error {
|
|
|
81
69
|
}
|
|
82
70
|
}
|
|
83
71
|
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// Race helper
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
72
|
type TimeoutResult<T> =
|
|
89
73
|
| { result: T; timedOut: false }
|
|
90
74
|
| { timedOut: true; durationMs: number };
|
|
@@ -129,10 +113,6 @@ export async function withTimeout<T>(
|
|
|
129
113
|
}
|
|
130
114
|
}
|
|
131
115
|
|
|
132
|
-
// ---------------------------------------------------------------------------
|
|
133
|
-
// Default response
|
|
134
|
-
// ---------------------------------------------------------------------------
|
|
135
|
-
|
|
136
116
|
/**
|
|
137
117
|
* Create the default 504 response for a timed-out request.
|
|
138
118
|
* Includes `X-Rango-Timeout-Phase` header for observability.
|