@rangojs/router 0.0.0-experimental.eb0645d3 → 0.0.0-experimental.f1468e3c
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 +126 -16
- package/dist/bin/rango.js +319 -95
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +2724 -1053
- 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 +11 -9
- package/skills/hooks/SKILL.md +243 -29
- package/skills/host-router/SKILL.md +83 -23
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +68 -19
- package/skills/layout/SKILL.md +13 -9
- package/skills/links/SKILL.md +190 -23
- package/skills/loader/SKILL.md +235 -9
- package/skills/middleware/SKILL.md +18 -10
- package/skills/migrate-nextjs/SKILL.md +43 -19
- package/skills/migrate-react-router/SKILL.md +8 -2
- package/skills/mime-routes/SKILL.md +28 -1
- package/skills/observability/SKILL.md +172 -0
- package/skills/parallel/SKILL.md +18 -7
- package/skills/prerender/SKILL.md +65 -60
- package/skills/rango/SKILL.md +251 -24
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +115 -48
- package/skills/route/SKILL.md +46 -5
- package/skills/router-setup/SKILL.md +30 -8
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +775 -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 +0 -65
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +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 +94 -25
- package/src/browser/navigation-client.ts +121 -84
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +115 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +147 -128
- package/src/browser/prefetch/cache.ts +107 -56
- package/src/browser/prefetch/fetch.ts +204 -34
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +30 -7
- package/src/browser/react/NavigationProvider.tsx +283 -118
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- 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 +17 -14
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +11 -11
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +25 -3
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +99 -8
- package/src/browser/rsc-router.tsx +91 -24
- package/src/browser/scroll-restoration.ts +30 -17
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +214 -55
- package/src/browser/types.ts +80 -9
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +60 -35
- package/src/build/generate-route-types.ts +2 -1
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +117 -14
- 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 +117 -23
- 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 +55 -28
- package/src/build/route-types/scan-filter.ts +1 -1
- package/src/build/route-types/source-scan.ts +216 -0
- package/src/build/runtime-discovery.ts +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 +36 -61
- 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 +31 -23
- 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 +96 -12
- package/src/index.ts +94 -14
- package/src/internal-debug.ts +11 -10
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +61 -6
- 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 +34 -0
- package/src/reverse.ts +65 -40
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +19 -77
- package/src/route-definition/dsl-helpers.ts +304 -309
- package/src/route-definition/helper-factories.ts +28 -140
- package/src/route-definition/helpers-types.ts +82 -55
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +12 -1
- package/src/route-definition/use-item-types.ts +29 -0
- package/src/route-map-builder.ts +0 -16
- package/src/route-types.ts +19 -46
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +73 -25
- package/src/router/error-handling.ts +45 -18
- package/src/router/find-match.ts +44 -23
- package/src/router/handler-context.ts +27 -43
- package/src/router/instrument.ts +350 -0
- package/src/router/intercept-resolution.ts +39 -20
- package/src/router/lazy-includes.ts +10 -47
- package/src/router/loader-resolution.ts +155 -72
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +18 -29
- package/src/router/match-api.ts +9 -24
- 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 +159 -285
- package/src/router/match-middleware/cache-store.ts +64 -52
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +44 -74
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -134
- package/src/router/middleware.ts +247 -166
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +85 -94
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prerender-match.ts +104 -65
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +28 -62
- package/src/router/revalidation.ts +123 -73
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +3 -28
- package/src/router/router-interfaces.ts +83 -35
- package/src/router/router-options.ts +136 -5
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +97 -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 +272 -320
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +56 -0
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +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 +162 -64
- package/src/router/types.ts +9 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +110 -55
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +264 -220
- package/src/rsc/helpers.ts +100 -6
- package/src/rsc/index.ts +2 -5
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +28 -41
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +117 -11
- 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 +88 -188
- package/src/rsc/rsc-rendering.ts +98 -76
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +281 -117
- package/src/rsc/ssr-setup.ts +16 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +23 -5
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +35 -30
- package/src/segment-loader-promise.ts +31 -4
- package/src/segment-system.tsx +254 -143
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +163 -51
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +287 -178
- package/src/ssr/index.tsx +21 -16
- package/src/static-handler.ts +10 -13
- 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 +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +110 -62
- package/src/types/index.ts +3 -10
- package/src/types/loader-types.ts +11 -9
- package/src/types/request-scope.ts +112 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +135 -14
- package/src/urls/include-helper.ts +9 -56
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +29 -12
- package/src/urls/path-helper.ts +17 -106
- package/src/urls/pattern-types.ts +36 -19
- package/src/urls/response-types.ts +22 -29
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -19
- package/src/use-loader.tsx +292 -107
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +8 -7
- package/src/vite/discovery/discover-routers.ts +126 -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 +96 -68
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +44 -0
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +2 -0
- package/src/vite/inject-client-debug.ts +36 -0
- package/src/vite/plugin-types.ts +126 -8
- 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-stub.ts +1 -21
- package/src/vite/plugins/expose-action-id.ts +48 -95
- package/src/vite/plugins/expose-id-utils.ts +88 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +101 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +11 -90
- package/src/vite/plugins/expose-ids/loader-transform.ts +14 -24
- package/src/vite/plugins/expose-ids/router-transform.ts +118 -29
- package/src/vite/plugins/expose-internal-ids.ts +505 -486
- package/src/vite/plugins/performance-tracks.ts +26 -25
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/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 +109 -118
- package/src/vite/router-discovery.ts +718 -119
- 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 +54 -39
- package/src/vite/utils/shared-utils.ts +90 -41
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -11,7 +11,15 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
+
import { parseCookiesFromHeader } from "./cookie-parse.js";
|
|
15
|
+
import type { CacheErrorCategory } from "../cache/cache-error.js";
|
|
14
16
|
import type { CookieOptions } from "../router/middleware.js";
|
|
17
|
+
import {
|
|
18
|
+
KEEP_CACHE_HEADER,
|
|
19
|
+
getRawCookieValue,
|
|
20
|
+
mintStateValue,
|
|
21
|
+
serializeStateCookie,
|
|
22
|
+
} from "../browser/cookie-name.js";
|
|
15
23
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
16
24
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
17
25
|
import type {
|
|
@@ -33,11 +41,21 @@ import {
|
|
|
33
41
|
type HandleData,
|
|
34
42
|
} from "./handle-store.js";
|
|
35
43
|
import { isHandle } from "../handle.js";
|
|
36
|
-
import {
|
|
44
|
+
import { withDefer } from "../defer.js";
|
|
45
|
+
import { type MetricsStore } from "./context.js";
|
|
46
|
+
import { observePhase, PHASES } from "../router/instrument.js";
|
|
37
47
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
38
48
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
39
49
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
40
|
-
import {
|
|
50
|
+
import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
|
|
51
|
+
import type { TransitionWhenFn } from "../types/segments.js";
|
|
52
|
+
import type { ResolvedTracing } from "../router/tracing.js";
|
|
53
|
+
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
54
|
+
import {
|
|
55
|
+
THEME_COOKIE,
|
|
56
|
+
isValidTheme,
|
|
57
|
+
warnInvalidTheme,
|
|
58
|
+
} from "../theme/constants.js";
|
|
41
59
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
42
60
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
43
61
|
import { isInsideCacheScope } from "./context.js";
|
|
@@ -45,7 +63,12 @@ import {
|
|
|
45
63
|
createReverseFunction,
|
|
46
64
|
stripInternalParams,
|
|
47
65
|
} from "../router/handler-context.js";
|
|
48
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
getGlobalRouteMap,
|
|
68
|
+
isRouteRootScoped,
|
|
69
|
+
getSearchSchema,
|
|
70
|
+
} from "../route-map-builder.js";
|
|
71
|
+
import { parseSearchParams } from "../search-params.js";
|
|
49
72
|
import { invariant } from "../errors.js";
|
|
50
73
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
51
74
|
|
|
@@ -58,22 +81,7 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
58
81
|
export interface RequestContext<
|
|
59
82
|
TEnv = DefaultEnv,
|
|
60
83
|
TParams = Record<string, string>,
|
|
61
|
-
> {
|
|
62
|
-
/** Platform bindings (Cloudflare env, etc.) */
|
|
63
|
-
env: TEnv;
|
|
64
|
-
/** Original HTTP request */
|
|
65
|
-
request: Request;
|
|
66
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
67
|
-
url: URL;
|
|
68
|
-
/**
|
|
69
|
-
* The original request URL with all parameters intact, including
|
|
70
|
-
* internal `_rsc*` transport params.
|
|
71
|
-
*/
|
|
72
|
-
originalUrl: URL;
|
|
73
|
-
/** URL pathname */
|
|
74
|
-
pathname: string;
|
|
75
|
-
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
76
|
-
searchParams: URLSearchParams;
|
|
84
|
+
> extends RequestScope<TEnv> {
|
|
77
85
|
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
78
86
|
_variables: Record<string, any>;
|
|
79
87
|
/** Get a variable set by middleware */
|
|
@@ -115,6 +123,10 @@ export interface RequestContext<
|
|
|
115
123
|
setStatus(status: number): void;
|
|
116
124
|
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
117
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;
|
|
118
130
|
|
|
119
131
|
/**
|
|
120
132
|
* Access loader data or push handle data.
|
|
@@ -150,29 +162,43 @@ export interface RequestContext<
|
|
|
150
162
|
/** @internal Handle store for tracking handle data across segments */
|
|
151
163
|
_handleStore: HandleStore;
|
|
152
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
|
+
|
|
153
174
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
154
175
|
_cacheStore?: SegmentCacheStore;
|
|
155
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
|
+
|
|
156
196
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
157
197
|
_cacheProfiles?: Record<
|
|
158
198
|
string,
|
|
159
199
|
import("../cache/profile-registry.js").CacheProfile
|
|
160
200
|
>;
|
|
161
201
|
|
|
162
|
-
/**
|
|
163
|
-
* Schedule work to run after the response is sent.
|
|
164
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
165
|
-
* On Node.js, runs as fire-and-forget.
|
|
166
|
-
*
|
|
167
|
-
* @example
|
|
168
|
-
* ```typescript
|
|
169
|
-
* ctx.waitUntil(async () => {
|
|
170
|
-
* await cacheStore.set(key, data, ttl);
|
|
171
|
-
* });
|
|
172
|
-
* ```
|
|
173
|
-
*/
|
|
174
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
175
|
-
|
|
176
202
|
/**
|
|
177
203
|
* Register a callback to run when the response is created.
|
|
178
204
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -276,6 +302,30 @@ export interface RequestContext<
|
|
|
276
302
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
277
303
|
_prevRouteKey?: string;
|
|
278
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
|
+
|
|
279
329
|
/**
|
|
280
330
|
* @internal Render barrier for experimental `rendered()` API.
|
|
281
331
|
* Resolves when all non-loader segments have settled and handle data
|
|
@@ -300,7 +350,9 @@ export interface RequestContext<
|
|
|
300
350
|
|
|
301
351
|
/**
|
|
302
352
|
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
303
|
-
* entries (streaming).
|
|
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.
|
|
304
356
|
*/
|
|
305
357
|
_treeHasStreaming?: boolean;
|
|
306
358
|
|
|
@@ -324,6 +376,18 @@ export interface RequestContext<
|
|
|
324
376
|
*/
|
|
325
377
|
_renderBarrierHandleSnapshot?: HandleData;
|
|
326
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
|
+
|
|
327
391
|
/** @internal Per-request error dedup set for onError reporting */
|
|
328
392
|
_reportedErrors: WeakSet<object>;
|
|
329
393
|
|
|
@@ -331,9 +395,13 @@ export interface RequestContext<
|
|
|
331
395
|
* @internal Report a non-fatal background error through the router's
|
|
332
396
|
* onError callback. Wired by the RSC handler / router during request
|
|
333
397
|
* creation. Cache-runtime and other subsystems call this to surface
|
|
334
|
-
* 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`).
|
|
335
400
|
*/
|
|
336
|
-
_reportBackgroundError?: (
|
|
401
|
+
_reportBackgroundError?: (
|
|
402
|
+
error: unknown,
|
|
403
|
+
category: CacheErrorCategory,
|
|
404
|
+
) => void;
|
|
337
405
|
|
|
338
406
|
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
339
407
|
_debugPerformance?: boolean;
|
|
@@ -341,6 +409,9 @@ export interface RequestContext<
|
|
|
341
409
|
/** @internal Request-scoped performance metrics store */
|
|
342
410
|
_metricsStore?: MetricsStore;
|
|
343
411
|
|
|
412
|
+
/** @internal Resolved platform phase-span tracing for this request (Cloudflare or OTel) */
|
|
413
|
+
_tracing?: ResolvedTracing;
|
|
414
|
+
|
|
344
415
|
/** @internal Router basename for this request (used by redirect()) */
|
|
345
416
|
_basename?: string;
|
|
346
417
|
|
|
@@ -349,6 +420,15 @@ export interface RequestContext<
|
|
|
349
420
|
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
350
421
|
*/
|
|
351
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[];
|
|
352
432
|
}
|
|
353
433
|
|
|
354
434
|
/**
|
|
@@ -367,13 +447,23 @@ export type PublicRequestContext<
|
|
|
367
447
|
| "setCookie"
|
|
368
448
|
| "deleteCookie"
|
|
369
449
|
| "_handleStore"
|
|
450
|
+
| "_transitionWhen"
|
|
370
451
|
| "_cacheStore"
|
|
452
|
+
| "_explicitTaggedStores"
|
|
453
|
+
| "_requestTags"
|
|
371
454
|
| "_cacheProfiles"
|
|
372
455
|
| "_onResponseCallbacks"
|
|
373
456
|
| "_themeConfig"
|
|
374
457
|
| "_locationState"
|
|
375
458
|
| "_routeName"
|
|
376
459
|
| "_prevRouteKey"
|
|
460
|
+
| "_gateCurrentUrl"
|
|
461
|
+
| "_gateCurrentParams"
|
|
462
|
+
| "_gateActionId"
|
|
463
|
+
| "_gateActionUrl"
|
|
464
|
+
| "_gateActionResult"
|
|
465
|
+
| "_gateFormData"
|
|
466
|
+
| "_inActionRevalidation"
|
|
377
467
|
| "_reportedErrors"
|
|
378
468
|
| "_renderBarrier"
|
|
379
469
|
| "_resolveRenderBarrier"
|
|
@@ -382,13 +472,17 @@ export type PublicRequestContext<
|
|
|
382
472
|
| "_renderBarrierWaiters"
|
|
383
473
|
| "_handlerLoaderDeps"
|
|
384
474
|
| "_renderBarrierHandleSnapshot"
|
|
475
|
+
| "_renderBarrierGuardClosed"
|
|
385
476
|
| "_reportBackgroundError"
|
|
386
477
|
| "_debugPerformance"
|
|
387
478
|
| "_metricsStore"
|
|
388
479
|
| "_basename"
|
|
389
480
|
| "_setStatus"
|
|
481
|
+
| "_rotateStateCookie"
|
|
482
|
+
| "_setKeepCacheDirective"
|
|
390
483
|
| "_variables"
|
|
391
484
|
| "_classifiedRoute"
|
|
485
|
+
| "_cacheSignal"
|
|
392
486
|
| "res"
|
|
393
487
|
>;
|
|
394
488
|
|
|
@@ -440,6 +534,7 @@ export function _getRequestContext<TEnv = DefaultEnv>():
|
|
|
440
534
|
export function setRequestContextParams(
|
|
441
535
|
params: Record<string, string>,
|
|
442
536
|
routeName?: string,
|
|
537
|
+
routeMap?: Record<string, string>,
|
|
443
538
|
): void {
|
|
444
539
|
const ctx = requestContextStorage.getStore();
|
|
445
540
|
if (ctx) {
|
|
@@ -452,9 +547,13 @@ export function setRequestContextParams(
|
|
|
452
547
|
: undefined
|
|
453
548
|
) as DefaultRouteName | undefined;
|
|
454
549
|
}
|
|
455
|
-
// 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.
|
|
456
555
|
ctx.reverse = createReverseFunction(
|
|
457
|
-
getGlobalRouteMap(),
|
|
556
|
+
routeMap ?? getGlobalRouteMap(),
|
|
458
557
|
routeName,
|
|
459
558
|
params,
|
|
460
559
|
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
@@ -470,11 +569,17 @@ export function setRequestContextParams(
|
|
|
470
569
|
*/
|
|
471
570
|
export function setRequestContextPrevRouteKey(
|
|
472
571
|
prevRouteKey: string | undefined,
|
|
572
|
+
currentUrl?: URL,
|
|
573
|
+
currentParams?: Record<string, string>,
|
|
473
574
|
): void {
|
|
474
575
|
const ctx = requestContextStorage.getStore();
|
|
475
|
-
if (ctx
|
|
476
|
-
|
|
477
|
-
}
|
|
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;
|
|
478
583
|
}
|
|
479
584
|
|
|
480
585
|
/**
|
|
@@ -488,23 +593,7 @@ export function getLocationState(): LocationStateEntry[] | undefined {
|
|
|
488
593
|
return ctx?._locationState;
|
|
489
594
|
}
|
|
490
595
|
|
|
491
|
-
|
|
492
|
-
* Get the current request context, throwing if not available
|
|
493
|
-
* @deprecated Use getRequestContext() directly — it now throws if outside context
|
|
494
|
-
*/
|
|
495
|
-
export function requireRequestContext<
|
|
496
|
-
TEnv = DefaultEnv,
|
|
497
|
-
>(): RequestContext<TEnv> {
|
|
498
|
-
return getRequestContext<TEnv>();
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
503
|
-
*/
|
|
504
|
-
export interface ExecutionContext {
|
|
505
|
-
waitUntil(promise: Promise<any>): void;
|
|
506
|
-
passThroughOnException(): void;
|
|
507
|
-
}
|
|
596
|
+
export type { ExecutionContext };
|
|
508
597
|
|
|
509
598
|
/**
|
|
510
599
|
* Options for creating a request context
|
|
@@ -518,6 +607,11 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
518
607
|
initialResponse?: Response;
|
|
519
608
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
520
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>;
|
|
521
615
|
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
522
616
|
cacheProfiles?: Record<
|
|
523
617
|
string,
|
|
@@ -527,6 +621,10 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
527
621
|
executionContext?: ExecutionContext;
|
|
528
622
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
529
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;
|
|
530
628
|
}
|
|
531
629
|
|
|
532
630
|
/**
|
|
@@ -547,15 +645,17 @@ export function createRequestContext<TEnv>(
|
|
|
547
645
|
variables,
|
|
548
646
|
initialResponse,
|
|
549
647
|
cacheStore,
|
|
648
|
+
explicitTaggedStores,
|
|
550
649
|
cacheProfiles,
|
|
551
650
|
executionContext,
|
|
552
651
|
themeConfig,
|
|
652
|
+
stateCookieName,
|
|
653
|
+
version: stateVersion,
|
|
553
654
|
} = options;
|
|
554
655
|
const cookieHeader = request.headers.get("Cookie");
|
|
656
|
+
let rangoStateRotated = false;
|
|
555
657
|
let parsedCookies: Record<string, string> | null = null;
|
|
556
658
|
|
|
557
|
-
// Create stub response for collecting headers/cookies.
|
|
558
|
-
// All cookie/header mutations go here; cookie reads derive from it.
|
|
559
659
|
let stubResponse = initialResponse
|
|
560
660
|
? new Response(null, {
|
|
561
661
|
status: initialResponse.status,
|
|
@@ -564,11 +664,9 @@ export function createRequestContext<TEnv>(
|
|
|
564
664
|
})
|
|
565
665
|
: new Response(null, { status: 200 });
|
|
566
666
|
|
|
567
|
-
// Create handle store and loader memoization for this request
|
|
568
667
|
const handleStore = createHandleStore();
|
|
569
668
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
570
669
|
|
|
571
|
-
// Lazy parse cookies from the original Cookie header
|
|
572
670
|
const getParsedCookies = (): Record<string, string> => {
|
|
573
671
|
if (!parsedCookies) {
|
|
574
672
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -576,7 +674,6 @@ export function createRequestContext<TEnv>(
|
|
|
576
674
|
return parsedCookies;
|
|
577
675
|
};
|
|
578
676
|
|
|
579
|
-
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
580
677
|
let responseCookieCache: Map<string, string | null> | null = null;
|
|
581
678
|
const getResponseCookies = (): Map<string, string | null> => {
|
|
582
679
|
if (!responseCookieCache) {
|
|
@@ -588,8 +685,6 @@ export function createRequestContext<TEnv>(
|
|
|
588
685
|
responseCookieCache = null;
|
|
589
686
|
};
|
|
590
687
|
|
|
591
|
-
// Guard: throw if a response-level side effect is called inside a cache() scope.
|
|
592
|
-
// Uses ALS to detect the scope (set during segment resolution).
|
|
593
688
|
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
594
689
|
if (isInsideCacheScope()) {
|
|
595
690
|
throw new Error(
|
|
@@ -600,8 +695,7 @@ export function createRequestContext<TEnv>(
|
|
|
600
695
|
}
|
|
601
696
|
}
|
|
602
697
|
|
|
603
|
-
//
|
|
604
|
-
// 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).
|
|
605
699
|
const effectiveCookie = (name: string): string | undefined => {
|
|
606
700
|
const mutations = getResponseCookies();
|
|
607
701
|
if (mutations.has(name)) {
|
|
@@ -611,14 +705,11 @@ export function createRequestContext<TEnv>(
|
|
|
611
705
|
return getParsedCookies()[name];
|
|
612
706
|
};
|
|
613
707
|
|
|
614
|
-
// Theme helpers (only used when themeConfig is provided)
|
|
615
708
|
const getTheme = (): Theme | undefined => {
|
|
616
709
|
if (!themeConfig) return undefined;
|
|
617
710
|
|
|
618
|
-
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
619
711
|
const stored = effectiveCookie(themeConfig.storageKey);
|
|
620
712
|
if (stored) {
|
|
621
|
-
// Validate stored value
|
|
622
713
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
623
714
|
return "system";
|
|
624
715
|
}
|
|
@@ -632,15 +723,15 @@ export function createRequestContext<TEnv>(
|
|
|
632
723
|
const setTheme = (theme: Theme): void => {
|
|
633
724
|
if (!themeConfig) return;
|
|
634
725
|
|
|
635
|
-
//
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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);
|
|
640
732
|
return;
|
|
641
733
|
}
|
|
642
734
|
|
|
643
|
-
// Write to stub — effectiveCookie() will pick it up on next read
|
|
644
735
|
stubResponse.headers.append(
|
|
645
736
|
"Set-Cookie",
|
|
646
737
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
@@ -652,10 +743,8 @@ export function createRequestContext<TEnv>(
|
|
|
652
743
|
invalidateResponseCookieCache();
|
|
653
744
|
};
|
|
654
745
|
|
|
655
|
-
// Strip internal _rsc* params so userland sees a clean URL.
|
|
656
746
|
const cleanUrl = stripInternalParams(url);
|
|
657
747
|
|
|
658
|
-
// Build the context object first (without use), then add use
|
|
659
748
|
const ctx: RequestContext<TEnv> = {
|
|
660
749
|
env,
|
|
661
750
|
request,
|
|
@@ -741,6 +830,45 @@ export function createRequestContext<TEnv>(
|
|
|
741
830
|
stubResponse.headers.set(name, value);
|
|
742
831
|
},
|
|
743
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
|
+
|
|
744
872
|
setStatus(status: number): void {
|
|
745
873
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
746
874
|
assertNotInsideCacheScopeALS("setStatus");
|
|
@@ -757,27 +885,31 @@ export function createRequestContext<TEnv>(
|
|
|
757
885
|
});
|
|
758
886
|
},
|
|
759
887
|
|
|
760
|
-
// Placeholder - will be replaced below
|
|
761
888
|
use: null as any,
|
|
762
889
|
|
|
763
890
|
method: request.method,
|
|
764
891
|
|
|
765
892
|
_handleStore: handleStore,
|
|
893
|
+
_transitionWhen: [],
|
|
766
894
|
_cacheStore: cacheStore,
|
|
895
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
896
|
+
_requestTags: new Set<string>(),
|
|
767
897
|
_cacheProfiles: cacheProfiles,
|
|
768
898
|
|
|
769
899
|
waitUntil(fn: () => Promise<void>): void {
|
|
770
900
|
if (executionContext?.waitUntil) {
|
|
771
|
-
//
|
|
772
|
-
|
|
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));
|
|
773
906
|
} else {
|
|
774
|
-
|
|
775
|
-
fn().catch((err) =>
|
|
776
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
777
|
-
);
|
|
907
|
+
fireAndForgetWaitUntil(fn);
|
|
778
908
|
}
|
|
779
909
|
},
|
|
780
910
|
|
|
911
|
+
executionContext,
|
|
912
|
+
|
|
781
913
|
_onResponseCallbacks: [],
|
|
782
914
|
|
|
783
915
|
onResponse(callback: (response: Response) => Response): void {
|
|
@@ -786,7 +918,6 @@ export function createRequestContext<TEnv>(
|
|
|
786
918
|
this._onResponseCallbacks.push(callback);
|
|
787
919
|
},
|
|
788
920
|
|
|
789
|
-
// Theme properties (only set when themeConfig is provided)
|
|
790
921
|
get theme() {
|
|
791
922
|
return themeConfig ? getTheme() : undefined;
|
|
792
923
|
},
|
|
@@ -810,19 +941,17 @@ export function createRequestContext<TEnv>(
|
|
|
810
941
|
_reportedErrors: new WeakSet<object>(),
|
|
811
942
|
_metricsStore: undefined,
|
|
812
943
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
_resolveRenderBarrier: null as any, // set below
|
|
944
|
+
_renderBarrier: null as any,
|
|
945
|
+
_resolveRenderBarrier: null as any,
|
|
816
946
|
_renderBarrierSegmentOrder: undefined,
|
|
817
947
|
|
|
818
948
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
819
949
|
};
|
|
820
950
|
|
|
821
|
-
// Lazy
|
|
822
|
-
// calls rendered(). Requests that don't use rendered() pay zero cost.
|
|
951
|
+
// Lazy allocation: only create Promise when a loader calls rendered().
|
|
823
952
|
let barrierResolved = false;
|
|
824
953
|
let resolveBarrier: (() => void) | undefined;
|
|
825
|
-
ctx._renderBarrier = null as any;
|
|
954
|
+
ctx._renderBarrier = null as any;
|
|
826
955
|
ctx._resolveRenderBarrier = (
|
|
827
956
|
segments: Array<{ type: string; id: string }>,
|
|
828
957
|
) => {
|
|
@@ -832,21 +961,26 @@ export function createRequestContext<TEnv>(
|
|
|
832
961
|
.filter((s) => s.type !== "loader")
|
|
833
962
|
.map((s) => s.id);
|
|
834
963
|
ctx._renderBarrierSegmentOrder = segOrder;
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
ctx.
|
|
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
|
+
}
|
|
843
980
|
if (resolveBarrier) resolveBarrier();
|
|
844
981
|
};
|
|
845
982
|
Object.defineProperty(ctx, "_renderBarrier", {
|
|
846
983
|
get() {
|
|
847
|
-
// Barrier already resolved (cache/prerender hit) or first lazy access.
|
|
848
|
-
// Either way, replace the getter with a concrete value to avoid
|
|
849
|
-
// repeated Promise.resolve() allocations on subsequent reads.
|
|
850
984
|
const p = barrierResolved
|
|
851
985
|
? Promise.resolve()
|
|
852
986
|
: new Promise<void>((resolve) => {
|
|
@@ -862,32 +996,33 @@ export function createRequestContext<TEnv>(
|
|
|
862
996
|
configurable: true,
|
|
863
997
|
});
|
|
864
998
|
|
|
865
|
-
// Now create use() with access to ctx
|
|
866
999
|
ctx.use = createUseFunction({
|
|
867
1000
|
handleStore,
|
|
868
1001
|
loaderPromises,
|
|
869
1002
|
getContext: () => ctx,
|
|
870
1003
|
});
|
|
871
1004
|
|
|
872
|
-
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
873
1005
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
874
1006
|
return ctx;
|
|
875
1007
|
}
|
|
876
1008
|
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
*
|
|
882
|
-
|
|
883
|
-
|
|
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
|
+
}
|
|
884
1020
|
|
|
885
1021
|
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
886
1022
|
const result = new Map<string, string | null>();
|
|
887
1023
|
const setCookies = response.headers.getSetCookie();
|
|
888
1024
|
|
|
889
1025
|
for (const header of setCookies) {
|
|
890
|
-
// First segment before ';' is the name=value pair
|
|
891
1026
|
const semiIdx = header.indexOf(";");
|
|
892
1027
|
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
893
1028
|
const eqIdx = pair.indexOf("=");
|
|
@@ -899,49 +1034,22 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
899
1034
|
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
900
1035
|
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
901
1036
|
} catch {
|
|
902
|
-
// Malformed encoding — skip this entry
|
|
903
1037
|
continue;
|
|
904
1038
|
}
|
|
905
1039
|
|
|
906
|
-
|
|
907
|
-
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
1040
|
+
const isDeleted = isCookieDeletion(header);
|
|
908
1041
|
result.set(name, isDeleted ? null : value);
|
|
909
1042
|
}
|
|
910
1043
|
|
|
911
1044
|
return result;
|
|
912
1045
|
}
|
|
913
1046
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
cookieHeader: string | null,
|
|
919
|
-
): Record<string, string> {
|
|
920
|
-
if (!cookieHeader) return {};
|
|
921
|
-
|
|
922
|
-
const cookies: Record<string, string> = {};
|
|
923
|
-
const pairs = cookieHeader.split(";");
|
|
924
|
-
|
|
925
|
-
for (const pair of pairs) {
|
|
926
|
-
const [name, ...rest] = pair.trim().split("=");
|
|
927
|
-
if (name) {
|
|
928
|
-
const raw = rest.join("=");
|
|
929
|
-
try {
|
|
930
|
-
cookies[name] = decodeURIComponent(raw);
|
|
931
|
-
} catch {
|
|
932
|
-
// Malformed percent-encoded value (e.g. %zz, %2) - fall back to raw value
|
|
933
|
-
cookies[name] = raw;
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
return cookies;
|
|
939
|
-
}
|
|
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 };
|
|
940
1051
|
|
|
941
|
-
|
|
942
|
-
* Serialize a cookie for Set-Cookie header
|
|
943
|
-
*/
|
|
944
|
-
function serializeCookieValue(
|
|
1052
|
+
export function serializeCookieValue(
|
|
945
1053
|
name: string,
|
|
946
1054
|
value: string,
|
|
947
1055
|
options: CookieOptions = {},
|
|
@@ -968,20 +1076,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
968
1076
|
getContext: () => RequestContext<TEnv>;
|
|
969
1077
|
}
|
|
970
1078
|
|
|
971
|
-
/**
|
|
972
|
-
* Create the use() function for loader and handle composition.
|
|
973
|
-
*
|
|
974
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
975
|
-
* - For loaders: executes and memoizes loader functions
|
|
976
|
-
* - For handles: returns a push function to add handle data
|
|
977
|
-
*/
|
|
978
1079
|
export function createUseFunction<TEnv>(
|
|
979
1080
|
options: CreateUseFunctionOptions<TEnv>,
|
|
980
1081
|
): RequestContext["use"] {
|
|
981
1082
|
const { handleStore, loaderPromises, getContext } = options;
|
|
982
1083
|
|
|
983
1084
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
984
|
-
// Handle case: return a push function
|
|
985
1085
|
if (isHandle(item)) {
|
|
986
1086
|
const handle = item;
|
|
987
1087
|
const ctx = getContext();
|
|
@@ -994,30 +1094,24 @@ export function createUseFunction<TEnv>(
|
|
|
994
1094
|
);
|
|
995
1095
|
}
|
|
996
1096
|
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
// Push directly - promises will be serialized by RSC and streamed
|
|
1008
|
-
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
1009
|
-
};
|
|
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
|
+
);
|
|
1010
1107
|
}
|
|
1011
1108
|
|
|
1012
|
-
// Loader case
|
|
1013
1109
|
const loader = item as LoaderDefinition<any, any>;
|
|
1014
1110
|
|
|
1015
|
-
// Return cached promise if already started
|
|
1016
1111
|
if (loaderPromises.has(loader.$$id)) {
|
|
1017
1112
|
return loaderPromises.get(loader.$$id);
|
|
1018
1113
|
}
|
|
1019
1114
|
|
|
1020
|
-
// Get loader function - either from loader object or fetchable registry
|
|
1021
1115
|
let loaderFn = loader.fn;
|
|
1022
1116
|
if (!loaderFn) {
|
|
1023
1117
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -1034,21 +1128,34 @@ export function createUseFunction<TEnv>(
|
|
|
1034
1128
|
|
|
1035
1129
|
const ctx = getContext();
|
|
1036
1130
|
|
|
1037
|
-
//
|
|
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
|
+
|
|
1038
1143
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
1039
1144
|
params: ctx.params,
|
|
1040
1145
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
1041
1146
|
request: ctx.request,
|
|
1042
1147
|
searchParams: ctx.searchParams,
|
|
1043
|
-
search:
|
|
1148
|
+
search: loaderSearch,
|
|
1044
1149
|
pathname: ctx.pathname,
|
|
1045
1150
|
url: ctx.url,
|
|
1151
|
+
originalUrl: ctx.originalUrl,
|
|
1046
1152
|
env: ctx.env as any,
|
|
1153
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1154
|
+
executionContext: ctx.executionContext,
|
|
1047
1155
|
get: ctx.get as any,
|
|
1048
1156
|
use: (<TDep, TDepParams = any>(
|
|
1049
1157
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
1050
1158
|
): Promise<TDep> => {
|
|
1051
|
-
// Recursive call - will start dep loader if not already started
|
|
1052
1159
|
return ctx.use(dep);
|
|
1053
1160
|
}) as LoaderContext["use"],
|
|
1054
1161
|
method: "GET",
|
|
@@ -1067,12 +1174,14 @@ export function createUseFunction<TEnv>(
|
|
|
1067
1174
|
},
|
|
1068
1175
|
};
|
|
1069
1176
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
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
|
+
);
|
|
1074
1184
|
|
|
1075
|
-
// Memoize for subsequent calls
|
|
1076
1185
|
loaderPromises.set(loader.$$id, promise);
|
|
1077
1186
|
|
|
1078
1187
|
return promise;
|