@rangojs/router 0.0.0-experimental.9c9afef3 → 0.0.0-experimental.a014d2b7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -0
- package/README.md +245 -49
- package/dist/bin/rango.js +440 -133
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3373 -1176
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +68 -14
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +64 -2
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +224 -32
- package/skills/caching/SKILL.md +279 -17
- package/skills/composability/SKILL.md +27 -3
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +4 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +250 -30
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +87 -18
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +771 -0
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +77 -7
- package/skills/prerender/SKILL.md +172 -125
- package/skills/rango/SKILL.md +251 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +123 -48
- package/skills/route/SKILL.md +70 -5
- package/skills/router-setup/SKILL.md +65 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +130 -0
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/typesafety/SKILL.md +322 -29
- package/skills/use-cache/SKILL.md +57 -14
- package/skills/view-transitions/SKILL.md +337 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +1 -66
- 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/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 +192 -150
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/navigation-bridge.ts +131 -30
- package/src/browser/navigation-client.ts +186 -100
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +157 -74
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +165 -112
- package/src/browser/prefetch/cache.ts +205 -62
- package/src/browser/prefetch/fetch.ts +347 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +102 -15
- package/src/browser/react/NavigationProvider.tsx +295 -119
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/deferred-handle-resolution.ts +75 -0
- package/src/browser/react/filter-segment-order.ts +66 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +178 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- 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 +46 -11
- 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 +114 -24
- package/src/browser/scroll-restoration.ts +37 -22
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +222 -72
- package/src/browser/types.ts +102 -12
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +165 -36
- 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 +125 -24
- 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 +260 -94
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +29 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +224 -41
- package/src/cache/cache-scope.ts +188 -82
- package/src/cache/cache-tag.ts +103 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +1989 -378
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +89 -21
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +10 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +9 -17
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +37 -100
- package/src/client.rsc.tsx +44 -21
- package/src/client.tsx +119 -290
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +109 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +18 -6
- package/src/decode-loader-results.ts +52 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +30 -4
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +62 -19
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +37 -8
- package/src/handles/is-thenable.ts +19 -0
- package/src/handles/meta.ts +51 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +99 -13
- package/src/index.ts +139 -19
- 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 +37 -41
- package/src/prerender.ts +198 -82
- 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 +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +461 -304
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +143 -69
- package/src/route-definition/index.ts +4 -2
- package/src/route-definition/redirect.ts +51 -10
- package/src/route-definition/resolve-handler-use.ts +160 -0
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +37 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +164 -17
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +52 -31
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +48 -24
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +268 -56
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- package/src/router/match-api.ts +124 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +170 -276
- 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 +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +266 -169
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +132 -90
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +195 -56
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +115 -35
- package/src/router/router-options.ts +172 -15
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +162 -84
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +76 -39
- package/src/router/segment-resolution/revalidation.ts +351 -321
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +161 -199
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +206 -0
- package/src/router/trie-matching.ts +163 -59
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +157 -54
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +655 -529
- 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 +122 -31
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +131 -14
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +99 -189
- package/src/rsc/rsc-rendering.ts +109 -74
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +287 -115
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +29 -9
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +149 -0
- package/src/segment-system.tsx +236 -202
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +224 -52
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +40 -38
- package/src/server/loader-registry.ts +38 -46
- package/src/server/request-context.ts +401 -173
- package/src/ssr/index.tsx +24 -16
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +701 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +257 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +105 -0
- package/src/testing/internal/context.ts +371 -0
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +581 -0
- package/src/testing/run-loader.ts +385 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +20 -58
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +125 -71
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +136 -15
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +68 -18
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +214 -132
- 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 +57 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +6 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +178 -5
- 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 +96 -51
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +15 -64
- 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 +553 -317
- package/src/vite/plugins/performance-tracks.ts +64 -170
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/version-injector.ts +40 -29
- package/src/vite/plugins/version-plugin.ts +37 -40
- package/src/vite/plugins/virtual-entries.ts +39 -25
- package/src/vite/rango.ts +118 -114
- package/src/vite/router-discovery.ts +941 -142
- 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 +81 -34
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- 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 {
|
|
@@ -26,13 +34,28 @@ import {
|
|
|
26
34
|
contextSet,
|
|
27
35
|
isNonCacheable,
|
|
28
36
|
} from "../context-var.js";
|
|
29
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
createHandleStore,
|
|
39
|
+
buildHandleSnapshot,
|
|
40
|
+
type HandleStore,
|
|
41
|
+
type HandleData,
|
|
42
|
+
} from "./handle-store.js";
|
|
30
43
|
import { isHandle } from "../handle.js";
|
|
31
|
-
import {
|
|
44
|
+
import { withDefer } from "../defer.js";
|
|
45
|
+
import { type MetricsStore } from "./context.js";
|
|
46
|
+
import { observePhase, PHASES } from "../router/instrument.js";
|
|
32
47
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
33
48
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
34
49
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
35
|
-
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";
|
|
36
59
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
37
60
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
38
61
|
import { isInsideCacheScope } from "./context.js";
|
|
@@ -40,7 +63,12 @@ import {
|
|
|
40
63
|
createReverseFunction,
|
|
41
64
|
stripInternalParams,
|
|
42
65
|
} from "../router/handler-context.js";
|
|
43
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
getGlobalRouteMap,
|
|
68
|
+
isRouteRootScoped,
|
|
69
|
+
getSearchSchema,
|
|
70
|
+
} from "../route-map-builder.js";
|
|
71
|
+
import { parseSearchParams } from "../search-params.js";
|
|
44
72
|
import { invariant } from "../errors.js";
|
|
45
73
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
46
74
|
|
|
@@ -53,24 +81,9 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
53
81
|
export interface RequestContext<
|
|
54
82
|
TEnv = DefaultEnv,
|
|
55
83
|
TParams = Record<string, string>,
|
|
56
|
-
> {
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
/** Original HTTP request */
|
|
60
|
-
request: Request;
|
|
61
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
62
|
-
url: URL;
|
|
63
|
-
/**
|
|
64
|
-
* The original request URL with all parameters intact, including
|
|
65
|
-
* internal `_rsc*` transport params.
|
|
66
|
-
*/
|
|
67
|
-
originalUrl: URL;
|
|
68
|
-
/** URL pathname */
|
|
69
|
-
pathname: string;
|
|
70
|
-
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
71
|
-
searchParams: URLSearchParams;
|
|
72
|
-
/** Variables set by middleware (same as ctx.var) */
|
|
73
|
-
var: Record<string, any>;
|
|
84
|
+
> extends RequestScope<TEnv> {
|
|
85
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
86
|
+
_variables: Record<string, any>;
|
|
74
87
|
/** Get a variable set by middleware */
|
|
75
88
|
get: {
|
|
76
89
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -110,6 +123,10 @@ export interface RequestContext<
|
|
|
110
123
|
setStatus(status: number): void;
|
|
111
124
|
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
112
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;
|
|
113
130
|
|
|
114
131
|
/**
|
|
115
132
|
* Access loader data or push handle data.
|
|
@@ -145,29 +162,43 @@ export interface RequestContext<
|
|
|
145
162
|
/** @internal Handle store for tracking handle data across segments */
|
|
146
163
|
_handleStore: HandleStore;
|
|
147
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
|
+
|
|
148
174
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
149
175
|
_cacheStore?: SegmentCacheStore;
|
|
150
176
|
|
|
177
|
+
/**
|
|
178
|
+
* @internal Handler-owned registry of explicit per-scope stores from
|
|
179
|
+
* cache({ store }). Created once per createRSCHandler() and threaded into
|
|
180
|
+
* every request context, so it accumulates every explicit store the handler
|
|
181
|
+
* resolves. updateTag()/revalidateTag() iterate this set plus _cacheStore to
|
|
182
|
+
* reach every store that may hold tagged entries. The app-level store is not
|
|
183
|
+
* added here (it is always reachable via _cacheStore).
|
|
184
|
+
*/
|
|
185
|
+
_explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @internal Union of every cache tag resolved while producing this request's
|
|
189
|
+
* response (from cache({ tags }), runtime cacheTag(), and loader cache tags).
|
|
190
|
+
* Populated at the tag-resolution sites via recordRequestTags(). Read by the
|
|
191
|
+
* document cache middleware so a full-page entry is tagged with everything its
|
|
192
|
+
* content used and can therefore be invalidated by updateTag()/revalidateTag().
|
|
193
|
+
*/
|
|
194
|
+
_requestTags: Set<string>;
|
|
195
|
+
|
|
151
196
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
152
197
|
_cacheProfiles?: Record<
|
|
153
198
|
string,
|
|
154
199
|
import("../cache/profile-registry.js").CacheProfile
|
|
155
200
|
>;
|
|
156
201
|
|
|
157
|
-
/**
|
|
158
|
-
* Schedule work to run after the response is sent.
|
|
159
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
160
|
-
* On Node.js, runs as fire-and-forget.
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* ```typescript
|
|
164
|
-
* ctx.waitUntil(async () => {
|
|
165
|
-
* await cacheStore.set(key, data, ttl);
|
|
166
|
-
* });
|
|
167
|
-
* ```
|
|
168
|
-
*/
|
|
169
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
170
|
-
|
|
171
202
|
/**
|
|
172
203
|
* Register a callback to run when the response is created.
|
|
173
204
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -271,6 +302,92 @@ export interface RequestContext<
|
|
|
271
302
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
272
303
|
_prevRouteKey?: string;
|
|
273
304
|
|
|
305
|
+
/**
|
|
306
|
+
* @internal Navigation/action source data the transition({ when }) gate reads
|
|
307
|
+
* to build its ShouldRevalidateFn-shaped predicate context. currentUrl/Params
|
|
308
|
+
* come from the navigation snapshot (set at match time); action* are stashed
|
|
309
|
+
* at the action-bearing gate call sites. All undefined when there is no source
|
|
310
|
+
* (initial full load) or no action (plain navigation).
|
|
311
|
+
*/
|
|
312
|
+
_gateCurrentUrl?: URL;
|
|
313
|
+
_gateCurrentParams?: Record<string, string>;
|
|
314
|
+
_gateActionId?: string;
|
|
315
|
+
_gateActionUrl?: URL;
|
|
316
|
+
_gateActionResult?: unknown;
|
|
317
|
+
_gateFormData?: FormData;
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @internal True while the post-action revalidation render is running (set by
|
|
321
|
+
* revalidateAfterAction). The "use cache" runtime reads this to prefer
|
|
322
|
+
* freshness over a fast stale response during an action: a stale entry
|
|
323
|
+
* re-executes in the foreground (so the action response reflects the refreshed
|
|
324
|
+
* value) with only the store write deferred, instead of serving stale and
|
|
325
|
+
* revalidating in the background. A plain navigation (flag unset) keeps SWR.
|
|
326
|
+
*/
|
|
327
|
+
_inActionRevalidation?: boolean;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* @internal Render barrier for experimental `rendered()` API.
|
|
331
|
+
* Resolves when all non-loader segments have settled and handle data
|
|
332
|
+
* is available. Used by DSL loaders that call `ctx.rendered()`.
|
|
333
|
+
*/
|
|
334
|
+
_renderBarrier: Promise<void>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* @internal Resolve the render barrier. Accepts resolved segments, filters
|
|
338
|
+
* out loaders, and captures non-loader segment IDs as the handle ordering.
|
|
339
|
+
* Called after segment resolution (fresh) or handle replay (cache/prerender).
|
|
340
|
+
*/
|
|
341
|
+
_resolveRenderBarrier: (
|
|
342
|
+
segments: Array<{ type: string; id: string }>,
|
|
343
|
+
) => void;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* @internal Segment order at barrier resolution time, used by loader
|
|
347
|
+
* ctx.use(handle) to collect handle data in correct order.
|
|
348
|
+
*/
|
|
349
|
+
_renderBarrierSegmentOrder?: string[];
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
353
|
+
* entries (streaming). On a streaming tree rendered() waits for the streaming
|
|
354
|
+
* handlers to settle (via handleStore.settled) before resolving, and the
|
|
355
|
+
* deadlock guard state is kept live until that wait completes.
|
|
356
|
+
*/
|
|
357
|
+
_treeHasStreaming?: boolean;
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* @internal Loader IDs that have called rendered() and are waiting for the
|
|
361
|
+
* barrier. Used to detect deadlocks when a handler tries to await the same
|
|
362
|
+
* loader via ctx.use(Loader).
|
|
363
|
+
*/
|
|
364
|
+
_renderBarrierWaiters?: Set<string>;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* @internal Loader IDs that handlers have started awaiting via ctx.use().
|
|
368
|
+
* Used for bidirectional deadlock detection: if a loader later calls
|
|
369
|
+
* rendered() and a handler already awaits it, we can detect the deadlock.
|
|
370
|
+
*/
|
|
371
|
+
_handlerLoaderDeps?: Set<string>;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* @internal Cached HandleData snapshot built at barrier resolution time.
|
|
375
|
+
* Avoids rebuilding the snapshot on every loader ctx.use(handle) call.
|
|
376
|
+
*/
|
|
377
|
+
_renderBarrierHandleSnapshot?: HandleData;
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* @internal The deadlock guard window is closed (no further handler-awaits-
|
|
381
|
+
* loader cycle is possible). For non-streaming trees this is set when the
|
|
382
|
+
* barrier resolves. For streaming trees the window stays open until
|
|
383
|
+
* handleStore.settled — rendered() keeps waiting past the barrier and a
|
|
384
|
+
* loading() handler can still resume and await a still-waiting loader — so it
|
|
385
|
+
* is set only after settled. The guard (loader-resolution `setupLoaderAccess`)
|
|
386
|
+
* reads this instead of `_renderBarrierSegmentOrder` so it does not go blind
|
|
387
|
+
* during the streaming settle wait.
|
|
388
|
+
*/
|
|
389
|
+
_renderBarrierGuardClosed?: boolean;
|
|
390
|
+
|
|
274
391
|
/** @internal Per-request error dedup set for onError reporting */
|
|
275
392
|
_reportedErrors: WeakSet<object>;
|
|
276
393
|
|
|
@@ -278,9 +395,13 @@ export interface RequestContext<
|
|
|
278
395
|
* @internal Report a non-fatal background error through the router's
|
|
279
396
|
* onError callback. Wired by the RSC handler / router during request
|
|
280
397
|
* creation. Cache-runtime and other subsystems call this to surface
|
|
281
|
-
* errors without failing the response.
|
|
398
|
+
* errors without failing the response. `category` is surfaced to consumers as
|
|
399
|
+
* `metadata.category` on the onError context (phase `cache`).
|
|
282
400
|
*/
|
|
283
|
-
_reportBackgroundError?: (
|
|
401
|
+
_reportBackgroundError?: (
|
|
402
|
+
error: unknown,
|
|
403
|
+
category: CacheErrorCategory,
|
|
404
|
+
) => void;
|
|
284
405
|
|
|
285
406
|
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
286
407
|
_debugPerformance?: boolean;
|
|
@@ -288,11 +409,26 @@ export interface RequestContext<
|
|
|
288
409
|
/** @internal Request-scoped performance metrics store */
|
|
289
410
|
_metricsStore?: MetricsStore;
|
|
290
411
|
|
|
291
|
-
/** @internal
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
412
|
+
/** @internal Resolved platform phase-span tracing for this request (Cloudflare or OTel) */
|
|
413
|
+
_tracing?: ResolvedTracing;
|
|
414
|
+
|
|
415
|
+
/** @internal Router basename for this request (used by redirect()) */
|
|
416
|
+
_basename?: string;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
|
|
420
|
+
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
421
|
+
*/
|
|
422
|
+
_classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* @internal Coarse route-level cache signal for the X-Rango-Cache debug
|
|
426
|
+
* header. Populated by match/matchPartial only when the debug cache signal
|
|
427
|
+
* gate is enabled (debugCacheSignal option or RANGO_TEST_SIGNALS=1). Read by
|
|
428
|
+
* the response-finalization path (createResponseWithMergedHeaders). Undefined
|
|
429
|
+
* when the gate is off, so no header is emitted.
|
|
430
|
+
*/
|
|
431
|
+
_cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
|
|
296
432
|
}
|
|
297
433
|
|
|
298
434
|
/**
|
|
@@ -311,19 +447,42 @@ export type PublicRequestContext<
|
|
|
311
447
|
| "setCookie"
|
|
312
448
|
| "deleteCookie"
|
|
313
449
|
| "_handleStore"
|
|
450
|
+
| "_transitionWhen"
|
|
314
451
|
| "_cacheStore"
|
|
452
|
+
| "_explicitTaggedStores"
|
|
453
|
+
| "_requestTags"
|
|
315
454
|
| "_cacheProfiles"
|
|
316
455
|
| "_onResponseCallbacks"
|
|
317
456
|
| "_themeConfig"
|
|
318
457
|
| "_locationState"
|
|
319
458
|
| "_routeName"
|
|
320
459
|
| "_prevRouteKey"
|
|
460
|
+
| "_gateCurrentUrl"
|
|
461
|
+
| "_gateCurrentParams"
|
|
462
|
+
| "_gateActionId"
|
|
463
|
+
| "_gateActionUrl"
|
|
464
|
+
| "_gateActionResult"
|
|
465
|
+
| "_gateFormData"
|
|
466
|
+
| "_inActionRevalidation"
|
|
321
467
|
| "_reportedErrors"
|
|
468
|
+
| "_renderBarrier"
|
|
469
|
+
| "_resolveRenderBarrier"
|
|
470
|
+
| "_renderBarrierSegmentOrder"
|
|
471
|
+
| "_treeHasStreaming"
|
|
472
|
+
| "_renderBarrierWaiters"
|
|
473
|
+
| "_handlerLoaderDeps"
|
|
474
|
+
| "_renderBarrierHandleSnapshot"
|
|
475
|
+
| "_renderBarrierGuardClosed"
|
|
322
476
|
| "_reportBackgroundError"
|
|
323
477
|
| "_debugPerformance"
|
|
324
478
|
| "_metricsStore"
|
|
325
|
-
| "
|
|
479
|
+
| "_basename"
|
|
326
480
|
| "_setStatus"
|
|
481
|
+
| "_rotateStateCookie"
|
|
482
|
+
| "_setKeepCacheDirective"
|
|
483
|
+
| "_variables"
|
|
484
|
+
| "_classifiedRoute"
|
|
485
|
+
| "_cacheSignal"
|
|
327
486
|
| "res"
|
|
328
487
|
>;
|
|
329
488
|
|
|
@@ -375,6 +534,7 @@ export function _getRequestContext<TEnv = DefaultEnv>():
|
|
|
375
534
|
export function setRequestContextParams(
|
|
376
535
|
params: Record<string, string>,
|
|
377
536
|
routeName?: string,
|
|
537
|
+
routeMap?: Record<string, string>,
|
|
378
538
|
): void {
|
|
379
539
|
const ctx = requestContextStorage.getStore();
|
|
380
540
|
if (ctx) {
|
|
@@ -387,9 +547,13 @@ export function setRequestContextParams(
|
|
|
387
547
|
: undefined
|
|
388
548
|
) as DefaultRouteName | undefined;
|
|
389
549
|
}
|
|
390
|
-
// Update reverse with scoped resolution now that route is known
|
|
550
|
+
// Update reverse with scoped resolution now that route is known. Production
|
|
551
|
+
// omits routeMap and uses the global map (routes are registered globally);
|
|
552
|
+
// the testing primitives (renderToFlightString/renderServerTree) pass a
|
|
553
|
+
// scoped routeMap so `ctx.reverse` is not order-dependent on whatever router
|
|
554
|
+
// registered last.
|
|
391
555
|
ctx.reverse = createReverseFunction(
|
|
392
|
-
getGlobalRouteMap(),
|
|
556
|
+
routeMap ?? getGlobalRouteMap(),
|
|
393
557
|
routeName,
|
|
394
558
|
params,
|
|
395
559
|
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
@@ -405,11 +569,17 @@ export function setRequestContextParams(
|
|
|
405
569
|
*/
|
|
406
570
|
export function setRequestContextPrevRouteKey(
|
|
407
571
|
prevRouteKey: string | undefined,
|
|
572
|
+
currentUrl?: URL,
|
|
573
|
+
currentParams?: Record<string, string>,
|
|
408
574
|
): void {
|
|
409
575
|
const ctx = requestContextStorage.getStore();
|
|
410
|
-
if (ctx
|
|
411
|
-
|
|
412
|
-
}
|
|
576
|
+
if (!ctx) return;
|
|
577
|
+
if (prevRouteKey !== undefined) ctx._prevRouteKey = prevRouteKey;
|
|
578
|
+
// Source URL/params for the transition({ when }) gate (effectiveFromUrl /
|
|
579
|
+
// effectiveFromMatch.params from the navigation snapshot). Same write point as
|
|
580
|
+
// _prevRouteKey, which doubles as fromRouteName.
|
|
581
|
+
if (currentUrl !== undefined) ctx._gateCurrentUrl = currentUrl;
|
|
582
|
+
if (currentParams !== undefined) ctx._gateCurrentParams = currentParams;
|
|
413
583
|
}
|
|
414
584
|
|
|
415
585
|
/**
|
|
@@ -423,23 +593,7 @@ export function getLocationState(): LocationStateEntry[] | undefined {
|
|
|
423
593
|
return ctx?._locationState;
|
|
424
594
|
}
|
|
425
595
|
|
|
426
|
-
|
|
427
|
-
* Get the current request context, throwing if not available
|
|
428
|
-
* @deprecated Use getRequestContext() directly — it now throws if outside context
|
|
429
|
-
*/
|
|
430
|
-
export function requireRequestContext<
|
|
431
|
-
TEnv = DefaultEnv,
|
|
432
|
-
>(): RequestContext<TEnv> {
|
|
433
|
-
return getRequestContext<TEnv>();
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
438
|
-
*/
|
|
439
|
-
export interface ExecutionContext {
|
|
440
|
-
waitUntil(promise: Promise<any>): void;
|
|
441
|
-
passThroughOnException(): void;
|
|
442
|
-
}
|
|
596
|
+
export type { ExecutionContext };
|
|
443
597
|
|
|
444
598
|
/**
|
|
445
599
|
* Options for creating a request context
|
|
@@ -453,6 +607,11 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
453
607
|
initialResponse?: Response;
|
|
454
608
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
455
609
|
cacheStore?: SegmentCacheStore;
|
|
610
|
+
/**
|
|
611
|
+
* Handler-owned registry of explicit per-scope stores for cross-store tag
|
|
612
|
+
* invalidation. Created once per handler, reused across requests.
|
|
613
|
+
*/
|
|
614
|
+
explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
456
615
|
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
457
616
|
cacheProfiles?: Record<
|
|
458
617
|
string,
|
|
@@ -462,6 +621,10 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
462
621
|
executionContext?: ExecutionContext;
|
|
463
622
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
464
623
|
themeConfig?: ResolvedThemeConfig | null;
|
|
624
|
+
/** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
|
|
625
|
+
stateCookieName?: string;
|
|
626
|
+
/** Build version, used as the prefix of a server-rotated rango state value. */
|
|
627
|
+
version?: string;
|
|
465
628
|
}
|
|
466
629
|
|
|
467
630
|
/**
|
|
@@ -482,15 +645,17 @@ export function createRequestContext<TEnv>(
|
|
|
482
645
|
variables,
|
|
483
646
|
initialResponse,
|
|
484
647
|
cacheStore,
|
|
648
|
+
explicitTaggedStores,
|
|
485
649
|
cacheProfiles,
|
|
486
650
|
executionContext,
|
|
487
651
|
themeConfig,
|
|
652
|
+
stateCookieName,
|
|
653
|
+
version: stateVersion,
|
|
488
654
|
} = options;
|
|
489
655
|
const cookieHeader = request.headers.get("Cookie");
|
|
656
|
+
let rangoStateRotated = false;
|
|
490
657
|
let parsedCookies: Record<string, string> | null = null;
|
|
491
658
|
|
|
492
|
-
// Create stub response for collecting headers/cookies.
|
|
493
|
-
// All cookie/header mutations go here; cookie reads derive from it.
|
|
494
659
|
let stubResponse = initialResponse
|
|
495
660
|
? new Response(null, {
|
|
496
661
|
status: initialResponse.status,
|
|
@@ -499,11 +664,9 @@ export function createRequestContext<TEnv>(
|
|
|
499
664
|
})
|
|
500
665
|
: new Response(null, { status: 200 });
|
|
501
666
|
|
|
502
|
-
// Create handle store and loader memoization for this request
|
|
503
667
|
const handleStore = createHandleStore();
|
|
504
668
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
505
669
|
|
|
506
|
-
// Lazy parse cookies from the original Cookie header
|
|
507
670
|
const getParsedCookies = (): Record<string, string> => {
|
|
508
671
|
if (!parsedCookies) {
|
|
509
672
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -511,7 +674,6 @@ export function createRequestContext<TEnv>(
|
|
|
511
674
|
return parsedCookies;
|
|
512
675
|
};
|
|
513
676
|
|
|
514
|
-
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
515
677
|
let responseCookieCache: Map<string, string | null> | null = null;
|
|
516
678
|
const getResponseCookies = (): Map<string, string | null> => {
|
|
517
679
|
if (!responseCookieCache) {
|
|
@@ -523,8 +685,6 @@ export function createRequestContext<TEnv>(
|
|
|
523
685
|
responseCookieCache = null;
|
|
524
686
|
};
|
|
525
687
|
|
|
526
|
-
// Guard: throw if a response-level side effect is called inside a cache() scope.
|
|
527
|
-
// Uses ALS to detect the scope (set during segment resolution).
|
|
528
688
|
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
529
689
|
if (isInsideCacheScope()) {
|
|
530
690
|
throw new Error(
|
|
@@ -535,8 +695,7 @@ export function createRequestContext<TEnv>(
|
|
|
535
695
|
}
|
|
536
696
|
}
|
|
537
697
|
|
|
538
|
-
//
|
|
539
|
-
// The stub IS the source of truth for same-request mutations.
|
|
698
|
+
// Response stub Set-Cookie wins, then original header (source of truth for mutations).
|
|
540
699
|
const effectiveCookie = (name: string): string | undefined => {
|
|
541
700
|
const mutations = getResponseCookies();
|
|
542
701
|
if (mutations.has(name)) {
|
|
@@ -546,14 +705,11 @@ export function createRequestContext<TEnv>(
|
|
|
546
705
|
return getParsedCookies()[name];
|
|
547
706
|
};
|
|
548
707
|
|
|
549
|
-
// Theme helpers (only used when themeConfig is provided)
|
|
550
708
|
const getTheme = (): Theme | undefined => {
|
|
551
709
|
if (!themeConfig) return undefined;
|
|
552
710
|
|
|
553
|
-
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
554
711
|
const stored = effectiveCookie(themeConfig.storageKey);
|
|
555
712
|
if (stored) {
|
|
556
|
-
// Validate stored value
|
|
557
713
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
558
714
|
return "system";
|
|
559
715
|
}
|
|
@@ -567,15 +723,15 @@ export function createRequestContext<TEnv>(
|
|
|
567
723
|
const setTheme = (theme: Theme): void => {
|
|
568
724
|
if (!themeConfig) return;
|
|
569
725
|
|
|
570
|
-
//
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
726
|
+
// Shared guard (isValidTheme): reject any value not in the configured theme
|
|
727
|
+
// set, AND reject "system" when system detection is off — a cookie of
|
|
728
|
+
// theme=system with enableSystem:false would re-apply a bogus class="system"
|
|
729
|
+
// on the next SSR.
|
|
730
|
+
if (!isValidTheme(theme, themeConfig)) {
|
|
731
|
+
warnInvalidTheme(theme, themeConfig);
|
|
575
732
|
return;
|
|
576
733
|
}
|
|
577
734
|
|
|
578
|
-
// Write to stub — effectiveCookie() will pick it up on next read
|
|
579
735
|
stubResponse.headers.append(
|
|
580
736
|
"Set-Cookie",
|
|
581
737
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
@@ -587,10 +743,8 @@ export function createRequestContext<TEnv>(
|
|
|
587
743
|
invalidateResponseCookieCache();
|
|
588
744
|
};
|
|
589
745
|
|
|
590
|
-
// Strip internal _rsc* params so userland sees a clean URL.
|
|
591
746
|
const cleanUrl = stripInternalParams(url);
|
|
592
747
|
|
|
593
|
-
// Build the context object first (without use), then add use
|
|
594
748
|
const ctx: RequestContext<TEnv> = {
|
|
595
749
|
env,
|
|
596
750
|
request,
|
|
@@ -598,7 +752,7 @@ export function createRequestContext<TEnv>(
|
|
|
598
752
|
originalUrl: new URL(request.url),
|
|
599
753
|
pathname: url.pathname,
|
|
600
754
|
searchParams: cleanUrl.searchParams,
|
|
601
|
-
|
|
755
|
+
_variables: variables,
|
|
602
756
|
get: ((keyOrVar: any) => {
|
|
603
757
|
if (isNonCacheable(variables, keyOrVar) && isInsideCacheScope()) {
|
|
604
758
|
throw new Error(
|
|
@@ -676,6 +830,45 @@ export function createRequestContext<TEnv>(
|
|
|
676
830
|
stubResponse.headers.set(name, value);
|
|
677
831
|
},
|
|
678
832
|
|
|
833
|
+
// Rotate the rango state cookie for the responding client (the server seat
|
|
834
|
+
// of invalidateClientCache). Writes ONE Set-Cookie per request with the
|
|
835
|
+
// value {version}:{timestamp}; the `:` stays raw (the cookie-name.ts
|
|
836
|
+
// serializer), not the URL-encoded form serializeCookieValue would produce.
|
|
837
|
+
// The timestamp is strictly greater than the client's current one (inbound
|
|
838
|
+
// X-Rango-State), so a same-millisecond server rotation still differs from
|
|
839
|
+
// the client value and the divergence observer fires.
|
|
840
|
+
_rotateStateCookie(): void {
|
|
841
|
+
if (rangoStateRotated) return;
|
|
842
|
+
rangoStateRotated = true;
|
|
843
|
+
if (!stateCookieName) return;
|
|
844
|
+
// The client's current value, for the monotonic guard: prefer the
|
|
845
|
+
// X-Rango-State header (router navigation/prefetch fetches send it), but
|
|
846
|
+
// fall back to the request's rango state cookie — action POSTs / plain
|
|
847
|
+
// app fetch()s carry no router header yet DO send the cookie. Without the
|
|
848
|
+
// fallback, prevTs stays 0 and a same-ms mint can equal the client value,
|
|
849
|
+
// leaving the divergence observer silent. `|| null` so an empty header
|
|
850
|
+
// ('' from proxy normalization) falls through instead of short-circuiting.
|
|
851
|
+
// getRawCookieValue reads the cookie undecoded (the wire value
|
|
852
|
+
// decodeStateValue decodes exactly once) AND is the same parser the client
|
|
853
|
+
// mirror uses, so both seats read the same jar entry.
|
|
854
|
+
const prevRaw =
|
|
855
|
+
(request.headers.get("x-rango-state") || null) ??
|
|
856
|
+
getRawCookieValue(cookieHeader, stateCookieName);
|
|
857
|
+
const value = mintStateValue(stateVersion ?? "0", prevRaw);
|
|
858
|
+
stubResponse.headers.append(
|
|
859
|
+
"Set-Cookie",
|
|
860
|
+
serializeStateCookie(stateCookieName, value, url.protocol === "https:"),
|
|
861
|
+
);
|
|
862
|
+
invalidateResponseCookieCache();
|
|
863
|
+
},
|
|
864
|
+
|
|
865
|
+
// Set the keepClientCache() directive header. The action bridge reads it on
|
|
866
|
+
// the response and suppresses its automatic invalidation. `.set` makes this
|
|
867
|
+
// idempotent (one header regardless of call count).
|
|
868
|
+
_setKeepCacheDirective(): void {
|
|
869
|
+
stubResponse.headers.set(KEEP_CACHE_HEADER, "1");
|
|
870
|
+
},
|
|
871
|
+
|
|
679
872
|
setStatus(status: number): void {
|
|
680
873
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
681
874
|
assertNotInsideCacheScopeALS("setStatus");
|
|
@@ -692,27 +885,31 @@ export function createRequestContext<TEnv>(
|
|
|
692
885
|
});
|
|
693
886
|
},
|
|
694
887
|
|
|
695
|
-
// Placeholder - will be replaced below
|
|
696
888
|
use: null as any,
|
|
697
889
|
|
|
698
890
|
method: request.method,
|
|
699
891
|
|
|
700
892
|
_handleStore: handleStore,
|
|
893
|
+
_transitionWhen: [],
|
|
701
894
|
_cacheStore: cacheStore,
|
|
895
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
896
|
+
_requestTags: new Set<string>(),
|
|
702
897
|
_cacheProfiles: cacheProfiles,
|
|
703
898
|
|
|
704
899
|
waitUntil(fn: () => Promise<void>): void {
|
|
705
900
|
if (executionContext?.waitUntil) {
|
|
706
|
-
//
|
|
707
|
-
|
|
901
|
+
// Wrap in Promise.resolve().then(fn) so a SYNCHRONOUS throw in a
|
|
902
|
+
// non-async callback becomes a rejected promise handed to the host's
|
|
903
|
+
// waitUntil (logged as a background failure), instead of escaping into
|
|
904
|
+
// the request flow. Mirrors fireAndForgetWaitUntil's deferral.
|
|
905
|
+
executionContext.waitUntil(Promise.resolve().then(fn));
|
|
708
906
|
} else {
|
|
709
|
-
|
|
710
|
-
fn().catch((err) =>
|
|
711
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
712
|
-
);
|
|
907
|
+
fireAndForgetWaitUntil(fn);
|
|
713
908
|
}
|
|
714
909
|
},
|
|
715
910
|
|
|
911
|
+
executionContext,
|
|
912
|
+
|
|
716
913
|
_onResponseCallbacks: [],
|
|
717
914
|
|
|
718
915
|
onResponse(callback: (response: Response) => Response): void {
|
|
@@ -721,7 +918,6 @@ export function createRequestContext<TEnv>(
|
|
|
721
918
|
this._onResponseCallbacks.push(callback);
|
|
722
919
|
},
|
|
723
920
|
|
|
724
|
-
// Theme properties (only set when themeConfig is provided)
|
|
725
921
|
get theme() {
|
|
726
922
|
return themeConfig ? getTheme() : undefined;
|
|
727
923
|
},
|
|
@@ -745,35 +941,88 @@ export function createRequestContext<TEnv>(
|
|
|
745
941
|
_reportedErrors: new WeakSet<object>(),
|
|
746
942
|
_metricsStore: undefined,
|
|
747
943
|
|
|
944
|
+
_renderBarrier: null as any,
|
|
945
|
+
_resolveRenderBarrier: null as any,
|
|
946
|
+
_renderBarrierSegmentOrder: undefined,
|
|
947
|
+
|
|
748
948
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
749
949
|
};
|
|
750
950
|
|
|
751
|
-
//
|
|
951
|
+
// Lazy allocation: only create Promise when a loader calls rendered().
|
|
952
|
+
let barrierResolved = false;
|
|
953
|
+
let resolveBarrier: (() => void) | undefined;
|
|
954
|
+
ctx._renderBarrier = null as any;
|
|
955
|
+
ctx._resolveRenderBarrier = (
|
|
956
|
+
segments: Array<{ type: string; id: string }>,
|
|
957
|
+
) => {
|
|
958
|
+
if (barrierResolved) return;
|
|
959
|
+
barrierResolved = true;
|
|
960
|
+
const segOrder = segments
|
|
961
|
+
.filter((s) => s.type !== "loader")
|
|
962
|
+
.map((s) => s.id);
|
|
963
|
+
ctx._renderBarrierSegmentOrder = segOrder;
|
|
964
|
+
|
|
965
|
+
const closeGuard = () => {
|
|
966
|
+
ctx._renderBarrierWaiters = undefined;
|
|
967
|
+
ctx._handlerLoaderDeps = undefined;
|
|
968
|
+
ctx._renderBarrierGuardClosed = true;
|
|
969
|
+
};
|
|
970
|
+
|
|
971
|
+
if (ctx._treeHasStreaming) {
|
|
972
|
+
handleStore.settled.then(closeGuard);
|
|
973
|
+
} else {
|
|
974
|
+
ctx._renderBarrierHandleSnapshot = buildHandleSnapshot(
|
|
975
|
+
handleStore,
|
|
976
|
+
segOrder,
|
|
977
|
+
);
|
|
978
|
+
closeGuard();
|
|
979
|
+
}
|
|
980
|
+
if (resolveBarrier) resolveBarrier();
|
|
981
|
+
};
|
|
982
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
983
|
+
get() {
|
|
984
|
+
const p = barrierResolved
|
|
985
|
+
? Promise.resolve()
|
|
986
|
+
: new Promise<void>((resolve) => {
|
|
987
|
+
resolveBarrier = resolve;
|
|
988
|
+
});
|
|
989
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
990
|
+
value: p,
|
|
991
|
+
writable: false,
|
|
992
|
+
configurable: false,
|
|
993
|
+
});
|
|
994
|
+
return p;
|
|
995
|
+
},
|
|
996
|
+
configurable: true,
|
|
997
|
+
});
|
|
998
|
+
|
|
752
999
|
ctx.use = createUseFunction({
|
|
753
1000
|
handleStore,
|
|
754
1001
|
loaderPromises,
|
|
755
1002
|
getContext: () => ctx,
|
|
756
1003
|
});
|
|
757
1004
|
|
|
758
|
-
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
759
1005
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
760
1006
|
return ctx;
|
|
761
1007
|
}
|
|
762
1008
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
*
|
|
768
|
-
|
|
769
|
-
|
|
1009
|
+
// Capture the Max-Age value so it can be parsed numerically. A leading zero
|
|
1010
|
+
// (Max-Age=05) is a non-zero lifetime, not a deletion; only a value that parses
|
|
1011
|
+
// to <= 0 marks a cookie for deletion. Pattern-matching a leading "0" misread
|
|
1012
|
+
// zero-prefixed values like 05 / 010 as deletions.
|
|
1013
|
+
const MAX_AGE_RE = /;\s*Max-Age\s*=\s*(-?\d+)/i;
|
|
1014
|
+
|
|
1015
|
+
function isCookieDeletion(header: string): boolean {
|
|
1016
|
+
const m = MAX_AGE_RE.exec(header);
|
|
1017
|
+
if (!m) return false;
|
|
1018
|
+
return Number(m[1]) <= 0;
|
|
1019
|
+
}
|
|
770
1020
|
|
|
771
1021
|
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
772
1022
|
const result = new Map<string, string | null>();
|
|
773
1023
|
const setCookies = response.headers.getSetCookie();
|
|
774
1024
|
|
|
775
1025
|
for (const header of setCookies) {
|
|
776
|
-
// First segment before ';' is the name=value pair
|
|
777
1026
|
const semiIdx = header.indexOf(";");
|
|
778
1027
|
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
779
1028
|
const eqIdx = pair.indexOf("=");
|
|
@@ -785,49 +1034,22 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
785
1034
|
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
786
1035
|
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
787
1036
|
} catch {
|
|
788
|
-
// Malformed encoding — skip this entry
|
|
789
1037
|
continue;
|
|
790
1038
|
}
|
|
791
1039
|
|
|
792
|
-
|
|
793
|
-
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
1040
|
+
const isDeleted = isCookieDeletion(header);
|
|
794
1041
|
result.set(name, isDeleted ? null : value);
|
|
795
1042
|
}
|
|
796
1043
|
|
|
797
1044
|
return result;
|
|
798
1045
|
}
|
|
799
1046
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
cookieHeader: string | null,
|
|
805
|
-
): Record<string, string> {
|
|
806
|
-
if (!cookieHeader) return {};
|
|
807
|
-
|
|
808
|
-
const cookies: Record<string, string> = {};
|
|
809
|
-
const pairs = cookieHeader.split(";");
|
|
810
|
-
|
|
811
|
-
for (const pair of pairs) {
|
|
812
|
-
const [name, ...rest] = pair.trim().split("=");
|
|
813
|
-
if (name) {
|
|
814
|
-
const raw = rest.join("=");
|
|
815
|
-
try {
|
|
816
|
-
cookies[name] = decodeURIComponent(raw);
|
|
817
|
-
} catch {
|
|
818
|
-
// Malformed percent-encoded value (e.g. %zz, %2) - fall back to raw value
|
|
819
|
-
cookies[name] = raw;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
return cookies;
|
|
825
|
-
}
|
|
1047
|
+
// Re-exported for unit tests and the existing import path. The implementation
|
|
1048
|
+
// lives in the dependency-free ./cookie-parse leaf so consumers (e.g. the host
|
|
1049
|
+
// dispatcher) can share it without pulling this module's request-context graph.
|
|
1050
|
+
export { parseCookiesFromHeader };
|
|
826
1051
|
|
|
827
|
-
|
|
828
|
-
* Serialize a cookie for Set-Cookie header
|
|
829
|
-
*/
|
|
830
|
-
function serializeCookieValue(
|
|
1052
|
+
export function serializeCookieValue(
|
|
831
1053
|
name: string,
|
|
832
1054
|
value: string,
|
|
833
1055
|
options: CookieOptions = {},
|
|
@@ -854,20 +1076,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
854
1076
|
getContext: () => RequestContext<TEnv>;
|
|
855
1077
|
}
|
|
856
1078
|
|
|
857
|
-
/**
|
|
858
|
-
* Create the use() function for loader and handle composition.
|
|
859
|
-
*
|
|
860
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
861
|
-
* - For loaders: executes and memoizes loader functions
|
|
862
|
-
* - For handles: returns a push function to add handle data
|
|
863
|
-
*/
|
|
864
1079
|
export function createUseFunction<TEnv>(
|
|
865
1080
|
options: CreateUseFunctionOptions<TEnv>,
|
|
866
1081
|
): RequestContext["use"] {
|
|
867
1082
|
const { handleStore, loaderPromises, getContext } = options;
|
|
868
1083
|
|
|
869
1084
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
870
|
-
// Handle case: return a push function
|
|
871
1085
|
if (isHandle(item)) {
|
|
872
1086
|
const handle = item;
|
|
873
1087
|
const ctx = getContext();
|
|
@@ -880,30 +1094,24 @@ export function createUseFunction<TEnv>(
|
|
|
880
1094
|
);
|
|
881
1095
|
}
|
|
882
1096
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
// Push directly - promises will be serialized by RSC and streamed
|
|
894
|
-
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
895
|
-
};
|
|
1097
|
+
return withDefer(
|
|
1098
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
1099
|
+
const valueOrPromise =
|
|
1100
|
+
typeof dataOrFn === "function"
|
|
1101
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
1102
|
+
: dataOrFn;
|
|
1103
|
+
|
|
1104
|
+
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
1105
|
+
},
|
|
1106
|
+
);
|
|
896
1107
|
}
|
|
897
1108
|
|
|
898
|
-
// Loader case
|
|
899
1109
|
const loader = item as LoaderDefinition<any, any>;
|
|
900
1110
|
|
|
901
|
-
// Return cached promise if already started
|
|
902
1111
|
if (loaderPromises.has(loader.$$id)) {
|
|
903
1112
|
return loaderPromises.get(loader.$$id);
|
|
904
1113
|
}
|
|
905
1114
|
|
|
906
|
-
// Get loader function - either from loader object or fetchable registry
|
|
907
1115
|
let loaderFn = loader.fn;
|
|
908
1116
|
if (!loaderFn) {
|
|
909
1117
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -920,24 +1128,36 @@ export function createUseFunction<TEnv>(
|
|
|
920
1128
|
|
|
921
1129
|
const ctx = getContext();
|
|
922
1130
|
|
|
923
|
-
//
|
|
1131
|
+
// Build the typed ctx.search the same way the render path
|
|
1132
|
+
// (createHandlerContext) and the fetchable-loader path (loader-fetch.ts) do:
|
|
1133
|
+
// parse the route's search schema over the cleaned searchParams. The base
|
|
1134
|
+
// RequestContext carries no `search` field, so reading `(ctx as any).search`
|
|
1135
|
+
// here always yielded {} — dropping typed search for action/dispatch loaders.
|
|
1136
|
+
const searchSchema = ctx._routeName
|
|
1137
|
+
? getSearchSchema(ctx._routeName)
|
|
1138
|
+
: undefined;
|
|
1139
|
+
const loaderSearch = searchSchema
|
|
1140
|
+
? parseSearchParams(ctx.searchParams, searchSchema)
|
|
1141
|
+
: {};
|
|
1142
|
+
|
|
924
1143
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
925
1144
|
params: ctx.params,
|
|
926
1145
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
927
1146
|
request: ctx.request,
|
|
928
1147
|
searchParams: ctx.searchParams,
|
|
929
|
-
search:
|
|
1148
|
+
search: loaderSearch,
|
|
930
1149
|
pathname: ctx.pathname,
|
|
931
1150
|
url: ctx.url,
|
|
1151
|
+
originalUrl: ctx.originalUrl,
|
|
932
1152
|
env: ctx.env as any,
|
|
933
|
-
|
|
1153
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1154
|
+
executionContext: ctx.executionContext,
|
|
934
1155
|
get: ctx.get as any,
|
|
935
|
-
use: <TDep, TDepParams = any>(
|
|
1156
|
+
use: (<TDep, TDepParams = any>(
|
|
936
1157
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
937
1158
|
): Promise<TDep> => {
|
|
938
|
-
// Recursive call - will start dep loader if not already started
|
|
939
1159
|
return ctx.use(dep);
|
|
940
|
-
},
|
|
1160
|
+
}) as LoaderContext["use"],
|
|
941
1161
|
method: "GET",
|
|
942
1162
|
body: undefined,
|
|
943
1163
|
reverse: createReverseFunction(
|
|
@@ -946,14 +1166,22 @@ export function createUseFunction<TEnv>(
|
|
|
946
1166
|
ctx.params as Record<string, string>,
|
|
947
1167
|
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
948
1168
|
),
|
|
1169
|
+
rendered: () => {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
|
|
1172
|
+
`It cannot be used from request-context loaders or server actions.`,
|
|
1173
|
+
);
|
|
1174
|
+
},
|
|
949
1175
|
};
|
|
950
1176
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1177
|
+
// Meter through the same unified phase API as the loader-resolution funnel
|
|
1178
|
+
// (observePhase), so a loader resolved via this base request-context ctx.use
|
|
1179
|
+
// co-emits the "loader:<id>" perf metric AND the "rango.loader" span — no
|
|
1180
|
+
// drift between the two ctx.use implementations.
|
|
1181
|
+
const promise = observePhase(PHASES.loader(loader.$$id), () =>
|
|
1182
|
+
Promise.resolve(loaderFn(loaderCtx)),
|
|
1183
|
+
);
|
|
955
1184
|
|
|
956
|
-
// Memoize for subsequent calls
|
|
957
1185
|
loaderPromises.set(loader.$$id, promise);
|
|
958
1186
|
|
|
959
1187
|
return promise;
|