@rangojs/router 0.0.0-experimental.79 → 0.0.0-experimental.7c7e4327
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -4
- package/README.md +301 -797
- package/dist/bin/rango.js +603 -145
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3750 -1160
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +96 -24
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +85 -6
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +228 -33
- package/skills/caching/SKILL.md +336 -19
- package/skills/catalog.json +271 -0
- 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 +110 -4
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +87 -56
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +12 -10
- package/skills/hooks/SKILL.md +73 -691
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +129 -27
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +75 -19
- package/skills/layout/SKILL.md +40 -19
- package/skills/links/SKILL.md +247 -17
- package/skills/loader/SKILL.md +248 -10
- package/skills/middleware/SKILL.md +25 -13
- package/skills/migrate-nextjs/SKILL.md +205 -20
- package/skills/migrate-react-router/SKILL.md +59 -670
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +29 -2
- package/skills/observability/SKILL.md +202 -0
- package/skills/parallel/SKILL.md +40 -10
- package/skills/ppr/SKILL.md +616 -0
- package/skills/prerender/SKILL.md +72 -60
- package/skills/rango/SKILL.md +318 -26
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +138 -49
- package/skills/route/SKILL.md +117 -9
- package/skills/router-setup/SKILL.md +44 -9
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +776 -0
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +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 +1 -1
- package/skills/typesafety/SKILL.md +45 -626
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +74 -15
- 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 +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +252 -158
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +94 -25
- package/src/browser/navigation-client.ts +144 -79
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +161 -73
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +183 -144
- package/src/browser/prefetch/cache.ts +242 -77
- package/src/browser/prefetch/fetch.ts +325 -69
- package/src/browser/prefetch/queue.ts +61 -12
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +58 -20
- package/src/browser/react/NavigationProvider.tsx +202 -120
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +32 -7
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +145 -28
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +236 -65
- package/src/browser/types.ts +102 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +203 -154
- package/src/build/generate-route-types.ts +3 -1
- package/src/build/index.ts +11 -3
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -21
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +16 -5
- package/src/build/route-types/include-resolution.ts +456 -62
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +22 -6
- package/src/build/route-types/router-processing.ts +128 -51
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +13 -21
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +421 -58
- package/src/cache/cache-scope.ts +187 -96
- package/src/cache/cache-tag.ts +149 -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 +2202 -372
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +180 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1127 -0
- package/src/client.rsc.tsx +41 -21
- package/src/client.tsx +33 -61
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +67 -37
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +9 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +97 -12
- package/src/index.ts +98 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +78 -10
- package/src/redirect-origin.ts +114 -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 +65 -39
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +87 -59
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +27 -42
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +82 -59
- package/src/router/loader-resolution.ts +167 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +80 -55
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +211 -165
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +69 -79
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +298 -172
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +181 -150
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +112 -67
- package/src/router/preview-match.ts +6 -2
- package/src/router/request-classification.ts +50 -69
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +115 -36
- package/src/router/router-options.ts +166 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +131 -86
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +139 -39
- package/src/router/segment-resolution/loader-mask.ts +67 -0
- package/src/router/segment-resolution/loader-snapshot.ts +251 -0
- package/src/router/segment-resolution/revalidation.ts +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +75 -0
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +105 -20
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +57 -0
- package/src/router.ts +157 -71
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +291 -217
- package/src/rsc/helpers.ts +168 -46
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +124 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +421 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +282 -116
- package/src/rsc/shell-capture.ts +1158 -0
- package/src/rsc/shell-serve.ts +150 -0
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +53 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +350 -149
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +208 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +152 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +395 -176
- package/src/ssr/index.tsx +458 -178
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +46 -0
- package/src/testing/dispatch.ts +813 -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 +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +19 -64
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -11
- package/src/urls/path-helper-types.ts +63 -17
- package/src/urls/path-helper.ts +22 -106
- package/src/urls/pattern-types.ts +72 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +98 -154
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- 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 +8 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +127 -86
- 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 +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +45 -1
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +4 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +210 -10
- package/src/vite/plugins/cjs-to-esm.ts +16 -19
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-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/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +138 -27
- package/src/vite/rango.ts +236 -138
- package/src/vite/router-discovery.ts +927 -136
- package/src/vite/utils/ast-handler-extract.ts +26 -35
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +71 -43
- package/src/vite/utils/shared-utils.ts +142 -43
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -11,7 +11,15 @@
|
|
|
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";
|
|
16
24
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
17
25
|
import type {
|
|
@@ -33,11 +41,20 @@ import {
|
|
|
33
41
|
type HandleData,
|
|
34
42
|
} from "./handle-store.js";
|
|
35
43
|
import { isHandle } from "../handle.js";
|
|
36
|
-
import {
|
|
44
|
+
import { withDefer } from "../defer.js";
|
|
45
|
+
import { type MetricsStore } from "./context.js";
|
|
46
|
+
import { observePhase, PHASES } from "../router/instrument.js";
|
|
37
47
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
38
48
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
39
49
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
40
|
-
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 {
|
|
54
|
+
THEME_COOKIE,
|
|
55
|
+
isValidTheme,
|
|
56
|
+
warnInvalidTheme,
|
|
57
|
+
} from "../theme/constants.js";
|
|
41
58
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
42
59
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
43
60
|
import { isInsideCacheScope } from "./context.js";
|
|
@@ -45,7 +62,12 @@ import {
|
|
|
45
62
|
createReverseFunction,
|
|
46
63
|
stripInternalParams,
|
|
47
64
|
} from "../router/handler-context.js";
|
|
48
|
-
import {
|
|
65
|
+
import {
|
|
66
|
+
getGlobalRouteMap,
|
|
67
|
+
isRouteRootScoped,
|
|
68
|
+
getSearchSchema,
|
|
69
|
+
} from "../route-map-builder.js";
|
|
70
|
+
import { parseSearchParams } from "../search-params.js";
|
|
49
71
|
import { invariant } from "../errors.js";
|
|
50
72
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
51
73
|
|
|
@@ -58,22 +80,7 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
58
80
|
export interface RequestContext<
|
|
59
81
|
TEnv = DefaultEnv,
|
|
60
82
|
TParams = Record<string, string>,
|
|
61
|
-
> {
|
|
62
|
-
/** Platform bindings (Cloudflare env, etc.) */
|
|
63
|
-
env: TEnv;
|
|
64
|
-
/** Original HTTP request */
|
|
65
|
-
request: Request;
|
|
66
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
67
|
-
url: URL;
|
|
68
|
-
/**
|
|
69
|
-
* The original request URL with all parameters intact, including
|
|
70
|
-
* internal `_rsc*` transport params.
|
|
71
|
-
*/
|
|
72
|
-
originalUrl: URL;
|
|
73
|
-
/** URL pathname */
|
|
74
|
-
pathname: string;
|
|
75
|
-
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
76
|
-
searchParams: URLSearchParams;
|
|
83
|
+
> extends RequestScope<TEnv> {
|
|
77
84
|
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
78
85
|
_variables: Record<string, any>;
|
|
79
86
|
/** Get a variable set by middleware */
|
|
@@ -115,6 +122,10 @@ export interface RequestContext<
|
|
|
115
122
|
setStatus(status: number): void;
|
|
116
123
|
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
117
124
|
_setStatus(status: number): void;
|
|
125
|
+
/** @internal Rotate the rango state cookie (server seat of invalidateClientCache). */
|
|
126
|
+
_rotateStateCookie(): void;
|
|
127
|
+
/** @internal Set the keepClientCache() directive header on the response. */
|
|
128
|
+
_setKeepCacheDirective(): void;
|
|
118
129
|
|
|
119
130
|
/**
|
|
120
131
|
* Access loader data or push handle data.
|
|
@@ -150,29 +161,100 @@ export interface RequestContext<
|
|
|
150
161
|
/** @internal Handle store for tracking handle data across segments */
|
|
151
162
|
_handleStore: HandleStore;
|
|
152
163
|
|
|
164
|
+
/**
|
|
165
|
+
* @internal transition({ when }) predicates for segments matched this request,
|
|
166
|
+
* keyed by segment id. Collected during resolution (the function is stripped
|
|
167
|
+
* from the serialized segment config), then evaluated post-handler in
|
|
168
|
+
* rsc-rendering — outside any cache scope — to drop the transition of any
|
|
169
|
+
* segment whose predicate returns false.
|
|
170
|
+
*/
|
|
171
|
+
_transitionWhen?: Array<{ id: string; when: TransitionWhenFn }>;
|
|
172
|
+
|
|
153
173
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
154
174
|
_cacheStore?: SegmentCacheStore;
|
|
155
175
|
|
|
176
|
+
/**
|
|
177
|
+
* @internal PPR shell-capture ACTIVE marker. True ONLY inside the background
|
|
178
|
+
* capture task's derived request context (built by shell-capture.ts). This is
|
|
179
|
+
* the switch every capture-specific behavior reads: loader masking
|
|
180
|
+
* (loader-mask.ts isShellCaptureActive / fresh.ts emitStreaming) and the
|
|
181
|
+
* cookies()/headers() capture guard (cookie-store.ts
|
|
182
|
+
* assertNotInsideShellCapture). The foreground render never sets it, so the
|
|
183
|
+
* served response is byte-identical to axis 1. The capture descriptor itself
|
|
184
|
+
* (key/ttl/swr/tags/store) is NOT threaded through the request context — the
|
|
185
|
+
* integrated PPR serve path (rsc/shell-serve.ts + rsc-rendering.ts) builds it
|
|
186
|
+
* locally and passes it to scheduleShellCapture directly.
|
|
187
|
+
*/
|
|
188
|
+
_shellCaptureRun?: boolean;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @internal Bake-lane loader containers collected DURING a shell capture:
|
|
192
|
+
* segment-key -> the loader's (pre-wrap) result promise. Populated by
|
|
193
|
+
* resolveLoaderData for loaders on entries with no renderable loading() (the
|
|
194
|
+
* bake lane — they execute at capture instead of being masked; see
|
|
195
|
+
* docs/design/loader-container-bake.md). Drained by captureAndStoreShell
|
|
196
|
+
* after the shell quiesces: settled containers are promise-elided,
|
|
197
|
+
* Flight-serialized, and pinned into the snapshot's loader family; a
|
|
198
|
+
* REJECTED container refuses the capture (error UI must never bake into the
|
|
199
|
+
* shared shell). Own property of the capture's derived context only.
|
|
200
|
+
*/
|
|
201
|
+
_shellCaptureLoaderRecords?: Map<string, Promise<unknown>>;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @internal Loader-family snapshot seed for a shell HIT's tail render:
|
|
205
|
+
* segment-key -> the capture's elided container (already Flight-deserialized
|
|
206
|
+
* by serveShellHit). resolveLoaderData overlays it onto the fresh run's
|
|
207
|
+
* container (recorded paths pinned, hole-marker paths keep the fresh nested
|
|
208
|
+
* promises) so the payload's baked bytes match the frozen prelude. Own
|
|
209
|
+
* property of the HIT tail's derived context only.
|
|
210
|
+
*/
|
|
211
|
+
_shellLoaderSeed?: Map<string, unknown>;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @internal Set (to the offending fn name) by the cookies()/headers()
|
|
215
|
+
* capture guard when it throws DURING a capture render. Load-bearing for the
|
|
216
|
+
* bake lane: a guard throw inside an executing loader is swallowed by
|
|
217
|
+
* wrapLoaderPromise into per-loader error UI, which would otherwise bake
|
|
218
|
+
* silently into the shared shell — the capture checks this flag after the
|
|
219
|
+
* render and refuses instead. Deterministic, so the capture does not retry.
|
|
220
|
+
*/
|
|
221
|
+
_shellCaptureGuardTripped?: string;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @internal The loader $$id whose BODY was executing when the capture guard
|
|
225
|
+
* tripped (read off the loader-body ALS scope at trip time), or undefined
|
|
226
|
+
* when the read came from handler/render code. Only used to make the
|
|
227
|
+
* once-per-key refusal warning name the real source — the old text
|
|
228
|
+
* hardcoded "a bake-lane loader", which misattributed handler-land reads
|
|
229
|
+
* and sent users debugging the wrong lane (issue #672, secondary).
|
|
230
|
+
*/
|
|
231
|
+
_shellCaptureGuardTrippedLoaderId?: string;
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @internal Handler-owned registry of explicit per-scope stores from
|
|
235
|
+
* cache({ store }). Created once per createRSCHandler() and threaded into
|
|
236
|
+
* every request context, so it accumulates every explicit store the handler
|
|
237
|
+
* resolves. updateTag()/revalidateTag() iterate this set plus _cacheStore to
|
|
238
|
+
* reach every store that may hold tagged entries. The app-level store is not
|
|
239
|
+
* added here (it is always reachable via _cacheStore).
|
|
240
|
+
*/
|
|
241
|
+
_explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @internal Union of every cache tag resolved while producing this request's
|
|
245
|
+
* response (from cache({ tags }), runtime cacheTag(), and loader cache tags).
|
|
246
|
+
* Populated at the tag-resolution sites via recordRequestTags(). Read by the
|
|
247
|
+
* document cache middleware so a full-page entry is tagged with everything its
|
|
248
|
+
* content used and can therefore be invalidated by updateTag()/revalidateTag().
|
|
249
|
+
*/
|
|
250
|
+
_requestTags: Set<string>;
|
|
251
|
+
|
|
156
252
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
157
253
|
_cacheProfiles?: Record<
|
|
158
254
|
string,
|
|
159
255
|
import("../cache/profile-registry.js").CacheProfile
|
|
160
256
|
>;
|
|
161
257
|
|
|
162
|
-
/**
|
|
163
|
-
* Schedule work to run after the response is sent.
|
|
164
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
165
|
-
* On Node.js, runs as fire-and-forget.
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```typescript
|
|
169
|
-
* ctx.waitUntil(async () => {
|
|
170
|
-
* await cacheStore.set(key, data, ttl);
|
|
171
|
-
* });
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
175
|
-
|
|
176
258
|
/**
|
|
177
259
|
* Register a callback to run when the response is created.
|
|
178
260
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -195,6 +277,19 @@ export interface RequestContext<
|
|
|
195
277
|
/** @internal Registered onResponse callbacks */
|
|
196
278
|
_onResponseCallbacks: Array<(response: Response) => Response>;
|
|
197
279
|
|
|
280
|
+
/**
|
|
281
|
+
* @internal Promises of the background tasks scheduled via this context's
|
|
282
|
+
* waitUntil (deferred cache writes, revalidations, consumer tasks). The PPR
|
|
283
|
+
* shell capture drains this list BEFORE its match/render as an ORDERING EDGE:
|
|
284
|
+
* every foreground deferred cache write is scheduled here before the capture
|
|
285
|
+
* task is, so settling the list first guarantees the capture's cache reads
|
|
286
|
+
* observe the foreground's generation instead of racing it (see
|
|
287
|
+
* shell-capture.ts). Tasks whose scheduling fn carries
|
|
288
|
+
* UNTRACKED_BACKGROUND_TASK are not tracked (the capture task itself —
|
|
289
|
+
* tracking it would make that drain await its own promise).
|
|
290
|
+
*/
|
|
291
|
+
_pendingBackgroundTasks?: Array<Promise<unknown>>;
|
|
292
|
+
|
|
198
293
|
/**
|
|
199
294
|
* Current theme setting (only available when theme is enabled in router config)
|
|
200
295
|
*
|
|
@@ -276,6 +371,30 @@ export interface RequestContext<
|
|
|
276
371
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
277
372
|
_prevRouteKey?: string;
|
|
278
373
|
|
|
374
|
+
/**
|
|
375
|
+
* @internal Navigation/action source data the transition({ when }) gate reads
|
|
376
|
+
* to build its ShouldRevalidateFn-shaped predicate context. currentUrl/Params
|
|
377
|
+
* come from the navigation snapshot (set at match time); action* are stashed
|
|
378
|
+
* at the action-bearing gate call sites. All undefined when there is no source
|
|
379
|
+
* (initial full load) or no action (plain navigation).
|
|
380
|
+
*/
|
|
381
|
+
_gateCurrentUrl?: URL;
|
|
382
|
+
_gateCurrentParams?: Record<string, string>;
|
|
383
|
+
_gateActionId?: string;
|
|
384
|
+
_gateActionUrl?: URL;
|
|
385
|
+
_gateActionResult?: unknown;
|
|
386
|
+
_gateFormData?: FormData;
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* @internal True while the post-action revalidation render is running (set by
|
|
390
|
+
* revalidateAfterAction). The "use cache" runtime reads this to prefer
|
|
391
|
+
* freshness over a fast stale response during an action: a stale entry
|
|
392
|
+
* re-executes in the foreground (so the action response reflects the refreshed
|
|
393
|
+
* value) with only the store write deferred, instead of serving stale and
|
|
394
|
+
* revalidating in the background. A plain navigation (flag unset) keeps SWR.
|
|
395
|
+
*/
|
|
396
|
+
_inActionRevalidation?: boolean;
|
|
397
|
+
|
|
279
398
|
/**
|
|
280
399
|
* @internal Render barrier for experimental `rendered()` API.
|
|
281
400
|
* Resolves when all non-loader segments have settled and handle data
|
|
@@ -300,7 +419,9 @@ export interface RequestContext<
|
|
|
300
419
|
|
|
301
420
|
/**
|
|
302
421
|
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
303
|
-
* entries (streaming).
|
|
422
|
+
* entries (streaming). On a streaming tree rendered() waits for the streaming
|
|
423
|
+
* handlers to settle (via handleStore.settled) before resolving, and the
|
|
424
|
+
* deadlock guard state is kept live until that wait completes.
|
|
304
425
|
*/
|
|
305
426
|
_treeHasStreaming?: boolean;
|
|
306
427
|
|
|
@@ -324,6 +445,18 @@ export interface RequestContext<
|
|
|
324
445
|
*/
|
|
325
446
|
_renderBarrierHandleSnapshot?: HandleData;
|
|
326
447
|
|
|
448
|
+
/**
|
|
449
|
+
* @internal The deadlock guard window is closed (no further handler-awaits-
|
|
450
|
+
* loader cycle is possible). For non-streaming trees this is set when the
|
|
451
|
+
* barrier resolves. For streaming trees the window stays open until
|
|
452
|
+
* handleStore.settled — rendered() keeps waiting past the barrier and a
|
|
453
|
+
* loading() handler can still resume and await a still-waiting loader — so it
|
|
454
|
+
* is set only after settled. The guard (loader-resolution `setupLoaderAccess`)
|
|
455
|
+
* reads this instead of `_renderBarrierSegmentOrder` so it does not go blind
|
|
456
|
+
* during the streaming settle wait.
|
|
457
|
+
*/
|
|
458
|
+
_renderBarrierGuardClosed?: boolean;
|
|
459
|
+
|
|
327
460
|
/** @internal Per-request error dedup set for onError reporting */
|
|
328
461
|
_reportedErrors: WeakSet<object>;
|
|
329
462
|
|
|
@@ -331,9 +464,13 @@ export interface RequestContext<
|
|
|
331
464
|
* @internal Report a non-fatal background error through the router's
|
|
332
465
|
* onError callback. Wired by the RSC handler / router during request
|
|
333
466
|
* creation. Cache-runtime and other subsystems call this to surface
|
|
334
|
-
* errors without failing the response.
|
|
467
|
+
* errors without failing the response. `category` is surfaced to consumers as
|
|
468
|
+
* `metadata.category` on the onError context (phase `cache`).
|
|
335
469
|
*/
|
|
336
|
-
_reportBackgroundError?: (
|
|
470
|
+
_reportBackgroundError?: (
|
|
471
|
+
error: unknown,
|
|
472
|
+
category: CacheErrorCategory,
|
|
473
|
+
) => void;
|
|
337
474
|
|
|
338
475
|
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
339
476
|
_debugPerformance?: boolean;
|
|
@@ -341,6 +478,18 @@ export interface RequestContext<
|
|
|
341
478
|
/** @internal Request-scoped performance metrics store */
|
|
342
479
|
_metricsStore?: MetricsStore;
|
|
343
480
|
|
|
481
|
+
/**
|
|
482
|
+
* @internal True request entry timestamp (performance.now() at handler entry).
|
|
483
|
+
* Set once at request-context creation (rsc/handler.ts) so a metrics store
|
|
484
|
+
* created MID-request — ctx.debugPerformance() or the getMetricsStore wrapper —
|
|
485
|
+
* anchors its timeline to the real request start instead of the opt-in moment,
|
|
486
|
+
* keeping phases that began before the opt-in at their true (non-negative) offset.
|
|
487
|
+
*/
|
|
488
|
+
_handlerStart?: number;
|
|
489
|
+
|
|
490
|
+
/** @internal Resolved platform phase-span tracing for this request (Cloudflare or OTel) */
|
|
491
|
+
_tracing?: ResolvedTracing;
|
|
492
|
+
|
|
344
493
|
/** @internal Router basename for this request (used by redirect()) */
|
|
345
494
|
_basename?: string;
|
|
346
495
|
|
|
@@ -349,6 +498,15 @@ export interface RequestContext<
|
|
|
349
498
|
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
350
499
|
*/
|
|
351
500
|
_classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @internal Coarse route-level cache signal for the X-Rango-Cache debug
|
|
504
|
+
* header. Populated by match/matchPartial only when the debug cache signal
|
|
505
|
+
* gate is enabled (debugCacheSignal option or RANGO_TEST_SIGNALS=1). Read by
|
|
506
|
+
* the response-finalization path (createResponseWithMergedHeaders). Undefined
|
|
507
|
+
* when the gate is off, so no header is emitted.
|
|
508
|
+
*/
|
|
509
|
+
_cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
|
|
352
510
|
}
|
|
353
511
|
|
|
354
512
|
/**
|
|
@@ -367,13 +525,25 @@ export type PublicRequestContext<
|
|
|
367
525
|
| "setCookie"
|
|
368
526
|
| "deleteCookie"
|
|
369
527
|
| "_handleStore"
|
|
528
|
+
| "_transitionWhen"
|
|
370
529
|
| "_cacheStore"
|
|
530
|
+
| "_shellCaptureRun"
|
|
531
|
+
| "_shellCaptureGuardTrippedLoaderId"
|
|
532
|
+
| "_explicitTaggedStores"
|
|
533
|
+
| "_requestTags"
|
|
371
534
|
| "_cacheProfiles"
|
|
372
535
|
| "_onResponseCallbacks"
|
|
373
536
|
| "_themeConfig"
|
|
374
537
|
| "_locationState"
|
|
375
538
|
| "_routeName"
|
|
376
539
|
| "_prevRouteKey"
|
|
540
|
+
| "_gateCurrentUrl"
|
|
541
|
+
| "_gateCurrentParams"
|
|
542
|
+
| "_gateActionId"
|
|
543
|
+
| "_gateActionUrl"
|
|
544
|
+
| "_gateActionResult"
|
|
545
|
+
| "_gateFormData"
|
|
546
|
+
| "_inActionRevalidation"
|
|
377
547
|
| "_reportedErrors"
|
|
378
548
|
| "_renderBarrier"
|
|
379
549
|
| "_resolveRenderBarrier"
|
|
@@ -382,16 +552,32 @@ export type PublicRequestContext<
|
|
|
382
552
|
| "_renderBarrierWaiters"
|
|
383
553
|
| "_handlerLoaderDeps"
|
|
384
554
|
| "_renderBarrierHandleSnapshot"
|
|
555
|
+
| "_renderBarrierGuardClosed"
|
|
385
556
|
| "_reportBackgroundError"
|
|
386
557
|
| "_debugPerformance"
|
|
387
558
|
| "_metricsStore"
|
|
559
|
+
| "_handlerStart"
|
|
388
560
|
| "_basename"
|
|
389
561
|
| "_setStatus"
|
|
562
|
+
| "_rotateStateCookie"
|
|
563
|
+
| "_setKeepCacheDirective"
|
|
390
564
|
| "_variables"
|
|
391
565
|
| "_classifiedRoute"
|
|
566
|
+
| "_cacheSignal"
|
|
392
567
|
| "res"
|
|
393
568
|
>;
|
|
394
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Marker for a waitUntil-scheduled fn whose task promise must NOT enter
|
|
572
|
+
* _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
|
|
573
|
+
* the capture's pre-render write barrier settles that list, so tracking the
|
|
574
|
+
* capture itself would make the drain wait on its own (still-running) promise.
|
|
575
|
+
* @internal
|
|
576
|
+
*/
|
|
577
|
+
export const UNTRACKED_BACKGROUND_TASK: unique symbol = Symbol.for(
|
|
578
|
+
"rango.untrackedBackgroundTask",
|
|
579
|
+
);
|
|
580
|
+
|
|
395
581
|
// AsyncLocalStorage instance for request context
|
|
396
582
|
const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
397
583
|
|
|
@@ -440,6 +626,7 @@ export function _getRequestContext<TEnv = DefaultEnv>():
|
|
|
440
626
|
export function setRequestContextParams(
|
|
441
627
|
params: Record<string, string>,
|
|
442
628
|
routeName?: string,
|
|
629
|
+
routeMap?: Record<string, string>,
|
|
443
630
|
): void {
|
|
444
631
|
const ctx = requestContextStorage.getStore();
|
|
445
632
|
if (ctx) {
|
|
@@ -452,9 +639,13 @@ export function setRequestContextParams(
|
|
|
452
639
|
: undefined
|
|
453
640
|
) as DefaultRouteName | undefined;
|
|
454
641
|
}
|
|
455
|
-
// Update reverse with scoped resolution now that route is known
|
|
642
|
+
// Update reverse with scoped resolution now that route is known. Production
|
|
643
|
+
// omits routeMap and uses the global map (routes are registered globally);
|
|
644
|
+
// the testing primitives (renderToFlightString/renderServerTree) pass a
|
|
645
|
+
// scoped routeMap so `ctx.reverse` is not order-dependent on whatever router
|
|
646
|
+
// registered last.
|
|
456
647
|
ctx.reverse = createReverseFunction(
|
|
457
|
-
getGlobalRouteMap(),
|
|
648
|
+
routeMap ?? getGlobalRouteMap(),
|
|
458
649
|
routeName,
|
|
459
650
|
params,
|
|
460
651
|
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
@@ -470,11 +661,17 @@ export function setRequestContextParams(
|
|
|
470
661
|
*/
|
|
471
662
|
export function setRequestContextPrevRouteKey(
|
|
472
663
|
prevRouteKey: string | undefined,
|
|
664
|
+
currentUrl?: URL,
|
|
665
|
+
currentParams?: Record<string, string>,
|
|
473
666
|
): void {
|
|
474
667
|
const ctx = requestContextStorage.getStore();
|
|
475
|
-
if (ctx
|
|
476
|
-
|
|
477
|
-
}
|
|
668
|
+
if (!ctx) return;
|
|
669
|
+
if (prevRouteKey !== undefined) ctx._prevRouteKey = prevRouteKey;
|
|
670
|
+
// Source URL/params for the transition({ when }) gate (effectiveFromUrl /
|
|
671
|
+
// effectiveFromMatch.params from the navigation snapshot). Same write point as
|
|
672
|
+
// _prevRouteKey, which doubles as fromRouteName.
|
|
673
|
+
if (currentUrl !== undefined) ctx._gateCurrentUrl = currentUrl;
|
|
674
|
+
if (currentParams !== undefined) ctx._gateCurrentParams = currentParams;
|
|
478
675
|
}
|
|
479
676
|
|
|
480
677
|
/**
|
|
@@ -488,23 +685,7 @@ export function getLocationState(): LocationStateEntry[] | undefined {
|
|
|
488
685
|
return ctx?._locationState;
|
|
489
686
|
}
|
|
490
687
|
|
|
491
|
-
|
|
492
|
-
* Get the current request context, throwing if not available
|
|
493
|
-
* @deprecated Use getRequestContext() directly — it now throws if outside context
|
|
494
|
-
*/
|
|
495
|
-
export function requireRequestContext<
|
|
496
|
-
TEnv = DefaultEnv,
|
|
497
|
-
>(): RequestContext<TEnv> {
|
|
498
|
-
return getRequestContext<TEnv>();
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
503
|
-
*/
|
|
504
|
-
export interface ExecutionContext {
|
|
505
|
-
waitUntil(promise: Promise<any>): void;
|
|
506
|
-
passThroughOnException(): void;
|
|
507
|
-
}
|
|
688
|
+
export type { ExecutionContext };
|
|
508
689
|
|
|
509
690
|
/**
|
|
510
691
|
* Options for creating a request context
|
|
@@ -518,6 +699,11 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
518
699
|
initialResponse?: Response;
|
|
519
700
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
520
701
|
cacheStore?: SegmentCacheStore;
|
|
702
|
+
/**
|
|
703
|
+
* Handler-owned registry of explicit per-scope stores for cross-store tag
|
|
704
|
+
* invalidation. Created once per handler, reused across requests.
|
|
705
|
+
*/
|
|
706
|
+
explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
521
707
|
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
522
708
|
cacheProfiles?: Record<
|
|
523
709
|
string,
|
|
@@ -527,6 +713,10 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
527
713
|
executionContext?: ExecutionContext;
|
|
528
714
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
529
715
|
themeConfig?: ResolvedThemeConfig | null;
|
|
716
|
+
/** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
|
|
717
|
+
stateCookieName?: string;
|
|
718
|
+
/** Build version, used as the prefix of a server-rotated rango state value. */
|
|
719
|
+
version?: string;
|
|
530
720
|
}
|
|
531
721
|
|
|
532
722
|
/**
|
|
@@ -547,15 +737,17 @@ export function createRequestContext<TEnv>(
|
|
|
547
737
|
variables,
|
|
548
738
|
initialResponse,
|
|
549
739
|
cacheStore,
|
|
740
|
+
explicitTaggedStores,
|
|
550
741
|
cacheProfiles,
|
|
551
742
|
executionContext,
|
|
552
743
|
themeConfig,
|
|
744
|
+
stateCookieName,
|
|
745
|
+
version: stateVersion,
|
|
553
746
|
} = options;
|
|
554
747
|
const cookieHeader = request.headers.get("Cookie");
|
|
748
|
+
let rangoStateRotated = false;
|
|
555
749
|
let parsedCookies: Record<string, string> | null = null;
|
|
556
750
|
|
|
557
|
-
// Create stub response for collecting headers/cookies.
|
|
558
|
-
// All cookie/header mutations go here; cookie reads derive from it.
|
|
559
751
|
let stubResponse = initialResponse
|
|
560
752
|
? new Response(null, {
|
|
561
753
|
status: initialResponse.status,
|
|
@@ -564,11 +756,9 @@ export function createRequestContext<TEnv>(
|
|
|
564
756
|
})
|
|
565
757
|
: new Response(null, { status: 200 });
|
|
566
758
|
|
|
567
|
-
// Create handle store and loader memoization for this request
|
|
568
759
|
const handleStore = createHandleStore();
|
|
569
760
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
570
761
|
|
|
571
|
-
// Lazy parse cookies from the original Cookie header
|
|
572
762
|
const getParsedCookies = (): Record<string, string> => {
|
|
573
763
|
if (!parsedCookies) {
|
|
574
764
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -576,7 +766,6 @@ export function createRequestContext<TEnv>(
|
|
|
576
766
|
return parsedCookies;
|
|
577
767
|
};
|
|
578
768
|
|
|
579
|
-
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
580
769
|
let responseCookieCache: Map<string, string | null> | null = null;
|
|
581
770
|
const getResponseCookies = (): Map<string, string | null> => {
|
|
582
771
|
if (!responseCookieCache) {
|
|
@@ -588,8 +777,6 @@ export function createRequestContext<TEnv>(
|
|
|
588
777
|
responseCookieCache = null;
|
|
589
778
|
};
|
|
590
779
|
|
|
591
|
-
// Guard: throw if a response-level side effect is called inside a cache() scope.
|
|
592
|
-
// Uses ALS to detect the scope (set during segment resolution).
|
|
593
780
|
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
594
781
|
if (isInsideCacheScope()) {
|
|
595
782
|
throw new Error(
|
|
@@ -600,8 +787,7 @@ export function createRequestContext<TEnv>(
|
|
|
600
787
|
}
|
|
601
788
|
}
|
|
602
789
|
|
|
603
|
-
//
|
|
604
|
-
// The stub IS the source of truth for same-request mutations.
|
|
790
|
+
// Response stub Set-Cookie wins, then original header (source of truth for mutations).
|
|
605
791
|
const effectiveCookie = (name: string): string | undefined => {
|
|
606
792
|
const mutations = getResponseCookies();
|
|
607
793
|
if (mutations.has(name)) {
|
|
@@ -611,14 +797,11 @@ export function createRequestContext<TEnv>(
|
|
|
611
797
|
return getParsedCookies()[name];
|
|
612
798
|
};
|
|
613
799
|
|
|
614
|
-
// Theme helpers (only used when themeConfig is provided)
|
|
615
800
|
const getTheme = (): Theme | undefined => {
|
|
616
801
|
if (!themeConfig) return undefined;
|
|
617
802
|
|
|
618
|
-
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
619
803
|
const stored = effectiveCookie(themeConfig.storageKey);
|
|
620
804
|
if (stored) {
|
|
621
|
-
// Validate stored value
|
|
622
805
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
623
806
|
return "system";
|
|
624
807
|
}
|
|
@@ -632,15 +815,15 @@ export function createRequestContext<TEnv>(
|
|
|
632
815
|
const setTheme = (theme: Theme): void => {
|
|
633
816
|
if (!themeConfig) return;
|
|
634
817
|
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
818
|
+
// Shared guard (isValidTheme): reject any value not in the configured theme
|
|
819
|
+
// set, AND reject "system" when system detection is off — a cookie of
|
|
820
|
+
// theme=system with enableSystem:false would re-apply a bogus class="system"
|
|
821
|
+
// on the next SSR.
|
|
822
|
+
if (!isValidTheme(theme, themeConfig)) {
|
|
823
|
+
warnInvalidTheme(theme, themeConfig);
|
|
640
824
|
return;
|
|
641
825
|
}
|
|
642
826
|
|
|
643
|
-
// Write to stub — effectiveCookie() will pick it up on next read
|
|
644
827
|
stubResponse.headers.append(
|
|
645
828
|
"Set-Cookie",
|
|
646
829
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
@@ -652,10 +835,8 @@ export function createRequestContext<TEnv>(
|
|
|
652
835
|
invalidateResponseCookieCache();
|
|
653
836
|
};
|
|
654
837
|
|
|
655
|
-
// Strip internal _rsc* params so userland sees a clean URL.
|
|
656
838
|
const cleanUrl = stripInternalParams(url);
|
|
657
839
|
|
|
658
|
-
// Build the context object first (without use), then add use
|
|
659
840
|
const ctx: RequestContext<TEnv> = {
|
|
660
841
|
env,
|
|
661
842
|
request,
|
|
@@ -741,6 +922,45 @@ export function createRequestContext<TEnv>(
|
|
|
741
922
|
stubResponse.headers.set(name, value);
|
|
742
923
|
},
|
|
743
924
|
|
|
925
|
+
// Rotate the rango state cookie for the responding client (the server seat
|
|
926
|
+
// of invalidateClientCache). Writes ONE Set-Cookie per request with the
|
|
927
|
+
// value {version}:{timestamp}; the `:` stays raw (the cookie-name.ts
|
|
928
|
+
// serializer), not the URL-encoded form serializeCookieValue would produce.
|
|
929
|
+
// The timestamp is strictly greater than the client's current one (inbound
|
|
930
|
+
// X-Rango-State), so a same-millisecond server rotation still differs from
|
|
931
|
+
// the client value and the divergence observer fires.
|
|
932
|
+
_rotateStateCookie(): void {
|
|
933
|
+
if (rangoStateRotated) return;
|
|
934
|
+
rangoStateRotated = true;
|
|
935
|
+
if (!stateCookieName) return;
|
|
936
|
+
// The client's current value, for the monotonic guard: prefer the
|
|
937
|
+
// X-Rango-State header (router navigation/prefetch fetches send it), but
|
|
938
|
+
// fall back to the request's rango state cookie — action POSTs / plain
|
|
939
|
+
// app fetch()s carry no router header yet DO send the cookie. Without the
|
|
940
|
+
// fallback, prevTs stays 0 and a same-ms mint can equal the client value,
|
|
941
|
+
// leaving the divergence observer silent. `|| null` so an empty header
|
|
942
|
+
// ('' from proxy normalization) falls through instead of short-circuiting.
|
|
943
|
+
// getRawCookieValue reads the cookie undecoded (the wire value
|
|
944
|
+
// decodeStateValue decodes exactly once) AND is the same parser the client
|
|
945
|
+
// mirror uses, so both seats read the same jar entry.
|
|
946
|
+
const prevRaw =
|
|
947
|
+
(request.headers.get("x-rango-state") || null) ??
|
|
948
|
+
getRawCookieValue(cookieHeader, stateCookieName);
|
|
949
|
+
const value = mintStateValue(stateVersion ?? "0", prevRaw);
|
|
950
|
+
stubResponse.headers.append(
|
|
951
|
+
"Set-Cookie",
|
|
952
|
+
serializeStateCookie(stateCookieName, value, url.protocol === "https:"),
|
|
953
|
+
);
|
|
954
|
+
invalidateResponseCookieCache();
|
|
955
|
+
},
|
|
956
|
+
|
|
957
|
+
// Set the keepClientCache() directive header. The action bridge reads it on
|
|
958
|
+
// the response and suppresses its automatic invalidation. `.set` makes this
|
|
959
|
+
// idempotent (one header regardless of call count).
|
|
960
|
+
_setKeepCacheDirective(): void {
|
|
961
|
+
stubResponse.headers.set(KEEP_CACHE_HEADER, "1");
|
|
962
|
+
},
|
|
963
|
+
|
|
744
964
|
setStatus(status: number): void {
|
|
745
965
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
746
966
|
assertNotInsideCacheScopeALS("setStatus");
|
|
@@ -757,28 +977,49 @@ export function createRequestContext<TEnv>(
|
|
|
757
977
|
});
|
|
758
978
|
},
|
|
759
979
|
|
|
760
|
-
// Placeholder - will be replaced below
|
|
761
980
|
use: null as any,
|
|
762
981
|
|
|
763
982
|
method: request.method,
|
|
764
983
|
|
|
765
984
|
_handleStore: handleStore,
|
|
985
|
+
_transitionWhen: [],
|
|
766
986
|
_cacheStore: cacheStore,
|
|
987
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
988
|
+
_requestTags: new Set<string>(),
|
|
767
989
|
_cacheProfiles: cacheProfiles,
|
|
768
990
|
|
|
769
991
|
waitUntil(fn: () => Promise<void>): void {
|
|
992
|
+
// Wrap in Promise.resolve().then(fn) so a SYNCHRONOUS throw in a
|
|
993
|
+
// non-async callback becomes a rejected promise handed to the host's
|
|
994
|
+
// waitUntil (logged as a background failure), instead of escaping into
|
|
995
|
+
// the request flow. Mirrors fireAndForgetWaitUntil's deferral.
|
|
996
|
+
const task = Promise.resolve().then(fn);
|
|
997
|
+
// Track the task promise so the PPR shell capture can settle the
|
|
998
|
+
// foreground's deferred cache writes before its own match/render (the
|
|
999
|
+
// ordering edge; see _pendingBackgroundTasks). The capture task itself
|
|
1000
|
+
// opts out via the marker — the drain must never await its own promise.
|
|
1001
|
+
if (
|
|
1002
|
+
!(fn as { [UNTRACKED_BACKGROUND_TASK]?: boolean })[
|
|
1003
|
+
UNTRACKED_BACKGROUND_TASK
|
|
1004
|
+
]
|
|
1005
|
+
) {
|
|
1006
|
+
ctx._pendingBackgroundTasks?.push(task);
|
|
1007
|
+
}
|
|
770
1008
|
if (executionContext?.waitUntil) {
|
|
771
|
-
|
|
772
|
-
executionContext.waitUntil(fn());
|
|
1009
|
+
executionContext.waitUntil(task);
|
|
773
1010
|
} else {
|
|
774
|
-
// Node
|
|
775
|
-
|
|
1011
|
+
// Node/dev fallback: fire-and-forget with error logging (the same
|
|
1012
|
+
// policy fireAndForgetWaitUntil applies).
|
|
1013
|
+
task.catch((err) =>
|
|
776
1014
|
console.error("[waitUntil] Background task failed:", err),
|
|
777
1015
|
);
|
|
778
1016
|
}
|
|
779
1017
|
},
|
|
780
1018
|
|
|
1019
|
+
executionContext,
|
|
1020
|
+
|
|
781
1021
|
_onResponseCallbacks: [],
|
|
1022
|
+
_pendingBackgroundTasks: [],
|
|
782
1023
|
|
|
783
1024
|
onResponse(callback: (response: Response) => Response): void {
|
|
784
1025
|
assertNotInsideCacheExec(ctx, "onResponse");
|
|
@@ -786,7 +1027,6 @@ export function createRequestContext<TEnv>(
|
|
|
786
1027
|
this._onResponseCallbacks.push(callback);
|
|
787
1028
|
},
|
|
788
1029
|
|
|
789
|
-
// Theme properties (only set when themeConfig is provided)
|
|
790
1030
|
get theme() {
|
|
791
1031
|
return themeConfig ? getTheme() : undefined;
|
|
792
1032
|
},
|
|
@@ -809,20 +1049,19 @@ export function createRequestContext<TEnv>(
|
|
|
809
1049
|
|
|
810
1050
|
_reportedErrors: new WeakSet<object>(),
|
|
811
1051
|
_metricsStore: undefined,
|
|
1052
|
+
_handlerStart: undefined,
|
|
812
1053
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
_resolveRenderBarrier: null as any, // set below
|
|
1054
|
+
_renderBarrier: null as any,
|
|
1055
|
+
_resolveRenderBarrier: null as any,
|
|
816
1056
|
_renderBarrierSegmentOrder: undefined,
|
|
817
1057
|
|
|
818
1058
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
819
1059
|
};
|
|
820
1060
|
|
|
821
|
-
// Lazy
|
|
822
|
-
// calls rendered(). Requests that don't use rendered() pay zero cost.
|
|
1061
|
+
// Lazy allocation: only create Promise when a loader calls rendered().
|
|
823
1062
|
let barrierResolved = false;
|
|
824
1063
|
let resolveBarrier: (() => void) | undefined;
|
|
825
|
-
ctx._renderBarrier = null as any;
|
|
1064
|
+
ctx._renderBarrier = null as any;
|
|
826
1065
|
ctx._resolveRenderBarrier = (
|
|
827
1066
|
segments: Array<{ type: string; id: string }>,
|
|
828
1067
|
) => {
|
|
@@ -832,21 +1071,26 @@ export function createRequestContext<TEnv>(
|
|
|
832
1071
|
.filter((s) => s.type !== "loader")
|
|
833
1072
|
.map((s) => s.id);
|
|
834
1073
|
ctx._renderBarrierSegmentOrder = segOrder;
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
ctx.
|
|
1074
|
+
|
|
1075
|
+
const closeGuard = () => {
|
|
1076
|
+
ctx._renderBarrierWaiters = undefined;
|
|
1077
|
+
ctx._handlerLoaderDeps = undefined;
|
|
1078
|
+
ctx._renderBarrierGuardClosed = true;
|
|
1079
|
+
};
|
|
1080
|
+
|
|
1081
|
+
if (ctx._treeHasStreaming) {
|
|
1082
|
+
handleStore.settled.then(closeGuard);
|
|
1083
|
+
} else {
|
|
1084
|
+
ctx._renderBarrierHandleSnapshot = buildHandleSnapshot(
|
|
1085
|
+
handleStore,
|
|
1086
|
+
segOrder,
|
|
1087
|
+
);
|
|
1088
|
+
closeGuard();
|
|
1089
|
+
}
|
|
843
1090
|
if (resolveBarrier) resolveBarrier();
|
|
844
1091
|
};
|
|
845
1092
|
Object.defineProperty(ctx, "_renderBarrier", {
|
|
846
1093
|
get() {
|
|
847
|
-
// Barrier already resolved (cache/prerender hit) or first lazy access.
|
|
848
|
-
// Either way, replace the getter with a concrete value to avoid
|
|
849
|
-
// repeated Promise.resolve() allocations on subsequent reads.
|
|
850
1094
|
const p = barrierResolved
|
|
851
1095
|
? Promise.resolve()
|
|
852
1096
|
: new Promise<void>((resolve) => {
|
|
@@ -862,32 +1106,33 @@ export function createRequestContext<TEnv>(
|
|
|
862
1106
|
configurable: true,
|
|
863
1107
|
});
|
|
864
1108
|
|
|
865
|
-
// Now create use() with access to ctx
|
|
866
1109
|
ctx.use = createUseFunction({
|
|
867
1110
|
handleStore,
|
|
868
1111
|
loaderPromises,
|
|
869
1112
|
getContext: () => ctx,
|
|
870
1113
|
});
|
|
871
1114
|
|
|
872
|
-
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
873
1115
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
874
1116
|
return ctx;
|
|
875
1117
|
}
|
|
876
1118
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
*
|
|
882
|
-
|
|
883
|
-
|
|
1119
|
+
// Capture the Max-Age value so it can be parsed numerically. A leading zero
|
|
1120
|
+
// (Max-Age=05) is a non-zero lifetime, not a deletion; only a value that parses
|
|
1121
|
+
// to <= 0 marks a cookie for deletion. Pattern-matching a leading "0" misread
|
|
1122
|
+
// zero-prefixed values like 05 / 010 as deletions.
|
|
1123
|
+
const MAX_AGE_RE = /;\s*Max-Age\s*=\s*(-?\d+)/i;
|
|
1124
|
+
|
|
1125
|
+
function isCookieDeletion(header: string): boolean {
|
|
1126
|
+
const m = MAX_AGE_RE.exec(header);
|
|
1127
|
+
if (!m) return false;
|
|
1128
|
+
return Number(m[1]) <= 0;
|
|
1129
|
+
}
|
|
884
1130
|
|
|
885
1131
|
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
886
1132
|
const result = new Map<string, string | null>();
|
|
887
1133
|
const setCookies = response.headers.getSetCookie();
|
|
888
1134
|
|
|
889
1135
|
for (const header of setCookies) {
|
|
890
|
-
// First segment before ';' is the name=value pair
|
|
891
1136
|
const semiIdx = header.indexOf(";");
|
|
892
1137
|
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
893
1138
|
const eqIdx = pair.indexOf("=");
|
|
@@ -899,49 +1144,22 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
899
1144
|
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
900
1145
|
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
901
1146
|
} catch {
|
|
902
|
-
// Malformed encoding — skip this entry
|
|
903
1147
|
continue;
|
|
904
1148
|
}
|
|
905
1149
|
|
|
906
|
-
|
|
907
|
-
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
1150
|
+
const isDeleted = isCookieDeletion(header);
|
|
908
1151
|
result.set(name, isDeleted ? null : value);
|
|
909
1152
|
}
|
|
910
1153
|
|
|
911
1154
|
return result;
|
|
912
1155
|
}
|
|
913
1156
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
cookieHeader: string | null,
|
|
919
|
-
): Record<string, string> {
|
|
920
|
-
if (!cookieHeader) return {};
|
|
921
|
-
|
|
922
|
-
const cookies: Record<string, string> = {};
|
|
923
|
-
const pairs = cookieHeader.split(";");
|
|
924
|
-
|
|
925
|
-
for (const pair of pairs) {
|
|
926
|
-
const [name, ...rest] = pair.trim().split("=");
|
|
927
|
-
if (name) {
|
|
928
|
-
const raw = rest.join("=");
|
|
929
|
-
try {
|
|
930
|
-
cookies[name] = decodeURIComponent(raw);
|
|
931
|
-
} catch {
|
|
932
|
-
// Malformed percent-encoded value (e.g. %zz, %2) - fall back to raw value
|
|
933
|
-
cookies[name] = raw;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
return cookies;
|
|
939
|
-
}
|
|
1157
|
+
// Re-exported for unit tests and the existing import path. The implementation
|
|
1158
|
+
// lives in the dependency-free ./cookie-parse leaf so consumers (e.g. the host
|
|
1159
|
+
// dispatcher) can share it without pulling this module's request-context graph.
|
|
1160
|
+
export { parseCookiesFromHeader };
|
|
940
1161
|
|
|
941
|
-
|
|
942
|
-
* Serialize a cookie for Set-Cookie header
|
|
943
|
-
*/
|
|
944
|
-
function serializeCookieValue(
|
|
1162
|
+
export function serializeCookieValue(
|
|
945
1163
|
name: string,
|
|
946
1164
|
value: string,
|
|
947
1165
|
options: CookieOptions = {},
|
|
@@ -968,20 +1186,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
968
1186
|
getContext: () => RequestContext<TEnv>;
|
|
969
1187
|
}
|
|
970
1188
|
|
|
971
|
-
/**
|
|
972
|
-
* Create the use() function for loader and handle composition.
|
|
973
|
-
*
|
|
974
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
975
|
-
* - For loaders: executes and memoizes loader functions
|
|
976
|
-
* - For handles: returns a push function to add handle data
|
|
977
|
-
*/
|
|
978
1189
|
export function createUseFunction<TEnv>(
|
|
979
1190
|
options: CreateUseFunctionOptions<TEnv>,
|
|
980
1191
|
): RequestContext["use"] {
|
|
981
1192
|
const { handleStore, loaderPromises, getContext } = options;
|
|
982
1193
|
|
|
983
1194
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
984
|
-
// Handle case: return a push function
|
|
985
1195
|
if (isHandle(item)) {
|
|
986
1196
|
const handle = item;
|
|
987
1197
|
const ctx = getContext();
|
|
@@ -994,30 +1204,24 @@ export function createUseFunction<TEnv>(
|
|
|
994
1204
|
);
|
|
995
1205
|
}
|
|
996
1206
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
// Push directly - promises will be serialized by RSC and streamed
|
|
1008
|
-
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
1009
|
-
};
|
|
1207
|
+
return withDefer(
|
|
1208
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
1209
|
+
const valueOrPromise =
|
|
1210
|
+
typeof dataOrFn === "function"
|
|
1211
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
1212
|
+
: dataOrFn;
|
|
1213
|
+
|
|
1214
|
+
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
1215
|
+
},
|
|
1216
|
+
);
|
|
1010
1217
|
}
|
|
1011
1218
|
|
|
1012
|
-
// Loader case
|
|
1013
1219
|
const loader = item as LoaderDefinition<any, any>;
|
|
1014
1220
|
|
|
1015
|
-
// Return cached promise if already started
|
|
1016
1221
|
if (loaderPromises.has(loader.$$id)) {
|
|
1017
1222
|
return loaderPromises.get(loader.$$id);
|
|
1018
1223
|
}
|
|
1019
1224
|
|
|
1020
|
-
// Get loader function - either from loader object or fetchable registry
|
|
1021
1225
|
let loaderFn = loader.fn;
|
|
1022
1226
|
if (!loaderFn) {
|
|
1023
1227
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -1034,21 +1238,34 @@ export function createUseFunction<TEnv>(
|
|
|
1034
1238
|
|
|
1035
1239
|
const ctx = getContext();
|
|
1036
1240
|
|
|
1037
|
-
//
|
|
1241
|
+
// Build the typed ctx.search the same way the render path
|
|
1242
|
+
// (createHandlerContext) and the fetchable-loader path (loader-fetch.ts) do:
|
|
1243
|
+
// parse the route's search schema over the cleaned searchParams. The base
|
|
1244
|
+
// RequestContext carries no `search` field, so reading `(ctx as any).search`
|
|
1245
|
+
// here always yielded {} — dropping typed search for action/dispatch loaders.
|
|
1246
|
+
const searchSchema = ctx._routeName
|
|
1247
|
+
? getSearchSchema(ctx._routeName)
|
|
1248
|
+
: undefined;
|
|
1249
|
+
const loaderSearch = searchSchema
|
|
1250
|
+
? parseSearchParams(ctx.searchParams, searchSchema)
|
|
1251
|
+
: {};
|
|
1252
|
+
|
|
1038
1253
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
1039
1254
|
params: ctx.params,
|
|
1040
1255
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
1041
1256
|
request: ctx.request,
|
|
1042
1257
|
searchParams: ctx.searchParams,
|
|
1043
|
-
search:
|
|
1258
|
+
search: loaderSearch,
|
|
1044
1259
|
pathname: ctx.pathname,
|
|
1045
1260
|
url: ctx.url,
|
|
1261
|
+
originalUrl: ctx.originalUrl,
|
|
1046
1262
|
env: ctx.env as any,
|
|
1263
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1264
|
+
executionContext: ctx.executionContext,
|
|
1047
1265
|
get: ctx.get as any,
|
|
1048
1266
|
use: (<TDep, TDepParams = any>(
|
|
1049
1267
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
1050
1268
|
): Promise<TDep> => {
|
|
1051
|
-
// Recursive call - will start dep loader if not already started
|
|
1052
1269
|
return ctx.use(dep);
|
|
1053
1270
|
}) as LoaderContext["use"],
|
|
1054
1271
|
method: "GET",
|
|
@@ -1067,12 +1284,14 @@ export function createUseFunction<TEnv>(
|
|
|
1067
1284
|
},
|
|
1068
1285
|
};
|
|
1069
1286
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1287
|
+
// Meter through the same unified phase API as the loader-resolution funnel
|
|
1288
|
+
// (observePhase), so a loader resolved via this base request-context ctx.use
|
|
1289
|
+
// co-emits the "loader:<id>" perf metric AND the "rango.loader" span — no
|
|
1290
|
+
// drift between the two ctx.use implementations.
|
|
1291
|
+
const promise = observePhase(PHASES.loader(loader.$$id), () =>
|
|
1292
|
+
Promise.resolve(loaderFn(loaderCtx)),
|
|
1293
|
+
);
|
|
1074
1294
|
|
|
1075
|
-
// Memoize for subsequent calls
|
|
1076
1295
|
loaderPromises.set(loader.$$id, promise);
|
|
1077
1296
|
|
|
1078
1297
|
return promise;
|