@rangojs/router 0.0.0-experimental.14 → 0.0.0-experimental.140
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 +17 -0
- package/README.md +432 -7
- package/dist/bin/rango.js +2073 -213
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +7258 -2714
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +140 -67
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +329 -0
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +487 -0
- package/skills/caching/SKILL.md +357 -25
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +246 -0
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +16 -10
- package/skills/document-cache/SKILL.md +87 -62
- package/skills/fonts/SKILL.md +6 -4
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +557 -79
- package/skills/host-router/SKILL.md +320 -0
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +207 -15
- package/skills/layout/SKILL.md +146 -6
- package/skills/links/SKILL.md +304 -25
- package/skills/loader/SKILL.md +616 -54
- package/skills/middleware/SKILL.md +217 -37
- package/skills/migrate-nextjs/SKILL.md +611 -0
- package/skills/migrate-react-router/SKILL.md +927 -0
- package/skills/mime-routes/SKILL.md +42 -11
- package/skills/observability/SKILL.md +194 -0
- package/skills/parallel/SKILL.md +284 -3
- package/skills/ppr/SKILL.md +426 -0
- package/skills/prerender/SKILL.md +437 -52
- package/skills/rango/SKILL.md +369 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +263 -121
- package/skills/route/SKILL.md +350 -21
- package/skills/router-setup/SKILL.md +246 -33
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +126 -222
- 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 +131 -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 +85 -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/theme/SKILL.md +9 -8
- package/skills/typesafety/SKILL.md +532 -103
- package/skills/use-cache/SKILL.md +367 -0
- package/skills/vercel/SKILL.md +128 -0
- 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 +77 -44
- package/src/bin/rango.ts +312 -15
- package/src/browser/action-coordinator.ts +114 -0
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +293 -202
- package/src/browser/history-state.ts +101 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/intercept-utils.ts +52 -0
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +24 -4
- package/src/browser/logging.ts +11 -0
- package/src/browser/merge-segment-loaders.ts +20 -12
- package/src/browser/navigation-bridge.ts +385 -576
- package/src/browser/navigation-client.ts +245 -75
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +184 -118
- package/src/browser/navigation-transaction.ts +247 -0
- package/src/browser/network-error-handler.ts +88 -0
- package/src/browser/partial-update.ts +412 -364
- package/src/browser/prefetch/cache.ts +359 -0
- package/src/browser/prefetch/fetch.ts +452 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +48 -0
- package/src/browser/prefetch/queue.ts +209 -0
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +194 -0
- package/src/browser/react/Link.tsx +275 -68
- package/src/browser/react/NavigationProvider.tsx +265 -109
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +11 -0
- package/src/browser/react/filter-segment-order.ts +70 -0
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +272 -60
- package/src/browser/react/location-state.ts +90 -20
- package/src/browser/react/mount-context.ts +6 -1
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +35 -66
- package/src/browser/react/use-handle.ts +39 -126
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +39 -13
- package/src/browser/react/use-navigation.ts +53 -69
- package/src/browser/react/use-params.ts +75 -0
- package/src/browser/react/use-pathname.ts +47 -0
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +98 -0
- package/src/browser/react/use-search-params.ts +51 -0
- package/src/browser/react/use-segments.ts +72 -99
- package/src/browser/response-adapter.ts +164 -0
- package/src/browser/rsc-router.tsx +300 -72
- package/src/browser/scroll-restoration.ts +138 -50
- package/src/browser/segment-reconciler.ts +243 -0
- package/src/browser/segment-structure-assert.ts +17 -1
- package/src/browser/server-action-bridge.ts +668 -613
- package/src/browser/types.ts +223 -51
- package/src/browser/validate-redirect-origin.ts +56 -0
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +252 -161
- package/src/build/generate-route-types.ts +41 -1038
- package/src/build/index.ts +12 -7
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +225 -42
- package/src/build/route-types/ast-helpers.ts +25 -0
- package/src/build/route-types/ast-route-extraction.ts +105 -0
- package/src/build/route-types/codegen.ts +113 -0
- package/src/build/route-types/include-resolution.ts +812 -0
- package/src/build/route-types/param-extraction.ts +51 -0
- package/src/build/route-types/per-module-writer.ts +144 -0
- package/src/build/route-types/router-processing.ts +695 -0
- package/src/build/route-types/scan-filter.ts +85 -0
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +223 -0
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +60 -0
- package/src/cache/cache-policy.ts +199 -0
- package/src/cache/cache-runtime.ts +525 -0
- package/src/cache/cache-scope.ts +298 -332
- 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 +2500 -158
- 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 +17 -17
- package/src/cache/document-cache.ts +199 -92
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/handle-snapshot.ts +111 -0
- package/src/cache/index.ts +29 -35
- package/src/cache/memory-segment-store.ts +363 -30
- package/src/cache/profile-registry.ts +88 -0
- package/src/cache/read-through-swr.ts +178 -0
- package/src/cache/segment-codec.ts +248 -0
- package/src/cache/shell-cache.ts +386 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +153 -0
- package/src/cache/types.ts +156 -211
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1102 -0
- package/src/client.rsc.tsx +43 -21
- package/src/client.tsx +131 -347
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +23 -4
- package/src/components/DefaultDocument.tsx +13 -3
- package/src/context-var.ts +168 -0
- package/src/debug.ts +19 -9
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +106 -10
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +110 -35
- package/src/handles/MetaTags.tsx +83 -59
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +93 -0
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +44 -53
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +20 -65
- package/src/host/errors.ts +21 -30
- package/src/host/index.ts +13 -9
- package/src/host/pattern-matcher.ts +50 -79
- package/src/host/router.ts +151 -121
- package/src/host/testing.ts +45 -32
- package/src/host/types.ts +52 -11
- package/src/host/utils.ts +2 -2
- package/src/href-client.ts +192 -57
- package/src/index.rsc.ts +177 -35
- package/src/index.ts +255 -71
- package/src/internal-debug.ts +9 -2
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +31 -99
- package/src/loader.ts +30 -12
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +41 -0
- package/src/prerender/param-hash.ts +16 -14
- package/src/prerender/store.ts +121 -21
- package/src/prerender.ts +460 -26
- 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 +62 -0
- package/src/reverse.ts +198 -128
- package/src/root-error-boundary.tsx +42 -48
- package/src/route-content-wrapper.tsx +22 -77
- package/src/route-definition/dsl-helpers.ts +1116 -0
- package/src/route-definition/helper-factories.ts +88 -0
- package/src/route-definition/helpers-types.ts +505 -0
- package/src/route-definition/index.ts +54 -0
- package/src/route-definition/redirect.ts +134 -0
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-definition.ts +1 -1481
- package/src/route-map-builder.ts +82 -144
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +71 -45
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +263 -0
- package/src/router/debug-manifest.ts +72 -0
- package/src/router/error-handling.ts +54 -27
- package/src/router/find-match.ts +245 -0
- package/src/router/handler-context.ts +377 -125
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +59 -28
- package/src/router/lazy-includes.ts +254 -0
- package/src/router/loader-resolution.ts +421 -157
- package/src/router/logging.ts +106 -6
- package/src/router/manifest.ts +131 -57
- package/src/router/match-api.ts +167 -246
- package/src/router/match-context.ts +4 -24
- package/src/router/match-handlers.ts +440 -0
- package/src/router/match-middleware/background-revalidation.ts +117 -93
- package/src/router/match-middleware/cache-lookup.ts +297 -150
- package/src/router/match-middleware/cache-store.ts +123 -51
- package/src/router/match-middleware/intercept-resolution.ts +44 -43
- package/src/router/match-middleware/segment-resolution.ts +64 -22
- package/src/router/match-pipelines.ts +11 -87
- package/src/router/match-result.ts +121 -50
- package/src/router/metrics.ts +219 -28
- package/src/router/middleware-types.ts +93 -0
- package/src/router/middleware.ts +505 -441
- package/src/router/navigation-snapshot.ts +133 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +311 -142
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +547 -0
- package/src/router/preview-match.ts +102 -0
- package/src/router/request-classification.ts +278 -0
- package/src/router/revalidation.ts +203 -62
- package/src/router/route-snapshot.ts +246 -0
- package/src/router/router-context.ts +45 -48
- package/src/router/router-interfaces.ts +554 -0
- package/src/router/router-options.ts +779 -0
- package/src/router/router-registry.ts +21 -0
- package/src/router/segment-resolution/fresh.ts +772 -0
- package/src/router/segment-resolution/helpers.ts +348 -0
- package/src/router/segment-resolution/loader-cache.ts +250 -0
- package/src/router/segment-resolution/loader-mask.ts +44 -0
- package/src/router/segment-resolution/revalidation.ts +1331 -0
- package/src/router/segment-resolution/static-store.ts +81 -0
- 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 +25 -1354
- package/src/router/segment-wrappers.ts +292 -0
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +261 -0
- package/src/router/telemetry.ts +377 -0
- package/src/router/timeout.ts +128 -0
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +240 -61
- package/src/router/types.ts +23 -70
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +781 -2378
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +46 -0
- package/src/rsc/handler.ts +905 -1142
- package/src/rsc/helpers.ts +275 -19
- package/src/rsc/index.ts +2 -25
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +305 -0
- package/src/rsc/manifest-init.ts +77 -0
- package/src/rsc/nonce.ts +14 -0
- package/src/rsc/origin-guard.ts +155 -0
- package/src/rsc/progressive-enhancement.ts +502 -0
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +104 -0
- package/src/rsc/response-route-handler.ts +257 -0
- package/src/rsc/rsc-rendering.ts +337 -0
- package/src/rsc/runtime-warnings.ts +55 -0
- package/src/rsc/server-action.ts +522 -0
- package/src/rsc/shell-capture.ts +439 -0
- package/src/rsc/ssr-setup.ts +144 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +95 -12
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +99 -82
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +349 -134
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +452 -85
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +310 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +123 -42
- package/src/server/live.ts +130 -0
- package/src/server/loader-registry.ts +51 -100
- package/src/server/request-context.ts +842 -157
- package/src/server.ts +15 -8
- package/src/ssr/index.tsx +412 -136
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +45 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -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 +199 -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 +584 -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 +40 -72
- package/src/theme/ThemeScript.tsx +12 -14
- package/src/theme/constants.ts +57 -15
- package/src/theme/index.ts +3 -20
- package/src/theme/theme-context.ts +5 -35
- package/src/theme/theme-script.ts +43 -39
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +123 -0
- package/src/types/cache-types.ts +207 -0
- package/src/types/error-types.ts +132 -0
- package/src/types/global-namespace.ts +113 -0
- package/src/types/handler-context.ts +839 -0
- package/src/types/index.ts +81 -0
- package/src/types/loader-types.ts +212 -0
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +138 -0
- package/src/types/route-entry.ts +114 -0
- package/src/types/segments.ts +271 -0
- package/src/types.ts +1 -1795
- package/src/urls/include-helper.ts +162 -0
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +43 -0
- package/src/urls/path-helper-types.ts +413 -0
- package/src/urls/path-helper.ts +275 -0
- package/src/urls/pattern-types.ts +124 -0
- package/src/urls/response-types.ts +109 -0
- package/src/urls/type-extraction.ts +316 -0
- package/src/urls/urls-function.ts +80 -0
- package/src/urls.ts +1 -1341
- package/src/use-loader.tsx +406 -141
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +182 -0
- package/src/vite/discovery/discover-routers.ts +389 -0
- package/src/vite/discovery/discovery-errors.ts +255 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +467 -0
- package/src/vite/discovery/route-types-writer.ts +214 -0
- package/src/vite/discovery/self-gen-tracking.ts +73 -0
- package/src/vite/discovery/state.ts +161 -0
- package/src/vite/discovery/virtual-module-codegen.ts +183 -0
- package/src/vite/index.ts +23 -2255
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +303 -0
- package/src/vite/plugins/cjs-to-esm.ts +90 -0
- package/src/vite/plugins/client-ref-dedup.ts +120 -0
- package/src/vite/plugins/client-ref-hashing.ts +118 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/{expose-action-id.ts → plugins/expose-action-id.ts} +88 -110
- package/src/vite/{expose-id-utils.ts → plugins/expose-id-utils.ts} +89 -79
- package/src/vite/plugins/expose-ids/export-analysis.ts +363 -0
- package/src/vite/plugins/expose-ids/handler-transform.ts +130 -0
- package/src/vite/plugins/expose-ids/loader-transform.ts +64 -0
- package/src/vite/plugins/expose-ids/router-transform.ts +199 -0
- package/src/vite/plugins/expose-ids/types.ts +45 -0
- package/src/vite/plugins/expose-internal-ids.ts +805 -0
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +127 -0
- package/src/vite/plugins/use-cache-transform.ts +313 -0
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +94 -0
- package/src/vite/plugins/version-plugin.ts +263 -0
- package/src/vite/plugins/virtual-entries.ts +234 -0
- package/src/vite/plugins/virtual-stub-plugin.ts +29 -0
- package/src/vite/rango.ts +560 -0
- package/src/vite/router-discovery.ts +1638 -0
- package/src/vite/{ast-handler-extract.ts → utils/ast-handler-extract.ts} +200 -37
- package/src/vite/utils/banner.ts +36 -0
- package/src/vite/utils/bundle-analysis.ts +132 -0
- 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 +15 -0
- package/src/vite/utils/package-resolution.ts +89 -0
- package/src/vite/utils/prerender-utils.ts +249 -0
- package/src/vite/utils/shared-utils.ts +269 -0
- package/CLAUDE.md +0 -43
- package/dist/vite/index.named-routes.gen.ts +0 -103
- package/src/browser/lru-cache.ts +0 -69
- package/src/browser/react/use-client-cache.ts +0 -56
- package/src/browser/request-controller.ts +0 -164
- package/src/browser/shallow.ts +0 -35
- package/src/cache/memory-store.ts +0 -253
- package/src/handles/index.ts +0 -6
- package/src/href-context.ts +0 -33
- package/src/network-error-thrower.tsx +0 -21
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- package/src/vite/expose-internal-ids.ts +0 -1167
- package/src/vite/package-resolution.ts +0 -125
- package/src/vite/virtual-entries.ts +0 -114
- /package/src/vite/{version.d.ts → plugins/version.d.ts} +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
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
|
+
import type { ResolveSegmentOptions } from "./segment-resolution.js";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
resolveAllSegments as _resolveAllSegments,
|
|
12
|
+
resolveLoadersOnly as _resolveLoadersOnly,
|
|
13
|
+
resolveLoadersOnlyWithRevalidation as _resolveLoadersOnlyWithRevalidation,
|
|
14
|
+
buildEntryRevalidateMap as _buildEntryRevalidateMap,
|
|
15
|
+
resolveAllSegmentsWithRevalidation as _resolveAllSegmentsWithRevalidation,
|
|
16
|
+
} from "./segment-resolution.js";
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
findInterceptForRoute as _findInterceptForRoute,
|
|
20
|
+
resolveInterceptEntry as _resolveInterceptEntry,
|
|
21
|
+
resolveInterceptLoadersOnly as _resolveInterceptLoadersOnly,
|
|
22
|
+
} from "./intercept-resolution.js";
|
|
23
|
+
|
|
24
|
+
import type { InterceptSelectorContext } from "../server/context";
|
|
25
|
+
|
|
26
|
+
export interface SegmentWrappers<TEnv = any> {
|
|
27
|
+
resolveAllSegments: (
|
|
28
|
+
entries: EntryData[],
|
|
29
|
+
routeKey: string,
|
|
30
|
+
params: Record<string, string>,
|
|
31
|
+
context: HandlerContext<any, TEnv>,
|
|
32
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
33
|
+
options?: ResolveSegmentOptions,
|
|
34
|
+
) => Promise<ResolvedSegment[]>;
|
|
35
|
+
resolveLoadersOnly: (
|
|
36
|
+
entries: EntryData[],
|
|
37
|
+
context: HandlerContext<any, TEnv>,
|
|
38
|
+
) => Promise<ResolvedSegment[]>;
|
|
39
|
+
resolveLoadersOnlyWithRevalidation: (
|
|
40
|
+
entries: EntryData[],
|
|
41
|
+
context: HandlerContext<any, TEnv>,
|
|
42
|
+
clientSegmentIds: Set<string>,
|
|
43
|
+
prevParams: Record<string, string>,
|
|
44
|
+
request: Request,
|
|
45
|
+
prevUrl: URL,
|
|
46
|
+
nextUrl: URL,
|
|
47
|
+
routeKey: string,
|
|
48
|
+
actionContext?: {
|
|
49
|
+
actionId?: string;
|
|
50
|
+
actionUrl?: URL;
|
|
51
|
+
actionResult?: any;
|
|
52
|
+
formData?: FormData;
|
|
53
|
+
},
|
|
54
|
+
stale?: boolean,
|
|
55
|
+
) => Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }>;
|
|
56
|
+
buildEntryRevalidateMap: (
|
|
57
|
+
entries: EntryData[],
|
|
58
|
+
) => Map<
|
|
59
|
+
string,
|
|
60
|
+
{ entry: EntryData; revalidate: ShouldRevalidateFn<any, any>[] }
|
|
61
|
+
>;
|
|
62
|
+
resolveAllSegmentsWithRevalidation: (
|
|
63
|
+
entries: EntryData[],
|
|
64
|
+
routeKey: string,
|
|
65
|
+
params: Record<string, string>,
|
|
66
|
+
context: HandlerContext<any, TEnv>,
|
|
67
|
+
clientSegmentSet: Set<string>,
|
|
68
|
+
prevParams: Record<string, string>,
|
|
69
|
+
request: Request,
|
|
70
|
+
prevUrl: URL,
|
|
71
|
+
nextUrl: URL,
|
|
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
|
+
options?: { skipMiddleware?: boolean },
|
|
98
|
+
) => Promise<ResolvedSegment[]>;
|
|
99
|
+
resolveInterceptLoadersOnly: (
|
|
100
|
+
interceptEntry: InterceptEntry,
|
|
101
|
+
parentEntry: EntryData,
|
|
102
|
+
params: Record<string, string>,
|
|
103
|
+
context: HandlerContext<any, TEnv>,
|
|
104
|
+
belongsToRoute?: boolean,
|
|
105
|
+
revalidationContext?: any,
|
|
106
|
+
) => Promise<{
|
|
107
|
+
loaderDataPromise: Promise<any[]> | any[];
|
|
108
|
+
loaderIds: string[];
|
|
109
|
+
} | null>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Create thin wrapper functions that bind segmentDeps to extracted
|
|
114
|
+
* segment resolution and intercept resolution functions.
|
|
115
|
+
*
|
|
116
|
+
* These maintain the same signatures as the original inline functions
|
|
117
|
+
* so that RouterContext and call sites don't need to change.
|
|
118
|
+
*/
|
|
119
|
+
export function createSegmentWrappers<TEnv = any>(
|
|
120
|
+
segmentDeps: SegmentResolutionDeps<TEnv>,
|
|
121
|
+
): SegmentWrappers<TEnv> {
|
|
122
|
+
function resolveAllSegments(
|
|
123
|
+
entries: EntryData[],
|
|
124
|
+
routeKey: string,
|
|
125
|
+
params: Record<string, string>,
|
|
126
|
+
context: HandlerContext<any, TEnv>,
|
|
127
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
128
|
+
options?: ResolveSegmentOptions,
|
|
129
|
+
): ReturnType<typeof _resolveAllSegments> {
|
|
130
|
+
return _resolveAllSegments(
|
|
131
|
+
entries,
|
|
132
|
+
routeKey,
|
|
133
|
+
params,
|
|
134
|
+
context,
|
|
135
|
+
loaderPromises,
|
|
136
|
+
segmentDeps,
|
|
137
|
+
options,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function resolveLoadersOnly(
|
|
142
|
+
entries: EntryData[],
|
|
143
|
+
context: HandlerContext<any, TEnv>,
|
|
144
|
+
): ReturnType<typeof _resolveLoadersOnly> {
|
|
145
|
+
return _resolveLoadersOnly(entries, context, segmentDeps);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolveLoadersOnlyWithRevalidation(
|
|
149
|
+
entries: EntryData[],
|
|
150
|
+
context: HandlerContext<any, TEnv>,
|
|
151
|
+
clientSegmentIds: Set<string>,
|
|
152
|
+
prevParams: Record<string, string>,
|
|
153
|
+
request: Request,
|
|
154
|
+
prevUrl: URL,
|
|
155
|
+
nextUrl: URL,
|
|
156
|
+
routeKey: string,
|
|
157
|
+
actionContext?: {
|
|
158
|
+
actionId?: string;
|
|
159
|
+
actionUrl?: URL;
|
|
160
|
+
actionResult?: any;
|
|
161
|
+
formData?: FormData;
|
|
162
|
+
},
|
|
163
|
+
stale?: boolean,
|
|
164
|
+
): ReturnType<typeof _resolveLoadersOnlyWithRevalidation> {
|
|
165
|
+
return _resolveLoadersOnlyWithRevalidation(
|
|
166
|
+
entries,
|
|
167
|
+
context,
|
|
168
|
+
clientSegmentIds,
|
|
169
|
+
prevParams,
|
|
170
|
+
request,
|
|
171
|
+
prevUrl,
|
|
172
|
+
nextUrl,
|
|
173
|
+
routeKey,
|
|
174
|
+
segmentDeps,
|
|
175
|
+
actionContext,
|
|
176
|
+
stale,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildEntryRevalidateMap(
|
|
181
|
+
entries: EntryData[],
|
|
182
|
+
): ReturnType<typeof _buildEntryRevalidateMap> {
|
|
183
|
+
return _buildEntryRevalidateMap(entries);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function resolveAllSegmentsWithRevalidation(
|
|
187
|
+
entries: EntryData[],
|
|
188
|
+
routeKey: string,
|
|
189
|
+
params: Record<string, string>,
|
|
190
|
+
context: HandlerContext<any, TEnv>,
|
|
191
|
+
clientSegmentSet: Set<string>,
|
|
192
|
+
prevParams: Record<string, string>,
|
|
193
|
+
request: Request,
|
|
194
|
+
prevUrl: URL,
|
|
195
|
+
nextUrl: URL,
|
|
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
|
+
actionContext,
|
|
220
|
+
interceptResult,
|
|
221
|
+
localRouteName,
|
|
222
|
+
pathname,
|
|
223
|
+
segmentDeps,
|
|
224
|
+
stale,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function findInterceptForRoute(
|
|
229
|
+
targetRouteKey: string,
|
|
230
|
+
fromEntry: EntryData | null,
|
|
231
|
+
selectorContext: InterceptSelectorContext | null = null,
|
|
232
|
+
isAction: boolean = false,
|
|
233
|
+
): ReturnType<typeof _findInterceptForRoute> {
|
|
234
|
+
return _findInterceptForRoute(
|
|
235
|
+
targetRouteKey,
|
|
236
|
+
fromEntry,
|
|
237
|
+
selectorContext,
|
|
238
|
+
isAction,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function resolveInterceptEntry(
|
|
243
|
+
interceptEntry: InterceptEntry,
|
|
244
|
+
parentEntry: EntryData,
|
|
245
|
+
params: Record<string, string>,
|
|
246
|
+
context: HandlerContext<any, TEnv>,
|
|
247
|
+
belongsToRoute: boolean = true,
|
|
248
|
+
revalidationContext?: any,
|
|
249
|
+
options?: { skipMiddleware?: boolean },
|
|
250
|
+
): ReturnType<typeof _resolveInterceptEntry> {
|
|
251
|
+
return _resolveInterceptEntry(
|
|
252
|
+
interceptEntry,
|
|
253
|
+
parentEntry,
|
|
254
|
+
params,
|
|
255
|
+
context,
|
|
256
|
+
belongsToRoute,
|
|
257
|
+
segmentDeps,
|
|
258
|
+
revalidationContext,
|
|
259
|
+
options,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function resolveInterceptLoadersOnly(
|
|
264
|
+
interceptEntry: InterceptEntry,
|
|
265
|
+
parentEntry: EntryData,
|
|
266
|
+
params: Record<string, string>,
|
|
267
|
+
context: HandlerContext<any, TEnv>,
|
|
268
|
+
belongsToRoute: boolean = true,
|
|
269
|
+
revalidationContext: any,
|
|
270
|
+
): ReturnType<typeof _resolveInterceptLoadersOnly> {
|
|
271
|
+
return _resolveInterceptLoadersOnly(
|
|
272
|
+
interceptEntry,
|
|
273
|
+
parentEntry,
|
|
274
|
+
params,
|
|
275
|
+
context,
|
|
276
|
+
belongsToRoute,
|
|
277
|
+
segmentDeps,
|
|
278
|
+
revalidationContext,
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
resolveAllSegments: resolveAllSegments,
|
|
284
|
+
resolveLoadersOnly: resolveLoadersOnly,
|
|
285
|
+
resolveLoadersOnlyWithRevalidation: resolveLoadersOnlyWithRevalidation,
|
|
286
|
+
buildEntryRevalidateMap: buildEntryRevalidateMap,
|
|
287
|
+
resolveAllSegmentsWithRevalidation: resolveAllSegmentsWithRevalidation,
|
|
288
|
+
findInterceptForRoute: findInterceptForRoute,
|
|
289
|
+
resolveInterceptEntry: resolveInterceptEntry,
|
|
290
|
+
resolveInterceptLoadersOnly: resolveInterceptLoadersOnly,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { DEFAULT_STATE_COOKIE_PREFIX } from "../browser/cookie-name.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the rango state cookie name once, server-side, at router init. The
|
|
5
|
+
* resolved string is shipped in payload metadata and the client reads it
|
|
6
|
+
* verbatim, so composition happens in exactly one place.
|
|
7
|
+
*
|
|
8
|
+
* Shape: `{sanitizedPrefix}_{sanitizedRouterId}`. The prefix charset excludes
|
|
9
|
+
* `_` so the FIRST `_` is always the prefix/routerId boundary; that keeps the
|
|
10
|
+
* name injective even though a routerId may legitimately contain `_` (the
|
|
11
|
+
* counter fallback is `router_{n}`). Without that exclusion, prefix
|
|
12
|
+
* `rango-state` + id `router_0` and prefix `rango-state_router` + id `0` would
|
|
13
|
+
* both resolve to `rango-state_router_0` and silently share a cache key.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Prefix excludes `_` so it can never collide with the separator.
|
|
17
|
+
function sanitizePrefix(prefix: string): string {
|
|
18
|
+
return prefix.replace(/[^A-Za-z0-9-]/g, "");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// routerId keeps `_` (so `router_0` survives); other illegal chars are dropped.
|
|
22
|
+
function sanitizeRouterId(routerId: string): string {
|
|
23
|
+
return routerId.replace(/[^A-Za-z0-9_-]/g, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function resolveStateCookieName(
|
|
27
|
+
prefix: string | undefined,
|
|
28
|
+
routerId: string,
|
|
29
|
+
): string {
|
|
30
|
+
const sanitized = sanitizePrefix(prefix ?? DEFAULT_STATE_COOKIE_PREFIX);
|
|
31
|
+
const finalPrefix = sanitized || DEFAULT_STATE_COOKIE_PREFIX;
|
|
32
|
+
return `${finalPrefix}_${sanitizeRouterId(routerId)}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { encodePathSegment, encodePathRemainder } from "./url-params.js";
|
|
2
|
+
import { parsePattern } from "./parse-pattern.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Substitute `:param` placeholders in a route pattern with values from
|
|
6
|
+
* `params`, producing a URL. Built by walking the SAME parsed segments the
|
|
7
|
+
* matcher uses (`parsePattern`) and emitting one piece per segment — so a
|
|
8
|
+
* substituted value is never re-scanned as if it were another placeholder (a
|
|
9
|
+
* catch-all value like `sha:abc/x` used to make the "required" pass read `:abc`
|
|
10
|
+
* and throw). Constraint syntax (`:name(en|gb)`) is stripped; trailing-slash
|
|
11
|
+
* patterns like `/blog/` are preserved unless an optional segment was omitted.
|
|
12
|
+
*
|
|
13
|
+
* Semantics per segment:
|
|
14
|
+
* - static -> emitted verbatim.
|
|
15
|
+
* - `:name` -> required; `undefined` throws, `""` yields an empty segment.
|
|
16
|
+
* - `:name?` -> optional; `undefined`/`""` omitted.
|
|
17
|
+
* - `:name*` / `:name+`-> catch-all; the value is multi-segment, so each segment
|
|
18
|
+
* is encoded and the `/` separators are preserved. `+`
|
|
19
|
+
* (one-or-more) throws when absent; `*` (and bare `*`)
|
|
20
|
+
* omit when absent.
|
|
21
|
+
*
|
|
22
|
+
* Shared by `ctx.reverse()` (server), `createReverse()` (typed runtime
|
|
23
|
+
* helper), and `useReverse()` (client hook). The behavior must stay
|
|
24
|
+
* identical across all three call sites.
|
|
25
|
+
*/
|
|
26
|
+
export function substitutePatternParams(
|
|
27
|
+
pattern: string,
|
|
28
|
+
params: Record<string, string | undefined>,
|
|
29
|
+
routeName: string,
|
|
30
|
+
): string {
|
|
31
|
+
const hasTrailingSlash = pattern.length > 1 && pattern.endsWith("/");
|
|
32
|
+
const normalized = hasTrailingSlash ? pattern.slice(0, -1) : pattern;
|
|
33
|
+
const segments = parsePattern(normalized);
|
|
34
|
+
|
|
35
|
+
const parts: string[] = [];
|
|
36
|
+
for (const seg of segments) {
|
|
37
|
+
if (seg.type === "static") {
|
|
38
|
+
parts.push("/" + seg.value);
|
|
39
|
+
} else if (seg.type === "wildcard") {
|
|
40
|
+
const value = params[seg.value];
|
|
41
|
+
if (value === undefined || value === "") {
|
|
42
|
+
// `:name+` requires at least one segment; bare `*` / `:name*` collapse.
|
|
43
|
+
if (seg.oneOrMore) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Missing param "${seg.value}" for route "${routeName}"`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
} else {
|
|
49
|
+
parts.push("/" + encodePathRemainder(value));
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// Plain param. Constraint (`seg.constraint`) is intentionally not re-emitted.
|
|
53
|
+
const value = params[seg.value];
|
|
54
|
+
const suffix = seg.suffix ?? "";
|
|
55
|
+
if (seg.optional) {
|
|
56
|
+
// The matcher omits absent optionals (`undefined`); callers/getParams()
|
|
57
|
+
// may pass `""` explicitly — treat both as absent.
|
|
58
|
+
if (value !== undefined && value !== "") {
|
|
59
|
+
parts.push("/" + encodePathSegment(value) + suffix);
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
if (value === undefined) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`Missing param "${seg.value}" for route "${routeName}"`,
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
parts.push("/" + encodePathSegment(value) + suffix);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
let result = parts.join("") || "/";
|
|
73
|
+
if (hasTrailingSlash && !result.endsWith("/")) result += "/";
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry adapters for the router.
|
|
3
|
+
*
|
|
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.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* import { trace } from "@opentelemetry/api";
|
|
26
|
+
* import { createRouter, createOTelTracing, createOTelSink } from "@rangojs/router";
|
|
27
|
+
*
|
|
28
|
+
* const tracer = trace.getTracer("my-app");
|
|
29
|
+
* const router = createRouter({
|
|
30
|
+
* tracing: createOTelTracing(tracer), // phase spans (callback-bound)
|
|
31
|
+
* telemetry: createOTelSink(tracer), // discrete-fact instant spans
|
|
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.
|
|
37
|
+
*/
|
|
38
|
+
|
|
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";
|
|
46
|
+
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Minimal OTel-compatible types (structurally typed, no import needed)
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Minimal Span interface compatible with @opentelemetry/api Span.
|
|
53
|
+
* Only the methods used by the adapters are declared.
|
|
54
|
+
*/
|
|
55
|
+
export interface OTelSpan {
|
|
56
|
+
setAttribute(key: string, value: string | number | boolean): OTelSpan | void;
|
|
57
|
+
setStatus(status: { code: number; message?: string }): OTelSpan | void;
|
|
58
|
+
recordException(exception: Error): void;
|
|
59
|
+
end(): void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
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.
|
|
68
|
+
*/
|
|
69
|
+
export interface OTelTracer {
|
|
70
|
+
startSpan(
|
|
71
|
+
name: string,
|
|
72
|
+
options?: {
|
|
73
|
+
attributes?: Record<string, string | number | boolean>;
|
|
74
|
+
},
|
|
75
|
+
): OTelSpan;
|
|
76
|
+
}
|
|
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
|
+
|
|
88
|
+
// OTel SpanStatusCode constants (mirrors @opentelemetry/api values)
|
|
89
|
+
const STATUS_ERROR = 2;
|
|
90
|
+
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// Tracing adapter: phase spans via startActiveSpan (the `tracing` slot)
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
|
|
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;
|
|
101
|
+
}
|
|
102
|
+
|
|
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
|
+
);
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
runner,
|
|
145
|
+
enabled: options.enabled ?? true,
|
|
146
|
+
spans: options.spans,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Telemetry sink: discrete-fact instant spans (the `telemetry` slot)
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
/**
|
|
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)
|
|
164
|
+
*
|
|
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.
|
|
169
|
+
*
|
|
170
|
+
* Attributes use the `rango.*` namespace for router-specific data and
|
|
171
|
+
* `http.method` / `http.route` for HTTP semantics.
|
|
172
|
+
*/
|
|
173
|
+
export function createOTelSink(tracer: OTelTracer): TelemetrySink {
|
|
174
|
+
const instant = (
|
|
175
|
+
name: string,
|
|
176
|
+
attributes: Record<string, string | number | boolean>,
|
|
177
|
+
): OTelSpan => tracer.startSpan(name, { attributes });
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
emit(event: TelemetryEvent): void {
|
|
181
|
+
switch (event.type) {
|
|
182
|
+
case "handler.error": {
|
|
183
|
+
const attrs: Record<string, string | number | boolean> = {
|
|
184
|
+
"rango.handled_by_boundary": event.handledByBoundary,
|
|
185
|
+
};
|
|
186
|
+
if (event.segmentId) attrs["rango.segment_id"] = event.segmentId;
|
|
187
|
+
if (event.segmentType)
|
|
188
|
+
attrs["rango.segment_type"] = event.segmentType;
|
|
189
|
+
if (event.pathname) attrs["http.route"] = event.pathname;
|
|
190
|
+
if (event.routeKey) attrs["rango.route_key"] = event.routeKey;
|
|
191
|
+
if (event.params)
|
|
192
|
+
attrs["rango.params"] = JSON.stringify(event.params);
|
|
193
|
+
|
|
194
|
+
const span = instant("rango.handler.error", attrs);
|
|
195
|
+
span.recordException(event.error);
|
|
196
|
+
span.setStatus({ code: STATUS_ERROR, message: event.error.message });
|
|
197
|
+
span.end();
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
case "cache.decision": {
|
|
202
|
+
const attrs: Record<string, string | number | boolean> = {
|
|
203
|
+
"http.route": event.pathname,
|
|
204
|
+
"rango.route_key": event.routeKey,
|
|
205
|
+
"rango.cache.hit": event.hit,
|
|
206
|
+
"rango.cache.should_revalidate": event.shouldRevalidate,
|
|
207
|
+
};
|
|
208
|
+
if (event.source) attrs["rango.cache.source"] = event.source;
|
|
209
|
+
instant("rango.cache.decision", attrs).end();
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
|
|
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}`,
|
|
236
|
+
});
|
|
237
|
+
span.end();
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
|
|
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" });
|
|
251
|
+
span.end();
|
|
252
|
+
break;
|
|
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.
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|