@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
|
@@ -11,16 +11,66 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
+
import { parseCookiesFromHeader } from "./cookie-parse.js";
|
|
15
|
+
import type { CacheErrorCategory } from "../cache/cache-error.js";
|
|
14
16
|
import type { CookieOptions } from "../router/middleware.js";
|
|
17
|
+
import {
|
|
18
|
+
KEEP_CACHE_HEADER,
|
|
19
|
+
getRawCookieValue,
|
|
20
|
+
mintStateValue,
|
|
21
|
+
serializeStateCookie,
|
|
22
|
+
} from "../browser/cookie-name.js";
|
|
15
23
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
24
|
+
import type { ScopedReverseFunction } from "../reverse.js";
|
|
25
|
+
import type {
|
|
26
|
+
DefaultEnv,
|
|
27
|
+
DefaultReverseRouteMap,
|
|
28
|
+
DefaultRouteName,
|
|
29
|
+
} from "../types/global-namespace.js";
|
|
16
30
|
import type { Handle } from "../handle.js";
|
|
17
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
type ContextVar,
|
|
33
|
+
contextGet,
|
|
34
|
+
contextSet,
|
|
35
|
+
isNonCacheable,
|
|
36
|
+
} from "../context-var.js";
|
|
37
|
+
import {
|
|
38
|
+
createHandleStore,
|
|
39
|
+
buildHandleSnapshot,
|
|
40
|
+
type HandleStore,
|
|
41
|
+
type HandleData,
|
|
42
|
+
} from "./handle-store.js";
|
|
18
43
|
import { isHandle } from "../handle.js";
|
|
19
|
-
import {
|
|
44
|
+
import { withDefer } from "../defer.js";
|
|
45
|
+
import { type MetricsStore } from "./context.js";
|
|
46
|
+
import { observePhase, PHASES } from "../router/instrument.js";
|
|
20
47
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
21
48
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
22
49
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
23
|
-
import {
|
|
50
|
+
import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
|
|
51
|
+
import type { TransitionWhenFn } from "../types/segments.js";
|
|
52
|
+
import type { ResolvedTracing } from "../router/tracing.js";
|
|
53
|
+
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
54
|
+
import {
|
|
55
|
+
THEME_COOKIE,
|
|
56
|
+
isValidTheme,
|
|
57
|
+
warnInvalidTheme,
|
|
58
|
+
} from "../theme/constants.js";
|
|
59
|
+
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
60
|
+
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
61
|
+
import { isInsideCacheScope } from "./context.js";
|
|
62
|
+
import {
|
|
63
|
+
createReverseFunction,
|
|
64
|
+
stripInternalParams,
|
|
65
|
+
} from "../router/handler-context.js";
|
|
66
|
+
import {
|
|
67
|
+
getGlobalRouteMap,
|
|
68
|
+
isRouteRootScoped,
|
|
69
|
+
getSearchSchema,
|
|
70
|
+
} from "../route-map-builder.js";
|
|
71
|
+
import { parseSearchParams } from "../search-params.js";
|
|
72
|
+
import { invariant } from "../errors.js";
|
|
73
|
+
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
24
74
|
|
|
25
75
|
/**
|
|
26
76
|
* Unified request context available via getRequestContext()
|
|
@@ -29,46 +79,54 @@ import { THEME_COOKIE } from "../theme/constants.js";
|
|
|
29
79
|
* Use this when you need access to request data outside of route handlers.
|
|
30
80
|
*/
|
|
31
81
|
export interface RequestContext<
|
|
32
|
-
TEnv =
|
|
82
|
+
TEnv = DefaultEnv,
|
|
33
83
|
TParams = Record<string, string>,
|
|
34
|
-
> {
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
/** Original HTTP request */
|
|
38
|
-
request: Request;
|
|
39
|
-
/** Parsed URL (system params like _rsc* are NOT filtered here) */
|
|
40
|
-
url: URL;
|
|
41
|
-
/** URL pathname */
|
|
42
|
-
pathname: string;
|
|
43
|
-
/** URL search params (system params like _rsc* are NOT filtered here) */
|
|
44
|
-
searchParams: URLSearchParams;
|
|
45
|
-
/** Variables set by middleware (same as ctx.var) */
|
|
46
|
-
var: Record<string, any>;
|
|
84
|
+
> extends RequestScope<TEnv> {
|
|
85
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
86
|
+
_variables: Record<string, any>;
|
|
47
87
|
/** Get a variable set by middleware */
|
|
48
|
-
get:
|
|
88
|
+
get: {
|
|
89
|
+
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
90
|
+
<K extends string>(key: K): any;
|
|
91
|
+
};
|
|
49
92
|
/** Set a variable (shared with middleware and handlers) */
|
|
50
|
-
set:
|
|
93
|
+
set: {
|
|
94
|
+
<T>(
|
|
95
|
+
contextVar: ContextVar<T>,
|
|
96
|
+
value: T,
|
|
97
|
+
options?: { cache?: boolean },
|
|
98
|
+
): void;
|
|
99
|
+
<K extends string>(key: K, value: any, options?: { cache?: boolean }): void;
|
|
100
|
+
};
|
|
51
101
|
/**
|
|
52
102
|
* Route params (populated after route matching)
|
|
53
103
|
* Initially empty, then set to matched params
|
|
54
104
|
*/
|
|
55
105
|
params: TParams;
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
* Headers set here are merged into the final response
|
|
59
|
-
*/
|
|
60
|
-
res: Response;
|
|
106
|
+
/** @internal Stub response for collecting headers/cookies. Use ctx.headers or ctx.header() instead. */
|
|
107
|
+
readonly res: Response;
|
|
61
108
|
|
|
62
|
-
/** Get a cookie value
|
|
109
|
+
/** @internal Get a cookie value (effective: request + response mutations). Use cookies().get() instead. */
|
|
63
110
|
cookie(name: string): string | undefined;
|
|
64
|
-
/** Get all cookies
|
|
111
|
+
/** @internal Get all cookies (effective merged view). Use cookies().getAll() instead. */
|
|
65
112
|
cookies(): Record<string, string>;
|
|
66
|
-
/** Set a cookie on the response */
|
|
113
|
+
/** @internal Set a cookie on the response. Use cookies().set() instead. */
|
|
67
114
|
setCookie(name: string, value: string, options?: CookieOptions): void;
|
|
68
|
-
/** Delete a cookie */
|
|
69
|
-
deleteCookie(
|
|
115
|
+
/** @internal Delete a cookie. Use cookies().delete() instead. */
|
|
116
|
+
deleteCookie(
|
|
117
|
+
name: string,
|
|
118
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
119
|
+
): void;
|
|
70
120
|
/** Set a response header */
|
|
71
121
|
header(name: string, value: string): void;
|
|
122
|
+
/** Set the response status code */
|
|
123
|
+
setStatus(status: number): void;
|
|
124
|
+
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
125
|
+
_setStatus(status: number): void;
|
|
126
|
+
/** @internal Rotate the rango state cookie (server seat of invalidateClientCache). */
|
|
127
|
+
_rotateStateCookie(): void;
|
|
128
|
+
/** @internal Set the keepClientCache() directive header on the response. */
|
|
129
|
+
_setKeepCacheDirective(): void;
|
|
72
130
|
|
|
73
131
|
/**
|
|
74
132
|
* Access loader data or push handle data.
|
|
@@ -90,10 +148,12 @@ export interface RequestContext<
|
|
|
90
148
|
* ```
|
|
91
149
|
*/
|
|
92
150
|
use: {
|
|
93
|
-
<T, TLoaderParams = any>(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
151
|
+
<T, TLoaderParams = any>(
|
|
152
|
+
loader: LoaderDefinition<T, TLoaderParams>,
|
|
153
|
+
): Promise<T>;
|
|
154
|
+
<TData, TAccumulated = TData[]>(
|
|
155
|
+
handle: Handle<TData, TAccumulated>,
|
|
156
|
+
): (data: TData | Promise<TData> | (() => Promise<TData>)) => void;
|
|
97
157
|
};
|
|
98
158
|
|
|
99
159
|
/** HTTP method (GET, POST, PUT, PATCH, DELETE, etc.) */
|
|
@@ -102,22 +162,88 @@ export interface RequestContext<
|
|
|
102
162
|
/** @internal Handle store for tracking handle data across segments */
|
|
103
163
|
_handleStore: HandleStore;
|
|
104
164
|
|
|
165
|
+
/**
|
|
166
|
+
* @internal transition({ when }) predicates for segments matched this request,
|
|
167
|
+
* keyed by segment id. Collected during resolution (the function is stripped
|
|
168
|
+
* from the serialized segment config), then evaluated post-handler in
|
|
169
|
+
* rsc-rendering — outside any cache scope — to drop the transition of any
|
|
170
|
+
* segment whose predicate returns false.
|
|
171
|
+
*/
|
|
172
|
+
_transitionWhen?: Array<{ id: string; when: TransitionWhenFn }>;
|
|
173
|
+
|
|
105
174
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
106
175
|
_cacheStore?: SegmentCacheStore;
|
|
107
176
|
|
|
108
177
|
/**
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
178
|
+
* @internal PPR shell-resume signal. Set by the shell-cache middleware on a
|
|
179
|
+
* validated shell HIT before it awaits next(); read in the render orchestration
|
|
180
|
+
* (rsc-rendering) to call the resume strategy instead of a full fizz render. The
|
|
181
|
+
* middleware sets it optimistically — the render layer is the final authority
|
|
182
|
+
* (nonce/formState/allReady bypass) and marks the response only when it actually
|
|
183
|
+
* resumed. Single-request; the middleware clears it in a finally.
|
|
184
|
+
*/
|
|
185
|
+
_shellResume?: { postponed: string | null };
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @internal PPR shell-capture DESCRIPTOR ("a capture of this document is
|
|
189
|
+
* wanted"). Set by the shell-cache middleware BEFORE its single foreground
|
|
190
|
+
* next() on a MISS or SWR stale hit, and read by the render orchestration
|
|
191
|
+
* (rsc-rendering) AFTER the response is built to schedule a background capture
|
|
192
|
+
* task. Its mere presence must NOT change the foreground render — loader
|
|
193
|
+
* masking and the cookie/header capture guard key off `_shellCaptureRun`, not
|
|
194
|
+
* this descriptor. `store` carries the SAME store the middleware resolved for
|
|
195
|
+
* its getShell read (options.store ?? _cacheStore), so a store-attached
|
|
196
|
+
* middleware writes captures where it reads them; without it an explicit
|
|
197
|
+
* options.store distinct from the app-level _cacheStore would read one store
|
|
198
|
+
* and write another and the shell would never HIT. `tags` is left unset here —
|
|
199
|
+
* the background capture collects the shell's own (non-loader) request tags
|
|
200
|
+
* from its derived render. Single-request; the middleware clears it in a
|
|
201
|
+
* finally after next() settles.
|
|
119
202
|
*/
|
|
120
|
-
|
|
203
|
+
_shellCapture?: {
|
|
204
|
+
key: string;
|
|
205
|
+
ttl?: number;
|
|
206
|
+
swr?: number;
|
|
207
|
+
tags?: string[];
|
|
208
|
+
store?: SegmentCacheStore<any>;
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* @internal PPR shell-capture ACTIVE marker. True ONLY inside the background
|
|
213
|
+
* capture task's derived request context (built by shell-capture.ts). This is
|
|
214
|
+
* the switch every capture-specific behavior reads: loader masking
|
|
215
|
+
* (loader-mask.ts isShellCaptureActive / fresh.ts emitStreaming) and the
|
|
216
|
+
* cookies()/headers() capture guard (cookie-store.ts
|
|
217
|
+
* assertNotInsideShellCapture). The foreground render never sets it — even when
|
|
218
|
+
* `_shellCapture` (the "capture wanted" descriptor) is present — so the served
|
|
219
|
+
* response is byte-identical to axis 1.
|
|
220
|
+
*/
|
|
221
|
+
_shellCaptureRun?: boolean;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @internal Handler-owned registry of explicit per-scope stores from
|
|
225
|
+
* cache({ store }). Created once per createRSCHandler() and threaded into
|
|
226
|
+
* every request context, so it accumulates every explicit store the handler
|
|
227
|
+
* resolves. updateTag()/revalidateTag() iterate this set plus _cacheStore to
|
|
228
|
+
* reach every store that may hold tagged entries. The app-level store is not
|
|
229
|
+
* added here (it is always reachable via _cacheStore).
|
|
230
|
+
*/
|
|
231
|
+
_explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @internal Union of every cache tag resolved while producing this request's
|
|
235
|
+
* response (from cache({ tags }), runtime cacheTag(), and loader cache tags).
|
|
236
|
+
* Populated at the tag-resolution sites via recordRequestTags(). Read by the
|
|
237
|
+
* document cache middleware so a full-page entry is tagged with everything its
|
|
238
|
+
* content used and can therefore be invalidated by updateTag()/revalidateTag().
|
|
239
|
+
*/
|
|
240
|
+
_requestTags: Set<string>;
|
|
241
|
+
|
|
242
|
+
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
243
|
+
_cacheProfiles?: Record<
|
|
244
|
+
string,
|
|
245
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
246
|
+
>;
|
|
121
247
|
|
|
122
248
|
/**
|
|
123
249
|
* Register a callback to run when the response is created.
|
|
@@ -178,8 +304,237 @@ export interface RequestContext<
|
|
|
178
304
|
|
|
179
305
|
/** @internal Theme configuration (null if theme not enabled) */
|
|
180
306
|
_themeConfig?: ResolvedThemeConfig | null;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Attach location state entries to the current response.
|
|
310
|
+
*
|
|
311
|
+
* For partial (SPA) requests, the state is included in the RSC payload
|
|
312
|
+
* metadata and merged into history.pushState on the client. For redirect
|
|
313
|
+
* responses, the state travels through the redirect payload so the target
|
|
314
|
+
* page can read it via useLocationState.
|
|
315
|
+
*
|
|
316
|
+
* Multiple calls accumulate entries.
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* ctx.setLocationState(Flash({ text: "Item saved!" }));
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void;
|
|
324
|
+
|
|
325
|
+
/** @internal Accumulated location state entries */
|
|
326
|
+
_locationState?: LocationStateEntry[];
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* The matched route name, if the route has an explicit name.
|
|
330
|
+
* Undefined before route matching or for unnamed routes.
|
|
331
|
+
* Includes the namespace prefix from include() (e.g., "blog.post").
|
|
332
|
+
*/
|
|
333
|
+
routeName?: DefaultRouteName;
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Generate URLs from route names.
|
|
337
|
+
* Uses the global route map. After route matching, scoped (`.name`) resolution
|
|
338
|
+
* works within the matched include() scope.
|
|
339
|
+
*/
|
|
340
|
+
reverse: ScopedReverseFunction<
|
|
341
|
+
Record<string, string>,
|
|
342
|
+
DefaultReverseRouteMap
|
|
343
|
+
>;
|
|
344
|
+
|
|
345
|
+
/** @internal Route name from route matching, used for scoped reverse resolution */
|
|
346
|
+
_routeName?: string;
|
|
347
|
+
|
|
348
|
+
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
349
|
+
_prevRouteKey?: string;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @internal Navigation/action source data the transition({ when }) gate reads
|
|
353
|
+
* to build its ShouldRevalidateFn-shaped predicate context. currentUrl/Params
|
|
354
|
+
* come from the navigation snapshot (set at match time); action* are stashed
|
|
355
|
+
* at the action-bearing gate call sites. All undefined when there is no source
|
|
356
|
+
* (initial full load) or no action (plain navigation).
|
|
357
|
+
*/
|
|
358
|
+
_gateCurrentUrl?: URL;
|
|
359
|
+
_gateCurrentParams?: Record<string, string>;
|
|
360
|
+
_gateActionId?: string;
|
|
361
|
+
_gateActionUrl?: URL;
|
|
362
|
+
_gateActionResult?: unknown;
|
|
363
|
+
_gateFormData?: FormData;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @internal True while the post-action revalidation render is running (set by
|
|
367
|
+
* revalidateAfterAction). The "use cache" runtime reads this to prefer
|
|
368
|
+
* freshness over a fast stale response during an action: a stale entry
|
|
369
|
+
* re-executes in the foreground (so the action response reflects the refreshed
|
|
370
|
+
* value) with only the store write deferred, instead of serving stale and
|
|
371
|
+
* revalidating in the background. A plain navigation (flag unset) keeps SWR.
|
|
372
|
+
*/
|
|
373
|
+
_inActionRevalidation?: boolean;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @internal Render barrier for experimental `rendered()` API.
|
|
377
|
+
* Resolves when all non-loader segments have settled and handle data
|
|
378
|
+
* is available. Used by DSL loaders that call `ctx.rendered()`.
|
|
379
|
+
*/
|
|
380
|
+
_renderBarrier: Promise<void>;
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* @internal Resolve the render barrier. Accepts resolved segments, filters
|
|
384
|
+
* out loaders, and captures non-loader segment IDs as the handle ordering.
|
|
385
|
+
* Called after segment resolution (fresh) or handle replay (cache/prerender).
|
|
386
|
+
*/
|
|
387
|
+
_resolveRenderBarrier: (
|
|
388
|
+
segments: Array<{ type: string; id: string }>,
|
|
389
|
+
) => void;
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* @internal Segment order at barrier resolution time, used by loader
|
|
393
|
+
* ctx.use(handle) to collect handle data in correct order.
|
|
394
|
+
*/
|
|
395
|
+
_renderBarrierSegmentOrder?: string[];
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
399
|
+
* entries (streaming). On a streaming tree rendered() waits for the streaming
|
|
400
|
+
* handlers to settle (via handleStore.settled) before resolving, and the
|
|
401
|
+
* deadlock guard state is kept live until that wait completes.
|
|
402
|
+
*/
|
|
403
|
+
_treeHasStreaming?: boolean;
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* @internal Loader IDs that have called rendered() and are waiting for the
|
|
407
|
+
* barrier. Used to detect deadlocks when a handler tries to await the same
|
|
408
|
+
* loader via ctx.use(Loader).
|
|
409
|
+
*/
|
|
410
|
+
_renderBarrierWaiters?: Set<string>;
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @internal Loader IDs that handlers have started awaiting via ctx.use().
|
|
414
|
+
* Used for bidirectional deadlock detection: if a loader later calls
|
|
415
|
+
* rendered() and a handler already awaits it, we can detect the deadlock.
|
|
416
|
+
*/
|
|
417
|
+
_handlerLoaderDeps?: Set<string>;
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* @internal Cached HandleData snapshot built at barrier resolution time.
|
|
421
|
+
* Avoids rebuilding the snapshot on every loader ctx.use(handle) call.
|
|
422
|
+
*/
|
|
423
|
+
_renderBarrierHandleSnapshot?: HandleData;
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* @internal The deadlock guard window is closed (no further handler-awaits-
|
|
427
|
+
* loader cycle is possible). For non-streaming trees this is set when the
|
|
428
|
+
* barrier resolves. For streaming trees the window stays open until
|
|
429
|
+
* handleStore.settled — rendered() keeps waiting past the barrier and a
|
|
430
|
+
* loading() handler can still resume and await a still-waiting loader — so it
|
|
431
|
+
* is set only after settled. The guard (loader-resolution `setupLoaderAccess`)
|
|
432
|
+
* reads this instead of `_renderBarrierSegmentOrder` so it does not go blind
|
|
433
|
+
* during the streaming settle wait.
|
|
434
|
+
*/
|
|
435
|
+
_renderBarrierGuardClosed?: boolean;
|
|
436
|
+
|
|
437
|
+
/** @internal Per-request error dedup set for onError reporting */
|
|
438
|
+
_reportedErrors: WeakSet<object>;
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* @internal Report a non-fatal background error through the router's
|
|
442
|
+
* onError callback. Wired by the RSC handler / router during request
|
|
443
|
+
* creation. Cache-runtime and other subsystems call this to surface
|
|
444
|
+
* errors without failing the response. `category` is surfaced to consumers as
|
|
445
|
+
* `metadata.category` on the onError context (phase `cache`).
|
|
446
|
+
*/
|
|
447
|
+
_reportBackgroundError?: (
|
|
448
|
+
error: unknown,
|
|
449
|
+
category: CacheErrorCategory,
|
|
450
|
+
) => void;
|
|
451
|
+
|
|
452
|
+
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
453
|
+
_debugPerformance?: boolean;
|
|
454
|
+
|
|
455
|
+
/** @internal Request-scoped performance metrics store */
|
|
456
|
+
_metricsStore?: MetricsStore;
|
|
457
|
+
|
|
458
|
+
/** @internal Resolved platform phase-span tracing for this request (Cloudflare or OTel) */
|
|
459
|
+
_tracing?: ResolvedTracing;
|
|
460
|
+
|
|
461
|
+
/** @internal Router basename for this request (used by redirect()) */
|
|
462
|
+
_basename?: string;
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
|
|
466
|
+
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
467
|
+
*/
|
|
468
|
+
_classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* @internal Coarse route-level cache signal for the X-Rango-Cache debug
|
|
472
|
+
* header. Populated by match/matchPartial only when the debug cache signal
|
|
473
|
+
* gate is enabled (debugCacheSignal option or RANGO_TEST_SIGNALS=1). Read by
|
|
474
|
+
* the response-finalization path (createResponseWithMergedHeaders). Undefined
|
|
475
|
+
* when the gate is off, so no header is emitted.
|
|
476
|
+
*/
|
|
477
|
+
_cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
|
|
181
478
|
}
|
|
182
479
|
|
|
480
|
+
/**
|
|
481
|
+
* Public view of RequestContext, without internal methods and fields.
|
|
482
|
+
*
|
|
483
|
+
* This is the type exported to library consumers. Internal code should
|
|
484
|
+
* use the full RequestContext interface directly.
|
|
485
|
+
*/
|
|
486
|
+
export type PublicRequestContext<
|
|
487
|
+
TEnv = DefaultEnv,
|
|
488
|
+
TParams = Record<string, string>,
|
|
489
|
+
> = Omit<
|
|
490
|
+
RequestContext<TEnv, TParams>,
|
|
491
|
+
| "cookie"
|
|
492
|
+
| "cookies"
|
|
493
|
+
| "setCookie"
|
|
494
|
+
| "deleteCookie"
|
|
495
|
+
| "_handleStore"
|
|
496
|
+
| "_transitionWhen"
|
|
497
|
+
| "_cacheStore"
|
|
498
|
+
| "_shellResume"
|
|
499
|
+
| "_shellCapture"
|
|
500
|
+
| "_shellCaptureRun"
|
|
501
|
+
| "_explicitTaggedStores"
|
|
502
|
+
| "_requestTags"
|
|
503
|
+
| "_cacheProfiles"
|
|
504
|
+
| "_onResponseCallbacks"
|
|
505
|
+
| "_themeConfig"
|
|
506
|
+
| "_locationState"
|
|
507
|
+
| "_routeName"
|
|
508
|
+
| "_prevRouteKey"
|
|
509
|
+
| "_gateCurrentUrl"
|
|
510
|
+
| "_gateCurrentParams"
|
|
511
|
+
| "_gateActionId"
|
|
512
|
+
| "_gateActionUrl"
|
|
513
|
+
| "_gateActionResult"
|
|
514
|
+
| "_gateFormData"
|
|
515
|
+
| "_inActionRevalidation"
|
|
516
|
+
| "_reportedErrors"
|
|
517
|
+
| "_renderBarrier"
|
|
518
|
+
| "_resolveRenderBarrier"
|
|
519
|
+
| "_renderBarrierSegmentOrder"
|
|
520
|
+
| "_treeHasStreaming"
|
|
521
|
+
| "_renderBarrierWaiters"
|
|
522
|
+
| "_handlerLoaderDeps"
|
|
523
|
+
| "_renderBarrierHandleSnapshot"
|
|
524
|
+
| "_renderBarrierGuardClosed"
|
|
525
|
+
| "_reportBackgroundError"
|
|
526
|
+
| "_debugPerformance"
|
|
527
|
+
| "_metricsStore"
|
|
528
|
+
| "_basename"
|
|
529
|
+
| "_setStatus"
|
|
530
|
+
| "_rotateStateCookie"
|
|
531
|
+
| "_setKeepCacheDirective"
|
|
532
|
+
| "_variables"
|
|
533
|
+
| "_classifiedRoute"
|
|
534
|
+
| "_cacheSignal"
|
|
535
|
+
| "res"
|
|
536
|
+
>;
|
|
537
|
+
|
|
183
538
|
// AsyncLocalStorage instance for request context
|
|
184
539
|
const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
185
540
|
|
|
@@ -189,16 +544,33 @@ const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
|
189
544
|
*/
|
|
190
545
|
export function runWithRequestContext<TEnv, T>(
|
|
191
546
|
context: RequestContext<TEnv>,
|
|
192
|
-
fn: () => T
|
|
547
|
+
fn: () => T,
|
|
193
548
|
): T {
|
|
194
549
|
return requestContextStorage.run(context, fn);
|
|
195
550
|
}
|
|
196
551
|
|
|
197
552
|
/**
|
|
198
553
|
* Get the current request context
|
|
199
|
-
*
|
|
554
|
+
* Throws if called outside of a request context
|
|
555
|
+
*/
|
|
556
|
+
export function getRequestContext<TEnv = DefaultEnv>(): RequestContext<TEnv> {
|
|
557
|
+
const ctx = requestContextStorage.getStore() as
|
|
558
|
+
| RequestContext<TEnv>
|
|
559
|
+
| undefined;
|
|
560
|
+
invariant(
|
|
561
|
+
ctx,
|
|
562
|
+
"getRequestContext() called outside of a request context. " +
|
|
563
|
+
"This function must be called from within a route handler, loader, middleware, " +
|
|
564
|
+
"server action, or server component.",
|
|
565
|
+
);
|
|
566
|
+
return ctx;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* @internal Get the request context without throwing — for internal code that
|
|
571
|
+
* may run outside a request context (cache stores, optional handle lookups, etc.)
|
|
200
572
|
*/
|
|
201
|
-
export function
|
|
573
|
+
export function _getRequestContext<TEnv = DefaultEnv>():
|
|
202
574
|
| RequestContext<TEnv>
|
|
203
575
|
| undefined {
|
|
204
576
|
return requestContextStorage.getStore() as RequestContext<TEnv> | undefined;
|
|
@@ -206,38 +578,72 @@ export function getRequestContext<TEnv = unknown>():
|
|
|
206
578
|
|
|
207
579
|
/**
|
|
208
580
|
* Update params on the current request context
|
|
209
|
-
* Called after route matching to populate route params
|
|
581
|
+
* Called after route matching to populate route params and route name
|
|
210
582
|
*/
|
|
211
|
-
export function setRequestContextParams(
|
|
583
|
+
export function setRequestContextParams(
|
|
584
|
+
params: Record<string, string>,
|
|
585
|
+
routeName?: string,
|
|
586
|
+
routeMap?: Record<string, string>,
|
|
587
|
+
): void {
|
|
212
588
|
const ctx = requestContextStorage.getStore();
|
|
213
589
|
if (ctx) {
|
|
214
590
|
ctx.params = params;
|
|
591
|
+
if (routeName !== undefined) {
|
|
592
|
+
ctx._routeName = routeName;
|
|
593
|
+
ctx.routeName = (
|
|
594
|
+
routeName && !isAutoGeneratedRouteName(routeName)
|
|
595
|
+
? routeName
|
|
596
|
+
: undefined
|
|
597
|
+
) as DefaultRouteName | undefined;
|
|
598
|
+
}
|
|
599
|
+
// Update reverse with scoped resolution now that route is known. Production
|
|
600
|
+
// omits routeMap and uses the global map (routes are registered globally);
|
|
601
|
+
// the testing primitives (renderToFlightString/renderServerTree) pass a
|
|
602
|
+
// scoped routeMap so `ctx.reverse` is not order-dependent on whatever router
|
|
603
|
+
// registered last.
|
|
604
|
+
ctx.reverse = createReverseFunction(
|
|
605
|
+
routeMap ?? getGlobalRouteMap(),
|
|
606
|
+
routeName,
|
|
607
|
+
params,
|
|
608
|
+
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
609
|
+
);
|
|
215
610
|
}
|
|
216
611
|
}
|
|
217
612
|
|
|
218
613
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
614
|
+
* Store the previous route key on the request context.
|
|
615
|
+
* Called during partial-match context creation to make the navigation source
|
|
616
|
+
* route key available for revalidation and intercept evaluation.
|
|
617
|
+
* @internal
|
|
221
618
|
*/
|
|
222
|
-
export function
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
619
|
+
export function setRequestContextPrevRouteKey(
|
|
620
|
+
prevRouteKey: string | undefined,
|
|
621
|
+
currentUrl?: URL,
|
|
622
|
+
currentParams?: Record<string, string>,
|
|
623
|
+
): void {
|
|
624
|
+
const ctx = requestContextStorage.getStore();
|
|
625
|
+
if (!ctx) return;
|
|
626
|
+
if (prevRouteKey !== undefined) ctx._prevRouteKey = prevRouteKey;
|
|
627
|
+
// Source URL/params for the transition({ when }) gate (effectiveFromUrl /
|
|
628
|
+
// effectiveFromMatch.params from the navigation snapshot). Same write point as
|
|
629
|
+
// _prevRouteKey, which doubles as fromRouteName.
|
|
630
|
+
if (currentUrl !== undefined) ctx._gateCurrentUrl = currentUrl;
|
|
631
|
+
if (currentParams !== undefined) ctx._gateCurrentParams = currentParams;
|
|
231
632
|
}
|
|
232
633
|
|
|
233
634
|
/**
|
|
234
|
-
*
|
|
635
|
+
* Get accumulated location state entries from the current request context.
|
|
636
|
+
* Returns undefined if no state has been set.
|
|
637
|
+
*
|
|
638
|
+
* @internal Used by the RSC handler to include state in payload metadata.
|
|
235
639
|
*/
|
|
236
|
-
export
|
|
237
|
-
|
|
238
|
-
|
|
640
|
+
export function getLocationState(): LocationStateEntry[] | undefined {
|
|
641
|
+
const ctx = getRequestContext();
|
|
642
|
+
return ctx?._locationState;
|
|
239
643
|
}
|
|
240
644
|
|
|
645
|
+
export type { ExecutionContext };
|
|
646
|
+
|
|
241
647
|
/**
|
|
242
648
|
* Options for creating a request context
|
|
243
649
|
*/
|
|
@@ -246,12 +652,28 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
246
652
|
request: Request;
|
|
247
653
|
url: URL;
|
|
248
654
|
variables: Record<string, any>;
|
|
655
|
+
/** Optional initial response stub headers/status to seed effective cookie reads */
|
|
656
|
+
initialResponse?: Response;
|
|
249
657
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
250
658
|
cacheStore?: SegmentCacheStore;
|
|
659
|
+
/**
|
|
660
|
+
* Handler-owned registry of explicit per-scope stores for cross-store tag
|
|
661
|
+
* invalidation. Created once per handler, reused across requests.
|
|
662
|
+
*/
|
|
663
|
+
explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
664
|
+
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
665
|
+
cacheProfiles?: Record<
|
|
666
|
+
string,
|
|
667
|
+
import("../cache/profile-registry.js").CacheProfile
|
|
668
|
+
>;
|
|
251
669
|
/** Optional Cloudflare execution context for waitUntil support */
|
|
252
670
|
executionContext?: ExecutionContext;
|
|
253
671
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
254
672
|
themeConfig?: ResolvedThemeConfig | null;
|
|
673
|
+
/** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
|
|
674
|
+
stateCookieName?: string;
|
|
675
|
+
/** Build version, used as the prefix of a server-rotated rango state value. */
|
|
676
|
+
version?: string;
|
|
255
677
|
}
|
|
256
678
|
|
|
257
679
|
/**
|
|
@@ -263,20 +685,37 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
263
685
|
* - Passed to handlers as ctx
|
|
264
686
|
*/
|
|
265
687
|
export function createRequestContext<TEnv>(
|
|
266
|
-
options: CreateRequestContextOptions<TEnv
|
|
688
|
+
options: CreateRequestContextOptions<TEnv>,
|
|
267
689
|
): RequestContext<TEnv> {
|
|
268
|
-
const {
|
|
690
|
+
const {
|
|
691
|
+
env,
|
|
692
|
+
request,
|
|
693
|
+
url,
|
|
694
|
+
variables,
|
|
695
|
+
initialResponse,
|
|
696
|
+
cacheStore,
|
|
697
|
+
explicitTaggedStores,
|
|
698
|
+
cacheProfiles,
|
|
699
|
+
executionContext,
|
|
700
|
+
themeConfig,
|
|
701
|
+
stateCookieName,
|
|
702
|
+
version: stateVersion,
|
|
703
|
+
} = options;
|
|
269
704
|
const cookieHeader = request.headers.get("Cookie");
|
|
705
|
+
let rangoStateRotated = false;
|
|
270
706
|
let parsedCookies: Record<string, string> | null = null;
|
|
271
707
|
|
|
272
|
-
|
|
273
|
-
|
|
708
|
+
let stubResponse = initialResponse
|
|
709
|
+
? new Response(null, {
|
|
710
|
+
status: initialResponse.status,
|
|
711
|
+
statusText: initialResponse.statusText,
|
|
712
|
+
headers: new Headers(initialResponse.headers),
|
|
713
|
+
})
|
|
714
|
+
: new Response(null, { status: 200 });
|
|
274
715
|
|
|
275
|
-
// Create handle store and loader memoization for this request
|
|
276
716
|
const handleStore = createHandleStore();
|
|
277
717
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
278
718
|
|
|
279
|
-
// Lazy parse cookies
|
|
280
719
|
const getParsedCookies = (): Record<string, string> => {
|
|
281
720
|
if (!parsedCookies) {
|
|
282
721
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -284,13 +723,42 @@ export function createRequestContext<TEnv>(
|
|
|
284
723
|
return parsedCookies;
|
|
285
724
|
};
|
|
286
725
|
|
|
287
|
-
|
|
726
|
+
let responseCookieCache: Map<string, string | null> | null = null;
|
|
727
|
+
const getResponseCookies = (): Map<string, string | null> => {
|
|
728
|
+
if (!responseCookieCache) {
|
|
729
|
+
responseCookieCache = parseResponseCookies(stubResponse);
|
|
730
|
+
}
|
|
731
|
+
return responseCookieCache;
|
|
732
|
+
};
|
|
733
|
+
const invalidateResponseCookieCache = () => {
|
|
734
|
+
responseCookieCache = null;
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
738
|
+
if (isInsideCacheScope()) {
|
|
739
|
+
throw new Error(
|
|
740
|
+
`ctx.${methodName}() cannot be called inside a cache() boundary. ` +
|
|
741
|
+
`On cache hit the handler is skipped, so this side effect would be lost. ` +
|
|
742
|
+
`Move ctx.${methodName}() to a middleware or layout outside the cache() scope.`,
|
|
743
|
+
);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Response stub Set-Cookie wins, then original header (source of truth for mutations).
|
|
748
|
+
const effectiveCookie = (name: string): string | undefined => {
|
|
749
|
+
const mutations = getResponseCookies();
|
|
750
|
+
if (mutations.has(name)) {
|
|
751
|
+
const v = mutations.get(name);
|
|
752
|
+
return v === null ? undefined : v;
|
|
753
|
+
}
|
|
754
|
+
return getParsedCookies()[name];
|
|
755
|
+
};
|
|
756
|
+
|
|
288
757
|
const getTheme = (): Theme | undefined => {
|
|
289
758
|
if (!themeConfig) return undefined;
|
|
290
759
|
|
|
291
|
-
const stored =
|
|
760
|
+
const stored = effectiveCookie(themeConfig.storageKey);
|
|
292
761
|
if (stored) {
|
|
293
|
-
// Validate stored value
|
|
294
762
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
295
763
|
return "system";
|
|
296
764
|
}
|
|
@@ -304,135 +772,336 @@ export function createRequestContext<TEnv>(
|
|
|
304
772
|
const setTheme = (theme: Theme): void => {
|
|
305
773
|
if (!themeConfig) return;
|
|
306
774
|
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
775
|
+
// Shared guard (isValidTheme): reject any value not in the configured theme
|
|
776
|
+
// set, AND reject "system" when system detection is off — a cookie of
|
|
777
|
+
// theme=system with enableSystem:false would re-apply a bogus class="system"
|
|
778
|
+
// on the next SSR.
|
|
779
|
+
if (!isValidTheme(theme, themeConfig)) {
|
|
780
|
+
warnInvalidTheme(theme, themeConfig);
|
|
310
781
|
return;
|
|
311
782
|
}
|
|
312
783
|
|
|
313
|
-
// Set cookie
|
|
314
784
|
stubResponse.headers.append(
|
|
315
785
|
"Set-Cookie",
|
|
316
786
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
317
787
|
path: THEME_COOKIE.path,
|
|
318
788
|
maxAge: THEME_COOKIE.maxAge,
|
|
319
789
|
sameSite: THEME_COOKIE.sameSite,
|
|
320
|
-
})
|
|
790
|
+
}),
|
|
321
791
|
);
|
|
792
|
+
invalidateResponseCookieCache();
|
|
322
793
|
};
|
|
323
794
|
|
|
324
|
-
|
|
795
|
+
const cleanUrl = stripInternalParams(url);
|
|
796
|
+
|
|
325
797
|
const ctx: RequestContext<TEnv> = {
|
|
326
798
|
env,
|
|
327
799
|
request,
|
|
328
|
-
url,
|
|
800
|
+
url: cleanUrl,
|
|
801
|
+
originalUrl: new URL(request.url),
|
|
329
802
|
pathname: url.pathname,
|
|
330
|
-
searchParams:
|
|
331
|
-
|
|
332
|
-
get:
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
803
|
+
searchParams: cleanUrl.searchParams,
|
|
804
|
+
_variables: variables,
|
|
805
|
+
get: ((keyOrVar: any) => {
|
|
806
|
+
if (isNonCacheable(variables, keyOrVar) && isInsideCacheScope()) {
|
|
807
|
+
throw new Error(
|
|
808
|
+
`ctx.get() for a non-cacheable variable cannot be called inside a cache() boundary. ` +
|
|
809
|
+
`The variable was created with { cache: false } or set with { cache: false }, ` +
|
|
810
|
+
`and its value would be stale on cache hit. Move the read outside the cached scope.`,
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
return contextGet(variables, keyOrVar);
|
|
814
|
+
}) as RequestContext<TEnv>["get"],
|
|
815
|
+
set: ((keyOrVar: any, value: any, options?: any) => {
|
|
816
|
+
assertNotInsideCacheExec(ctx, "set");
|
|
817
|
+
contextSet(variables, keyOrVar, value, options);
|
|
818
|
+
}) as RequestContext<TEnv>["set"],
|
|
336
819
|
params: {} as Record<string, string>,
|
|
337
|
-
|
|
820
|
+
|
|
821
|
+
get res(): Response {
|
|
822
|
+
return stubResponse;
|
|
823
|
+
},
|
|
824
|
+
set res(_: Response) {
|
|
825
|
+
throw new Error(
|
|
826
|
+
"ctx.res is read-only. Use ctx.header() to set response headers, or cookies() for cookie mutations.",
|
|
827
|
+
);
|
|
828
|
+
},
|
|
338
829
|
|
|
339
830
|
cookie(name: string): string | undefined {
|
|
340
|
-
return
|
|
831
|
+
return effectiveCookie(name);
|
|
341
832
|
},
|
|
342
833
|
|
|
343
834
|
cookies(): Record<string, string> {
|
|
344
|
-
|
|
835
|
+
const parsed = getParsedCookies();
|
|
836
|
+
const mutations = getResponseCookies();
|
|
837
|
+
if (mutations.size === 0) return { ...parsed };
|
|
838
|
+
// Build result without delete (avoids V8 dictionary-mode de-opt)
|
|
839
|
+
const deleted = new Set<string>();
|
|
840
|
+
for (const [k, v] of mutations) {
|
|
841
|
+
if (v === null) deleted.add(k);
|
|
842
|
+
}
|
|
843
|
+
const result: Record<string, string> = {};
|
|
844
|
+
for (const key of Object.keys(parsed)) {
|
|
845
|
+
if (!deleted.has(key)) result[key] = parsed[key];
|
|
846
|
+
}
|
|
847
|
+
for (const [k, v] of mutations) {
|
|
848
|
+
if (v !== null) result[k] = v;
|
|
849
|
+
}
|
|
850
|
+
return result;
|
|
345
851
|
},
|
|
346
852
|
|
|
347
853
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
854
|
+
assertNotInsideCacheExec(ctx, "setCookie");
|
|
855
|
+
assertNotInsideCacheScopeALS("setCookie");
|
|
348
856
|
stubResponse.headers.append(
|
|
349
857
|
"Set-Cookie",
|
|
350
|
-
serializeCookieValue(name, value, options)
|
|
858
|
+
serializeCookieValue(name, value, options),
|
|
351
859
|
);
|
|
860
|
+
invalidateResponseCookieCache();
|
|
352
861
|
},
|
|
353
862
|
|
|
354
863
|
deleteCookie(
|
|
355
864
|
name: string,
|
|
356
|
-
options?: Pick<CookieOptions, "domain" | "path"
|
|
865
|
+
options?: Pick<CookieOptions, "domain" | "path">,
|
|
357
866
|
): void {
|
|
867
|
+
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
868
|
+
assertNotInsideCacheScopeALS("deleteCookie");
|
|
358
869
|
stubResponse.headers.append(
|
|
359
870
|
"Set-Cookie",
|
|
360
|
-
serializeCookieValue(name, "", { ...options, maxAge: 0 })
|
|
871
|
+
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
361
872
|
);
|
|
873
|
+
invalidateResponseCookieCache();
|
|
362
874
|
},
|
|
363
875
|
|
|
364
876
|
header(name: string, value: string): void {
|
|
877
|
+
assertNotInsideCacheExec(ctx, "header");
|
|
878
|
+
assertNotInsideCacheScopeALS("header");
|
|
365
879
|
stubResponse.headers.set(name, value);
|
|
366
880
|
},
|
|
367
881
|
|
|
368
|
-
//
|
|
882
|
+
// Rotate the rango state cookie for the responding client (the server seat
|
|
883
|
+
// of invalidateClientCache). Writes ONE Set-Cookie per request with the
|
|
884
|
+
// value {version}:{timestamp}; the `:` stays raw (the cookie-name.ts
|
|
885
|
+
// serializer), not the URL-encoded form serializeCookieValue would produce.
|
|
886
|
+
// The timestamp is strictly greater than the client's current one (inbound
|
|
887
|
+
// X-Rango-State), so a same-millisecond server rotation still differs from
|
|
888
|
+
// the client value and the divergence observer fires.
|
|
889
|
+
_rotateStateCookie(): void {
|
|
890
|
+
if (rangoStateRotated) return;
|
|
891
|
+
rangoStateRotated = true;
|
|
892
|
+
if (!stateCookieName) return;
|
|
893
|
+
// The client's current value, for the monotonic guard: prefer the
|
|
894
|
+
// X-Rango-State header (router navigation/prefetch fetches send it), but
|
|
895
|
+
// fall back to the request's rango state cookie — action POSTs / plain
|
|
896
|
+
// app fetch()s carry no router header yet DO send the cookie. Without the
|
|
897
|
+
// fallback, prevTs stays 0 and a same-ms mint can equal the client value,
|
|
898
|
+
// leaving the divergence observer silent. `|| null` so an empty header
|
|
899
|
+
// ('' from proxy normalization) falls through instead of short-circuiting.
|
|
900
|
+
// getRawCookieValue reads the cookie undecoded (the wire value
|
|
901
|
+
// decodeStateValue decodes exactly once) AND is the same parser the client
|
|
902
|
+
// mirror uses, so both seats read the same jar entry.
|
|
903
|
+
const prevRaw =
|
|
904
|
+
(request.headers.get("x-rango-state") || null) ??
|
|
905
|
+
getRawCookieValue(cookieHeader, stateCookieName);
|
|
906
|
+
const value = mintStateValue(stateVersion ?? "0", prevRaw);
|
|
907
|
+
stubResponse.headers.append(
|
|
908
|
+
"Set-Cookie",
|
|
909
|
+
serializeStateCookie(stateCookieName, value, url.protocol === "https:"),
|
|
910
|
+
);
|
|
911
|
+
invalidateResponseCookieCache();
|
|
912
|
+
},
|
|
913
|
+
|
|
914
|
+
// Set the keepClientCache() directive header. The action bridge reads it on
|
|
915
|
+
// the response and suppresses its automatic invalidation. `.set` makes this
|
|
916
|
+
// idempotent (one header regardless of call count).
|
|
917
|
+
_setKeepCacheDirective(): void {
|
|
918
|
+
stubResponse.headers.set(KEEP_CACHE_HEADER, "1");
|
|
919
|
+
},
|
|
920
|
+
|
|
921
|
+
setStatus(status: number): void {
|
|
922
|
+
assertNotInsideCacheExec(ctx, "setStatus");
|
|
923
|
+
assertNotInsideCacheScopeALS("setStatus");
|
|
924
|
+
stubResponse = new Response(null, {
|
|
925
|
+
status,
|
|
926
|
+
headers: stubResponse.headers,
|
|
927
|
+
});
|
|
928
|
+
},
|
|
929
|
+
|
|
930
|
+
_setStatus(status: number): void {
|
|
931
|
+
stubResponse = new Response(null, {
|
|
932
|
+
status,
|
|
933
|
+
headers: stubResponse.headers,
|
|
934
|
+
});
|
|
935
|
+
},
|
|
936
|
+
|
|
369
937
|
use: null as any,
|
|
370
938
|
|
|
371
939
|
method: request.method,
|
|
372
940
|
|
|
373
941
|
_handleStore: handleStore,
|
|
942
|
+
_transitionWhen: [],
|
|
374
943
|
_cacheStore: cacheStore,
|
|
944
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
945
|
+
_requestTags: new Set<string>(),
|
|
946
|
+
_cacheProfiles: cacheProfiles,
|
|
375
947
|
|
|
376
948
|
waitUntil(fn: () => Promise<void>): void {
|
|
377
949
|
if (executionContext?.waitUntil) {
|
|
378
|
-
//
|
|
379
|
-
|
|
950
|
+
// Wrap in Promise.resolve().then(fn) so a SYNCHRONOUS throw in a
|
|
951
|
+
// non-async callback becomes a rejected promise handed to the host's
|
|
952
|
+
// waitUntil (logged as a background failure), instead of escaping into
|
|
953
|
+
// the request flow. Mirrors fireAndForgetWaitUntil's deferral.
|
|
954
|
+
executionContext.waitUntil(Promise.resolve().then(fn));
|
|
380
955
|
} else {
|
|
381
|
-
|
|
382
|
-
fn().catch((err) => console.error("[waitUntil] Background task failed:", err));
|
|
956
|
+
fireAndForgetWaitUntil(fn);
|
|
383
957
|
}
|
|
384
958
|
},
|
|
385
959
|
|
|
960
|
+
executionContext,
|
|
961
|
+
|
|
386
962
|
_onResponseCallbacks: [],
|
|
387
963
|
|
|
388
964
|
onResponse(callback: (response: Response) => Response): void {
|
|
965
|
+
assertNotInsideCacheExec(ctx, "onResponse");
|
|
966
|
+
assertNotInsideCacheScopeALS("onResponse");
|
|
389
967
|
this._onResponseCallbacks.push(callback);
|
|
390
968
|
},
|
|
391
969
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
970
|
+
get theme() {
|
|
971
|
+
return themeConfig ? getTheme() : undefined;
|
|
972
|
+
},
|
|
973
|
+
setTheme: themeConfig
|
|
974
|
+
? (theme: Theme) => {
|
|
975
|
+
assertNotInsideCacheExec(ctx, "setTheme");
|
|
976
|
+
setTheme(theme);
|
|
977
|
+
}
|
|
978
|
+
: undefined,
|
|
395
979
|
_themeConfig: themeConfig,
|
|
980
|
+
|
|
981
|
+
setLocationState(entries: LocationStateEntry | LocationStateEntry[]): void {
|
|
982
|
+
assertNotInsideCacheExec(ctx, "setLocationState");
|
|
983
|
+
const arr = Array.isArray(entries) ? entries : [entries];
|
|
984
|
+
this._locationState = this._locationState
|
|
985
|
+
? [...this._locationState, ...arr]
|
|
986
|
+
: arr;
|
|
987
|
+
},
|
|
988
|
+
_locationState: undefined,
|
|
989
|
+
|
|
990
|
+
_reportedErrors: new WeakSet<object>(),
|
|
991
|
+
_metricsStore: undefined,
|
|
992
|
+
|
|
993
|
+
_renderBarrier: null as any,
|
|
994
|
+
_resolveRenderBarrier: null as any,
|
|
995
|
+
_renderBarrierSegmentOrder: undefined,
|
|
996
|
+
|
|
997
|
+
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
998
|
+
};
|
|
999
|
+
|
|
1000
|
+
// Lazy allocation: only create Promise when a loader calls rendered().
|
|
1001
|
+
let barrierResolved = false;
|
|
1002
|
+
let resolveBarrier: (() => void) | undefined;
|
|
1003
|
+
ctx._renderBarrier = null as any;
|
|
1004
|
+
ctx._resolveRenderBarrier = (
|
|
1005
|
+
segments: Array<{ type: string; id: string }>,
|
|
1006
|
+
) => {
|
|
1007
|
+
if (barrierResolved) return;
|
|
1008
|
+
barrierResolved = true;
|
|
1009
|
+
const segOrder = segments
|
|
1010
|
+
.filter((s) => s.type !== "loader")
|
|
1011
|
+
.map((s) => s.id);
|
|
1012
|
+
ctx._renderBarrierSegmentOrder = segOrder;
|
|
1013
|
+
|
|
1014
|
+
const closeGuard = () => {
|
|
1015
|
+
ctx._renderBarrierWaiters = undefined;
|
|
1016
|
+
ctx._handlerLoaderDeps = undefined;
|
|
1017
|
+
ctx._renderBarrierGuardClosed = true;
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
if (ctx._treeHasStreaming) {
|
|
1021
|
+
handleStore.settled.then(closeGuard);
|
|
1022
|
+
} else {
|
|
1023
|
+
ctx._renderBarrierHandleSnapshot = buildHandleSnapshot(
|
|
1024
|
+
handleStore,
|
|
1025
|
+
segOrder,
|
|
1026
|
+
);
|
|
1027
|
+
closeGuard();
|
|
1028
|
+
}
|
|
1029
|
+
if (resolveBarrier) resolveBarrier();
|
|
396
1030
|
};
|
|
1031
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
1032
|
+
get() {
|
|
1033
|
+
const p = barrierResolved
|
|
1034
|
+
? Promise.resolve()
|
|
1035
|
+
: new Promise<void>((resolve) => {
|
|
1036
|
+
resolveBarrier = resolve;
|
|
1037
|
+
});
|
|
1038
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
1039
|
+
value: p,
|
|
1040
|
+
writable: false,
|
|
1041
|
+
configurable: false,
|
|
1042
|
+
});
|
|
1043
|
+
return p;
|
|
1044
|
+
},
|
|
1045
|
+
configurable: true,
|
|
1046
|
+
});
|
|
397
1047
|
|
|
398
|
-
// Now create use() with access to ctx
|
|
399
1048
|
ctx.use = createUseFunction({
|
|
400
1049
|
handleStore,
|
|
401
1050
|
loaderPromises,
|
|
402
1051
|
getContext: () => ctx,
|
|
403
1052
|
});
|
|
404
1053
|
|
|
1054
|
+
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
405
1055
|
return ctx;
|
|
406
1056
|
}
|
|
407
1057
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
1058
|
+
// Capture the Max-Age value so it can be parsed numerically. A leading zero
|
|
1059
|
+
// (Max-Age=05) is a non-zero lifetime, not a deletion; only a value that parses
|
|
1060
|
+
// to <= 0 marks a cookie for deletion. Pattern-matching a leading "0" misread
|
|
1061
|
+
// zero-prefixed values like 05 / 010 as deletions.
|
|
1062
|
+
const MAX_AGE_RE = /;\s*Max-Age\s*=\s*(-?\d+)/i;
|
|
1063
|
+
|
|
1064
|
+
function isCookieDeletion(header: string): boolean {
|
|
1065
|
+
const m = MAX_AGE_RE.exec(header);
|
|
1066
|
+
if (!m) return false;
|
|
1067
|
+
return Number(m[1]) <= 0;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
1071
|
+
const result = new Map<string, string | null>();
|
|
1072
|
+
const setCookies = response.headers.getSetCookie();
|
|
1073
|
+
|
|
1074
|
+
for (const header of setCookies) {
|
|
1075
|
+
const semiIdx = header.indexOf(";");
|
|
1076
|
+
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
1077
|
+
const eqIdx = pair.indexOf("=");
|
|
1078
|
+
if (eqIdx === -1) continue;
|
|
1079
|
+
|
|
1080
|
+
let name: string;
|
|
1081
|
+
let value: string;
|
|
1082
|
+
try {
|
|
1083
|
+
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
1084
|
+
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
1085
|
+
} catch {
|
|
1086
|
+
continue;
|
|
423
1087
|
}
|
|
1088
|
+
|
|
1089
|
+
const isDeleted = isCookieDeletion(header);
|
|
1090
|
+
result.set(name, isDeleted ? null : value);
|
|
424
1091
|
}
|
|
425
1092
|
|
|
426
|
-
return
|
|
1093
|
+
return result;
|
|
427
1094
|
}
|
|
428
1095
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
1096
|
+
// Re-exported for unit tests and the existing import path. The implementation
|
|
1097
|
+
// lives in the dependency-free ./cookie-parse leaf so consumers (e.g. the host
|
|
1098
|
+
// dispatcher) can share it without pulling this module's request-context graph.
|
|
1099
|
+
export { parseCookiesFromHeader };
|
|
1100
|
+
|
|
1101
|
+
export function serializeCookieValue(
|
|
433
1102
|
name: string,
|
|
434
1103
|
value: string,
|
|
435
|
-
options: CookieOptions = {}
|
|
1104
|
+
options: CookieOptions = {},
|
|
436
1105
|
): string {
|
|
437
1106
|
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
438
1107
|
|
|
@@ -456,20 +1125,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
456
1125
|
getContext: () => RequestContext<TEnv>;
|
|
457
1126
|
}
|
|
458
1127
|
|
|
459
|
-
/**
|
|
460
|
-
* Create the use() function for loader and handle composition.
|
|
461
|
-
*
|
|
462
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
463
|
-
* - For loaders: executes and memoizes loader functions
|
|
464
|
-
* - For handles: returns a push function to add handle data
|
|
465
|
-
*/
|
|
466
1128
|
export function createUseFunction<TEnv>(
|
|
467
|
-
options: CreateUseFunctionOptions<TEnv
|
|
1129
|
+
options: CreateUseFunctionOptions<TEnv>,
|
|
468
1130
|
): RequestContext["use"] {
|
|
469
1131
|
const { handleStore, loaderPromises, getContext } = options;
|
|
470
1132
|
|
|
471
1133
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
472
|
-
// Handle case: return a push function
|
|
473
1134
|
if (isHandle(item)) {
|
|
474
1135
|
const handle = item;
|
|
475
1136
|
const ctx = getContext();
|
|
@@ -478,31 +1139,28 @@ export function createUseFunction<TEnv>(
|
|
|
478
1139
|
if (!segmentId) {
|
|
479
1140
|
throw new Error(
|
|
480
1141
|
`Handle "${handle.$$id}" used outside of handler context. ` +
|
|
481
|
-
`Handles must be used within route/layout handlers
|
|
1142
|
+
`Handles must be used within route/layout handlers.`,
|
|
482
1143
|
);
|
|
483
1144
|
}
|
|
484
1145
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
1146
|
+
return withDefer(
|
|
1147
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
1148
|
+
const valueOrPromise =
|
|
1149
|
+
typeof dataOrFn === "function"
|
|
1150
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
1151
|
+
: dataOrFn;
|
|
491
1152
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
1153
|
+
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
1154
|
+
},
|
|
1155
|
+
);
|
|
495
1156
|
}
|
|
496
1157
|
|
|
497
|
-
// Loader case
|
|
498
1158
|
const loader = item as LoaderDefinition<any, any>;
|
|
499
1159
|
|
|
500
|
-
// Return cached promise if already started
|
|
501
1160
|
if (loaderPromises.has(loader.$$id)) {
|
|
502
1161
|
return loaderPromises.get(loader.$$id);
|
|
503
1162
|
}
|
|
504
1163
|
|
|
505
|
-
// Get loader function - either from loader object or fetchable registry
|
|
506
1164
|
let loaderFn = loader.fn;
|
|
507
1165
|
if (!loaderFn) {
|
|
508
1166
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -513,39 +1171,66 @@ export function createUseFunction<TEnv>(
|
|
|
513
1171
|
|
|
514
1172
|
if (!loaderFn) {
|
|
515
1173
|
throw new Error(
|
|
516
|
-
`Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build
|
|
1174
|
+
`Loader "${loader.$$id}" has no function. This usually means the loader was defined without "use server" and the function was not included in the build.`,
|
|
517
1175
|
);
|
|
518
1176
|
}
|
|
519
1177
|
|
|
520
1178
|
const ctx = getContext();
|
|
521
1179
|
|
|
522
|
-
//
|
|
1180
|
+
// Build the typed ctx.search the same way the render path
|
|
1181
|
+
// (createHandlerContext) and the fetchable-loader path (loader-fetch.ts) do:
|
|
1182
|
+
// parse the route's search schema over the cleaned searchParams. The base
|
|
1183
|
+
// RequestContext carries no `search` field, so reading `(ctx as any).search`
|
|
1184
|
+
// here always yielded {} — dropping typed search for action/dispatch loaders.
|
|
1185
|
+
const searchSchema = ctx._routeName
|
|
1186
|
+
? getSearchSchema(ctx._routeName)
|
|
1187
|
+
: undefined;
|
|
1188
|
+
const loaderSearch = searchSchema
|
|
1189
|
+
? parseSearchParams(ctx.searchParams, searchSchema)
|
|
1190
|
+
: {};
|
|
1191
|
+
|
|
523
1192
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
524
1193
|
params: ctx.params,
|
|
1194
|
+
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
525
1195
|
request: ctx.request,
|
|
526
1196
|
searchParams: ctx.searchParams,
|
|
1197
|
+
search: loaderSearch,
|
|
527
1198
|
pathname: ctx.pathname,
|
|
528
1199
|
url: ctx.url,
|
|
1200
|
+
originalUrl: ctx.originalUrl,
|
|
529
1201
|
env: ctx.env as any,
|
|
530
|
-
|
|
1202
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1203
|
+
executionContext: ctx.executionContext,
|
|
531
1204
|
get: ctx.get as any,
|
|
532
|
-
use: <TDep, TDepParams = any>(
|
|
533
|
-
dep: LoaderDefinition<TDep, TDepParams
|
|
1205
|
+
use: (<TDep, TDepParams = any>(
|
|
1206
|
+
dep: LoaderDefinition<TDep, TDepParams>,
|
|
534
1207
|
): Promise<TDep> => {
|
|
535
|
-
// Recursive call - will start dep loader if not already started
|
|
536
1208
|
return ctx.use(dep);
|
|
537
|
-
},
|
|
1209
|
+
}) as LoaderContext["use"],
|
|
538
1210
|
method: "GET",
|
|
539
1211
|
body: undefined,
|
|
1212
|
+
reverse: createReverseFunction(
|
|
1213
|
+
getGlobalRouteMap(),
|
|
1214
|
+
ctx._routeName,
|
|
1215
|
+
ctx.params as Record<string, string>,
|
|
1216
|
+
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
1217
|
+
),
|
|
1218
|
+
rendered: () => {
|
|
1219
|
+
throw new Error(
|
|
1220
|
+
`ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
|
|
1221
|
+
`It cannot be used from request-context loaders or server actions.`,
|
|
1222
|
+
);
|
|
1223
|
+
},
|
|
540
1224
|
};
|
|
541
1225
|
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
1226
|
+
// Meter through the same unified phase API as the loader-resolution funnel
|
|
1227
|
+
// (observePhase), so a loader resolved via this base request-context ctx.use
|
|
1228
|
+
// co-emits the "loader:<id>" perf metric AND the "rango.loader" span — no
|
|
1229
|
+
// drift between the two ctx.use implementations.
|
|
1230
|
+
const promise = observePhase(PHASES.loader(loader.$$id), () =>
|
|
1231
|
+
Promise.resolve(loaderFn(loaderCtx)),
|
|
1232
|
+
);
|
|
547
1233
|
|
|
548
|
-
// Memoize for subsequent calls
|
|
549
1234
|
loaderPromises.set(loader.$$id, promise);
|
|
550
1235
|
|
|
551
1236
|
return promise;
|