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