@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17
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 +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- 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 +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -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 +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- 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/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/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- 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 +148 -19
- package/src/browser/navigation-client.ts +187 -67
- 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/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- 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 +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -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-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- 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 +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -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 +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- 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 +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- 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/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -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 +437 -274
- 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 -0
- package/src/route-definition/redirect.ts +52 -10
- 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 +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- 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 +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- 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 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +192 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +64 -25
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +348 -128
- package/src/ssr/index.tsx +23 -15
- 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 +618 -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 +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -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 +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -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 +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- 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 +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- 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 +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- 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 +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- 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/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 +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- 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/router/middleware-cookies.ts +0 -55
|
@@ -11,7 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
14
|
+
import type { CacheErrorCategory } from "../cache/cache-error.js";
|
|
14
15
|
import type { CookieOptions } from "../router/middleware.js";
|
|
16
|
+
import {
|
|
17
|
+
KEEP_CACHE_HEADER,
|
|
18
|
+
getRawCookieValue,
|
|
19
|
+
mintStateValue,
|
|
20
|
+
serializeStateCookie,
|
|
21
|
+
} from "../browser/cookie-name.js";
|
|
15
22
|
import type { LoaderDefinition, LoaderContext } from "../types.js";
|
|
16
23
|
import type { ScopedReverseFunction } from "../reverse.js";
|
|
17
24
|
import type {
|
|
@@ -20,17 +27,34 @@ import type {
|
|
|
20
27
|
DefaultRouteName,
|
|
21
28
|
} from "../types/global-namespace.js";
|
|
22
29
|
import type { Handle } from "../handle.js";
|
|
23
|
-
import {
|
|
24
|
-
|
|
30
|
+
import {
|
|
31
|
+
type ContextVar,
|
|
32
|
+
contextGet,
|
|
33
|
+
contextSet,
|
|
34
|
+
isNonCacheable,
|
|
35
|
+
} from "../context-var.js";
|
|
36
|
+
import {
|
|
37
|
+
createHandleStore,
|
|
38
|
+
buildHandleSnapshot,
|
|
39
|
+
type HandleStore,
|
|
40
|
+
type HandleData,
|
|
41
|
+
} from "./handle-store.js";
|
|
25
42
|
import { isHandle } from "../handle.js";
|
|
43
|
+
import { withDefer } from "../defer.js";
|
|
26
44
|
import { track, type MetricsStore } from "./context.js";
|
|
27
45
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
28
46
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
29
47
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
48
|
+
import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
|
|
49
|
+
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
30
50
|
import { THEME_COOKIE } from "../theme/constants.js";
|
|
31
51
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
32
52
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
33
|
-
import {
|
|
53
|
+
import { isInsideCacheScope } from "./context.js";
|
|
54
|
+
import {
|
|
55
|
+
createReverseFunction,
|
|
56
|
+
stripInternalParams,
|
|
57
|
+
} from "../router/handler-context.js";
|
|
34
58
|
import { getGlobalRouteMap, isRouteRootScoped } from "../route-map-builder.js";
|
|
35
59
|
import { invariant } from "../errors.js";
|
|
36
60
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
@@ -44,24 +68,9 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
44
68
|
export interface RequestContext<
|
|
45
69
|
TEnv = DefaultEnv,
|
|
46
70
|
TParams = Record<string, string>,
|
|
47
|
-
> {
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
/** Original HTTP request */
|
|
51
|
-
request: Request;
|
|
52
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
53
|
-
url: URL;
|
|
54
|
-
/**
|
|
55
|
-
* The original request URL with all parameters intact, including
|
|
56
|
-
* internal `_rsc*` transport params.
|
|
57
|
-
*/
|
|
58
|
-
originalUrl: URL;
|
|
59
|
-
/** URL pathname */
|
|
60
|
-
pathname: string;
|
|
61
|
-
/** URL search params (system params like _rsc* are NOT filtered here) */
|
|
62
|
-
searchParams: URLSearchParams;
|
|
63
|
-
/** Variables set by middleware (same as ctx.var) */
|
|
64
|
-
var: Record<string, any>;
|
|
71
|
+
> extends RequestScope<TEnv> {
|
|
72
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
73
|
+
_variables: Record<string, any>;
|
|
65
74
|
/** Get a variable set by middleware */
|
|
66
75
|
get: {
|
|
67
76
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -69,8 +78,12 @@ export interface RequestContext<
|
|
|
69
78
|
};
|
|
70
79
|
/** Set a variable (shared with middleware and handlers) */
|
|
71
80
|
set: {
|
|
72
|
-
<T>(
|
|
73
|
-
|
|
81
|
+
<T>(
|
|
82
|
+
contextVar: ContextVar<T>,
|
|
83
|
+
value: T,
|
|
84
|
+
options?: { cache?: boolean },
|
|
85
|
+
): void;
|
|
86
|
+
<K extends string>(key: K, value: any, options?: { cache?: boolean }): void;
|
|
74
87
|
};
|
|
75
88
|
/**
|
|
76
89
|
* Route params (populated after route matching)
|
|
@@ -95,6 +108,12 @@ export interface RequestContext<
|
|
|
95
108
|
header(name: string, value: string): void;
|
|
96
109
|
/** Set the response status code */
|
|
97
110
|
setStatus(status: number): void;
|
|
111
|
+
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
112
|
+
_setStatus(status: number): void;
|
|
113
|
+
/** @internal Rotate the rango state cookie (server seat of invalidateClientCache). */
|
|
114
|
+
_rotateStateCookie(): void;
|
|
115
|
+
/** @internal Set the keepClientCache() directive header on the response. */
|
|
116
|
+
_setKeepCacheDirective(): void;
|
|
98
117
|
|
|
99
118
|
/**
|
|
100
119
|
* Access loader data or push handle data.
|
|
@@ -133,26 +152,31 @@ export interface RequestContext<
|
|
|
133
152
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
134
153
|
_cacheStore?: SegmentCacheStore;
|
|
135
154
|
|
|
155
|
+
/**
|
|
156
|
+
* @internal Handler-owned registry of explicit per-scope stores from
|
|
157
|
+
* cache({ store }). Created once per createRSCHandler() and threaded into
|
|
158
|
+
* every request context, so it accumulates every explicit store the handler
|
|
159
|
+
* resolves. updateTag()/revalidateTag() iterate this set plus _cacheStore to
|
|
160
|
+
* reach every store that may hold tagged entries. The app-level store is not
|
|
161
|
+
* added here (it is always reachable via _cacheStore).
|
|
162
|
+
*/
|
|
163
|
+
_explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @internal Union of every cache tag resolved while producing this request's
|
|
167
|
+
* response (from cache({ tags }), runtime cacheTag(), and loader cache tags).
|
|
168
|
+
* Populated at the tag-resolution sites via recordRequestTags(). Read by the
|
|
169
|
+
* document cache middleware so a full-page entry is tagged with everything its
|
|
170
|
+
* content used and can therefore be invalidated by updateTag()/revalidateTag().
|
|
171
|
+
*/
|
|
172
|
+
_requestTags: Set<string>;
|
|
173
|
+
|
|
136
174
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
137
175
|
_cacheProfiles?: Record<
|
|
138
176
|
string,
|
|
139
177
|
import("../cache/profile-registry.js").CacheProfile
|
|
140
178
|
>;
|
|
141
179
|
|
|
142
|
-
/**
|
|
143
|
-
* Schedule work to run after the response is sent.
|
|
144
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
145
|
-
* On Node.js, runs as fire-and-forget.
|
|
146
|
-
*
|
|
147
|
-
* @example
|
|
148
|
-
* ```typescript
|
|
149
|
-
* ctx.waitUntil(async () => {
|
|
150
|
-
* await cacheStore.set(key, data, ttl);
|
|
151
|
-
* });
|
|
152
|
-
* ```
|
|
153
|
-
*/
|
|
154
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
155
|
-
|
|
156
180
|
/**
|
|
157
181
|
* Register a callback to run when the response is created.
|
|
158
182
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -256,6 +280,68 @@ export interface RequestContext<
|
|
|
256
280
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
257
281
|
_prevRouteKey?: string;
|
|
258
282
|
|
|
283
|
+
/**
|
|
284
|
+
* @internal Render barrier for experimental `rendered()` API.
|
|
285
|
+
* Resolves when all non-loader segments have settled and handle data
|
|
286
|
+
* is available. Used by DSL loaders that call `ctx.rendered()`.
|
|
287
|
+
*/
|
|
288
|
+
_renderBarrier: Promise<void>;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @internal Resolve the render barrier. Accepts resolved segments, filters
|
|
292
|
+
* out loaders, and captures non-loader segment IDs as the handle ordering.
|
|
293
|
+
* Called after segment resolution (fresh) or handle replay (cache/prerender).
|
|
294
|
+
*/
|
|
295
|
+
_resolveRenderBarrier: (
|
|
296
|
+
segments: Array<{ type: string; id: string }>,
|
|
297
|
+
) => void;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @internal Segment order at barrier resolution time, used by loader
|
|
301
|
+
* ctx.use(handle) to collect handle data in correct order.
|
|
302
|
+
*/
|
|
303
|
+
_renderBarrierSegmentOrder?: string[];
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @internal Set to true when the matched entry tree contains any `loading()`
|
|
307
|
+
* entries (streaming). On a streaming tree rendered() waits for the streaming
|
|
308
|
+
* handlers to settle (via handleStore.settled) before resolving, and the
|
|
309
|
+
* deadlock guard state is kept live until that wait completes.
|
|
310
|
+
*/
|
|
311
|
+
_treeHasStreaming?: boolean;
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* @internal Loader IDs that have called rendered() and are waiting for the
|
|
315
|
+
* barrier. Used to detect deadlocks when a handler tries to await the same
|
|
316
|
+
* loader via ctx.use(Loader).
|
|
317
|
+
*/
|
|
318
|
+
_renderBarrierWaiters?: Set<string>;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @internal Loader IDs that handlers have started awaiting via ctx.use().
|
|
322
|
+
* Used for bidirectional deadlock detection: if a loader later calls
|
|
323
|
+
* rendered() and a handler already awaits it, we can detect the deadlock.
|
|
324
|
+
*/
|
|
325
|
+
_handlerLoaderDeps?: Set<string>;
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @internal Cached HandleData snapshot built at barrier resolution time.
|
|
329
|
+
* Avoids rebuilding the snapshot on every loader ctx.use(handle) call.
|
|
330
|
+
*/
|
|
331
|
+
_renderBarrierHandleSnapshot?: HandleData;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* @internal The deadlock guard window is closed (no further handler-awaits-
|
|
335
|
+
* loader cycle is possible). For non-streaming trees this is set when the
|
|
336
|
+
* barrier resolves. For streaming trees the window stays open until
|
|
337
|
+
* handleStore.settled — rendered() keeps waiting past the barrier and a
|
|
338
|
+
* loading() handler can still resume and await a still-waiting loader — so it
|
|
339
|
+
* is set only after settled. The guard (loader-resolution `setupLoaderAccess`)
|
|
340
|
+
* reads this instead of `_renderBarrierSegmentOrder` so it does not go blind
|
|
341
|
+
* during the streaming settle wait.
|
|
342
|
+
*/
|
|
343
|
+
_renderBarrierGuardClosed?: boolean;
|
|
344
|
+
|
|
259
345
|
/** @internal Per-request error dedup set for onError reporting */
|
|
260
346
|
_reportedErrors: WeakSet<object>;
|
|
261
347
|
|
|
@@ -263,15 +349,37 @@ export interface RequestContext<
|
|
|
263
349
|
* @internal Report a non-fatal background error through the router's
|
|
264
350
|
* onError callback. Wired by the RSC handler / router during request
|
|
265
351
|
* creation. Cache-runtime and other subsystems call this to surface
|
|
266
|
-
* errors without failing the response.
|
|
352
|
+
* errors without failing the response. `category` is surfaced to consumers as
|
|
353
|
+
* `metadata.category` on the onError context (phase `cache`).
|
|
267
354
|
*/
|
|
268
|
-
_reportBackgroundError?: (
|
|
355
|
+
_reportBackgroundError?: (
|
|
356
|
+
error: unknown,
|
|
357
|
+
category: CacheErrorCategory,
|
|
358
|
+
) => void;
|
|
269
359
|
|
|
270
360
|
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
271
361
|
_debugPerformance?: boolean;
|
|
272
362
|
|
|
273
363
|
/** @internal Request-scoped performance metrics store */
|
|
274
364
|
_metricsStore?: MetricsStore;
|
|
365
|
+
|
|
366
|
+
/** @internal Router basename for this request (used by redirect()) */
|
|
367
|
+
_basename?: string;
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* @internal RouteSnapshot from classifyRequest, reused by match/matchPartial
|
|
371
|
+
* to avoid a second resolveRoute call. Cleared on HMR invalidation.
|
|
372
|
+
*/
|
|
373
|
+
_classifiedRoute?: import("../router/route-snapshot.js").RouteSnapshot;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* @internal Coarse route-level cache signal for the X-Rango-Cache debug
|
|
377
|
+
* header. Populated by match/matchPartial only when the debug cache signal
|
|
378
|
+
* gate is enabled (debugCacheSignal option or RANGO_TEST_SIGNALS=1). Read by
|
|
379
|
+
* the response-finalization path (createResponseWithMergedHeaders). Undefined
|
|
380
|
+
* when the gate is off, so no header is emitted.
|
|
381
|
+
*/
|
|
382
|
+
_cacheSignal?: import("../router/telemetry.js").CacheSegmentSignal[];
|
|
275
383
|
}
|
|
276
384
|
|
|
277
385
|
/**
|
|
@@ -291,6 +399,8 @@ export type PublicRequestContext<
|
|
|
291
399
|
| "deleteCookie"
|
|
292
400
|
| "_handleStore"
|
|
293
401
|
| "_cacheStore"
|
|
402
|
+
| "_explicitTaggedStores"
|
|
403
|
+
| "_requestTags"
|
|
294
404
|
| "_cacheProfiles"
|
|
295
405
|
| "_onResponseCallbacks"
|
|
296
406
|
| "_themeConfig"
|
|
@@ -298,9 +408,24 @@ export type PublicRequestContext<
|
|
|
298
408
|
| "_routeName"
|
|
299
409
|
| "_prevRouteKey"
|
|
300
410
|
| "_reportedErrors"
|
|
411
|
+
| "_renderBarrier"
|
|
412
|
+
| "_resolveRenderBarrier"
|
|
413
|
+
| "_renderBarrierSegmentOrder"
|
|
414
|
+
| "_treeHasStreaming"
|
|
415
|
+
| "_renderBarrierWaiters"
|
|
416
|
+
| "_handlerLoaderDeps"
|
|
417
|
+
| "_renderBarrierHandleSnapshot"
|
|
418
|
+
| "_renderBarrierGuardClosed"
|
|
301
419
|
| "_reportBackgroundError"
|
|
302
420
|
| "_debugPerformance"
|
|
303
421
|
| "_metricsStore"
|
|
422
|
+
| "_basename"
|
|
423
|
+
| "_setStatus"
|
|
424
|
+
| "_rotateStateCookie"
|
|
425
|
+
| "_setKeepCacheDirective"
|
|
426
|
+
| "_variables"
|
|
427
|
+
| "_classifiedRoute"
|
|
428
|
+
| "_cacheSignal"
|
|
304
429
|
| "res"
|
|
305
430
|
>;
|
|
306
431
|
|
|
@@ -352,6 +477,7 @@ export function _getRequestContext<TEnv = DefaultEnv>():
|
|
|
352
477
|
export function setRequestContextParams(
|
|
353
478
|
params: Record<string, string>,
|
|
354
479
|
routeName?: string,
|
|
480
|
+
routeMap?: Record<string, string>,
|
|
355
481
|
): void {
|
|
356
482
|
const ctx = requestContextStorage.getStore();
|
|
357
483
|
if (ctx) {
|
|
@@ -364,9 +490,13 @@ export function setRequestContextParams(
|
|
|
364
490
|
: undefined
|
|
365
491
|
) as DefaultRouteName | undefined;
|
|
366
492
|
}
|
|
367
|
-
// Update reverse with scoped resolution now that route is known
|
|
493
|
+
// Update reverse with scoped resolution now that route is known. Production
|
|
494
|
+
// omits routeMap and uses the global map (routes are registered globally);
|
|
495
|
+
// the testing primitives (renderToFlightString/renderServerTree) pass a
|
|
496
|
+
// scoped routeMap so `ctx.reverse` is not order-dependent on whatever router
|
|
497
|
+
// registered last.
|
|
368
498
|
ctx.reverse = createReverseFunction(
|
|
369
|
-
getGlobalRouteMap(),
|
|
499
|
+
routeMap ?? getGlobalRouteMap(),
|
|
370
500
|
routeName,
|
|
371
501
|
params,
|
|
372
502
|
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
@@ -410,13 +540,7 @@ export function requireRequestContext<
|
|
|
410
540
|
return getRequestContext<TEnv>();
|
|
411
541
|
}
|
|
412
542
|
|
|
413
|
-
|
|
414
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
415
|
-
*/
|
|
416
|
-
export interface ExecutionContext {
|
|
417
|
-
waitUntil(promise: Promise<any>): void;
|
|
418
|
-
passThroughOnException(): void;
|
|
419
|
-
}
|
|
543
|
+
export type { ExecutionContext };
|
|
420
544
|
|
|
421
545
|
/**
|
|
422
546
|
* Options for creating a request context
|
|
@@ -430,6 +554,11 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
430
554
|
initialResponse?: Response;
|
|
431
555
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
432
556
|
cacheStore?: SegmentCacheStore;
|
|
557
|
+
/**
|
|
558
|
+
* Handler-owned registry of explicit per-scope stores for cross-store tag
|
|
559
|
+
* invalidation. Created once per handler, reused across requests.
|
|
560
|
+
*/
|
|
561
|
+
explicitTaggedStores?: Set<SegmentCacheStore>;
|
|
433
562
|
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
434
563
|
cacheProfiles?: Record<
|
|
435
564
|
string,
|
|
@@ -439,6 +568,10 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
439
568
|
executionContext?: ExecutionContext;
|
|
440
569
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
441
570
|
themeConfig?: ResolvedThemeConfig | null;
|
|
571
|
+
/** Resolved rango state cookie name, for the server seat of invalidateClientCache(). */
|
|
572
|
+
stateCookieName?: string;
|
|
573
|
+
/** Build version, used as the prefix of a server-rotated rango state value. */
|
|
574
|
+
version?: string;
|
|
442
575
|
}
|
|
443
576
|
|
|
444
577
|
/**
|
|
@@ -459,15 +592,17 @@ export function createRequestContext<TEnv>(
|
|
|
459
592
|
variables,
|
|
460
593
|
initialResponse,
|
|
461
594
|
cacheStore,
|
|
595
|
+
explicitTaggedStores,
|
|
462
596
|
cacheProfiles,
|
|
463
597
|
executionContext,
|
|
464
598
|
themeConfig,
|
|
599
|
+
stateCookieName,
|
|
600
|
+
version: stateVersion,
|
|
465
601
|
} = options;
|
|
466
602
|
const cookieHeader = request.headers.get("Cookie");
|
|
603
|
+
let rangoStateRotated = false;
|
|
467
604
|
let parsedCookies: Record<string, string> | null = null;
|
|
468
605
|
|
|
469
|
-
// Create stub response for collecting headers/cookies.
|
|
470
|
-
// All cookie/header mutations go here; cookie reads derive from it.
|
|
471
606
|
let stubResponse = initialResponse
|
|
472
607
|
? new Response(null, {
|
|
473
608
|
status: initialResponse.status,
|
|
@@ -476,11 +611,9 @@ export function createRequestContext<TEnv>(
|
|
|
476
611
|
})
|
|
477
612
|
: new Response(null, { status: 200 });
|
|
478
613
|
|
|
479
|
-
// Create handle store and loader memoization for this request
|
|
480
614
|
const handleStore = createHandleStore();
|
|
481
615
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
482
616
|
|
|
483
|
-
// Lazy parse cookies from the original Cookie header
|
|
484
617
|
const getParsedCookies = (): Record<string, string> => {
|
|
485
618
|
if (!parsedCookies) {
|
|
486
619
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -488,7 +621,6 @@ export function createRequestContext<TEnv>(
|
|
|
488
621
|
return parsedCookies;
|
|
489
622
|
};
|
|
490
623
|
|
|
491
|
-
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
492
624
|
let responseCookieCache: Map<string, string | null> | null = null;
|
|
493
625
|
const getResponseCookies = (): Map<string, string | null> => {
|
|
494
626
|
if (!responseCookieCache) {
|
|
@@ -500,8 +632,17 @@ export function createRequestContext<TEnv>(
|
|
|
500
632
|
responseCookieCache = null;
|
|
501
633
|
};
|
|
502
634
|
|
|
503
|
-
|
|
504
|
-
|
|
635
|
+
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
636
|
+
if (isInsideCacheScope()) {
|
|
637
|
+
throw new Error(
|
|
638
|
+
`ctx.${methodName}() cannot be called inside a cache() boundary. ` +
|
|
639
|
+
`On cache hit the handler is skipped, so this side effect would be lost. ` +
|
|
640
|
+
`Move ctx.${methodName}() to a middleware or layout outside the cache() scope.`,
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Response stub Set-Cookie wins, then original header (source of truth for mutations).
|
|
505
646
|
const effectiveCookie = (name: string): string | undefined => {
|
|
506
647
|
const mutations = getResponseCookies();
|
|
507
648
|
if (mutations.has(name)) {
|
|
@@ -511,14 +652,11 @@ export function createRequestContext<TEnv>(
|
|
|
511
652
|
return getParsedCookies()[name];
|
|
512
653
|
};
|
|
513
654
|
|
|
514
|
-
// Theme helpers (only used when themeConfig is provided)
|
|
515
655
|
const getTheme = (): Theme | undefined => {
|
|
516
656
|
if (!themeConfig) return undefined;
|
|
517
657
|
|
|
518
|
-
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
519
658
|
const stored = effectiveCookie(themeConfig.storageKey);
|
|
520
659
|
if (stored) {
|
|
521
|
-
// Validate stored value
|
|
522
660
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
523
661
|
return "system";
|
|
524
662
|
}
|
|
@@ -532,7 +670,6 @@ export function createRequestContext<TEnv>(
|
|
|
532
670
|
const setTheme = (theme: Theme): void => {
|
|
533
671
|
if (!themeConfig) return;
|
|
534
672
|
|
|
535
|
-
// Validate theme value
|
|
536
673
|
if (theme !== "system" && !themeConfig.themes.includes(theme)) {
|
|
537
674
|
console.warn(
|
|
538
675
|
`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`,
|
|
@@ -540,7 +677,6 @@ export function createRequestContext<TEnv>(
|
|
|
540
677
|
return;
|
|
541
678
|
}
|
|
542
679
|
|
|
543
|
-
// Write to stub — effectiveCookie() will pick it up on next read
|
|
544
680
|
stubResponse.headers.append(
|
|
545
681
|
"Set-Cookie",
|
|
546
682
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
@@ -552,20 +688,29 @@ export function createRequestContext<TEnv>(
|
|
|
552
688
|
invalidateResponseCookieCache();
|
|
553
689
|
};
|
|
554
690
|
|
|
555
|
-
|
|
691
|
+
const cleanUrl = stripInternalParams(url);
|
|
692
|
+
|
|
556
693
|
const ctx: RequestContext<TEnv> = {
|
|
557
694
|
env,
|
|
558
695
|
request,
|
|
559
|
-
url,
|
|
696
|
+
url: cleanUrl,
|
|
560
697
|
originalUrl: new URL(request.url),
|
|
561
698
|
pathname: url.pathname,
|
|
562
|
-
searchParams:
|
|
563
|
-
|
|
564
|
-
get: ((keyOrVar: any) =>
|
|
565
|
-
|
|
566
|
-
|
|
699
|
+
searchParams: cleanUrl.searchParams,
|
|
700
|
+
_variables: variables,
|
|
701
|
+
get: ((keyOrVar: any) => {
|
|
702
|
+
if (isNonCacheable(variables, keyOrVar) && isInsideCacheScope()) {
|
|
703
|
+
throw new Error(
|
|
704
|
+
`ctx.get() for a non-cacheable variable cannot be called inside a cache() boundary. ` +
|
|
705
|
+
`The variable was created with { cache: false } or set with { cache: false }, ` +
|
|
706
|
+
`and its value would be stale on cache hit. Move the read outside the cached scope.`,
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
return contextGet(variables, keyOrVar);
|
|
710
|
+
}) as RequestContext<TEnv>["get"],
|
|
711
|
+
set: ((keyOrVar: any, value: any, options?: any) => {
|
|
567
712
|
assertNotInsideCacheExec(ctx, "set");
|
|
568
|
-
contextSet(variables, keyOrVar, value);
|
|
713
|
+
contextSet(variables, keyOrVar, value, options);
|
|
569
714
|
}) as RequestContext<TEnv>["set"],
|
|
570
715
|
params: {} as Record<string, string>,
|
|
571
716
|
|
|
@@ -603,6 +748,7 @@ export function createRequestContext<TEnv>(
|
|
|
603
748
|
|
|
604
749
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
605
750
|
assertNotInsideCacheExec(ctx, "setCookie");
|
|
751
|
+
assertNotInsideCacheScopeALS("setCookie");
|
|
606
752
|
stubResponse.headers.append(
|
|
607
753
|
"Set-Cookie",
|
|
608
754
|
serializeCookieValue(name, value, options),
|
|
@@ -615,6 +761,7 @@ export function createRequestContext<TEnv>(
|
|
|
615
761
|
options?: Pick<CookieOptions, "domain" | "path">,
|
|
616
762
|
): void {
|
|
617
763
|
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
764
|
+
assertNotInsideCacheScopeALS("deleteCookie");
|
|
618
765
|
stubResponse.headers.append(
|
|
619
766
|
"Set-Cookie",
|
|
620
767
|
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
@@ -624,48 +771,93 @@ export function createRequestContext<TEnv>(
|
|
|
624
771
|
|
|
625
772
|
header(name: string, value: string): void {
|
|
626
773
|
assertNotInsideCacheExec(ctx, "header");
|
|
774
|
+
assertNotInsideCacheScopeALS("header");
|
|
627
775
|
stubResponse.headers.set(name, value);
|
|
628
776
|
},
|
|
629
777
|
|
|
778
|
+
// Rotate the rango state cookie for the responding client (the server seat
|
|
779
|
+
// of invalidateClientCache). Writes ONE Set-Cookie per request with the
|
|
780
|
+
// value {version}:{timestamp}; the `:` stays raw (the cookie-name.ts
|
|
781
|
+
// serializer), not the URL-encoded form serializeCookieValue would produce.
|
|
782
|
+
// The timestamp is strictly greater than the client's current one (inbound
|
|
783
|
+
// X-Rango-State), so a same-millisecond server rotation still differs from
|
|
784
|
+
// the client value and the divergence observer fires.
|
|
785
|
+
_rotateStateCookie(): void {
|
|
786
|
+
if (rangoStateRotated) return;
|
|
787
|
+
rangoStateRotated = true;
|
|
788
|
+
if (!stateCookieName) return;
|
|
789
|
+
// The client's current value, for the monotonic guard: prefer the
|
|
790
|
+
// X-Rango-State header (router navigation/prefetch fetches send it), but
|
|
791
|
+
// fall back to the request's rango state cookie — action POSTs / plain
|
|
792
|
+
// app fetch()s carry no router header yet DO send the cookie. Without the
|
|
793
|
+
// fallback, prevTs stays 0 and a same-ms mint can equal the client value,
|
|
794
|
+
// leaving the divergence observer silent. `|| null` so an empty header
|
|
795
|
+
// ('' from proxy normalization) falls through instead of short-circuiting.
|
|
796
|
+
// getRawCookieValue reads the cookie undecoded (the wire value
|
|
797
|
+
// decodeStateValue decodes exactly once) AND is the same parser the client
|
|
798
|
+
// mirror uses, so both seats read the same jar entry.
|
|
799
|
+
const prevRaw =
|
|
800
|
+
(request.headers.get("x-rango-state") || null) ??
|
|
801
|
+
getRawCookieValue(cookieHeader, stateCookieName);
|
|
802
|
+
const value = mintStateValue(stateVersion ?? "0", prevRaw);
|
|
803
|
+
stubResponse.headers.append(
|
|
804
|
+
"Set-Cookie",
|
|
805
|
+
serializeStateCookie(stateCookieName, value, url.protocol === "https:"),
|
|
806
|
+
);
|
|
807
|
+
invalidateResponseCookieCache();
|
|
808
|
+
},
|
|
809
|
+
|
|
810
|
+
// Set the keepClientCache() directive header. The action bridge reads it on
|
|
811
|
+
// the response and suppresses its automatic invalidation. `.set` makes this
|
|
812
|
+
// idempotent (one header regardless of call count).
|
|
813
|
+
_setKeepCacheDirective(): void {
|
|
814
|
+
stubResponse.headers.set(KEEP_CACHE_HEADER, "1");
|
|
815
|
+
},
|
|
816
|
+
|
|
630
817
|
setStatus(status: number): void {
|
|
631
818
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
632
|
-
|
|
633
|
-
|
|
819
|
+
assertNotInsideCacheScopeALS("setStatus");
|
|
820
|
+
stubResponse = new Response(null, {
|
|
821
|
+
status,
|
|
822
|
+
headers: stubResponse.headers,
|
|
823
|
+
});
|
|
824
|
+
},
|
|
825
|
+
|
|
826
|
+
_setStatus(status: number): void {
|
|
634
827
|
stubResponse = new Response(null, {
|
|
635
828
|
status,
|
|
636
829
|
headers: stubResponse.headers,
|
|
637
830
|
});
|
|
638
831
|
},
|
|
639
832
|
|
|
640
|
-
// Placeholder - will be replaced below
|
|
641
833
|
use: null as any,
|
|
642
834
|
|
|
643
835
|
method: request.method,
|
|
644
836
|
|
|
645
837
|
_handleStore: handleStore,
|
|
646
838
|
_cacheStore: cacheStore,
|
|
839
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
840
|
+
_requestTags: new Set<string>(),
|
|
647
841
|
_cacheProfiles: cacheProfiles,
|
|
648
842
|
|
|
649
843
|
waitUntil(fn: () => Promise<void>): void {
|
|
650
844
|
if (executionContext?.waitUntil) {
|
|
651
|
-
// Cloudflare Workers: use native waitUntil
|
|
652
845
|
executionContext.waitUntil(fn());
|
|
653
846
|
} else {
|
|
654
|
-
|
|
655
|
-
fn().catch((err) =>
|
|
656
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
657
|
-
);
|
|
847
|
+
fireAndForgetWaitUntil(fn);
|
|
658
848
|
}
|
|
659
849
|
},
|
|
660
850
|
|
|
851
|
+
executionContext,
|
|
852
|
+
|
|
661
853
|
_onResponseCallbacks: [],
|
|
662
854
|
|
|
663
855
|
onResponse(callback: (response: Response) => Response): void {
|
|
664
856
|
assertNotInsideCacheExec(ctx, "onResponse");
|
|
857
|
+
assertNotInsideCacheScopeALS("onResponse");
|
|
665
858
|
this._onResponseCallbacks.push(callback);
|
|
666
859
|
},
|
|
667
860
|
|
|
668
|
-
// Theme properties (only set when themeConfig is provided)
|
|
669
861
|
get theme() {
|
|
670
862
|
return themeConfig ? getTheme() : undefined;
|
|
671
863
|
},
|
|
@@ -689,27 +881,71 @@ export function createRequestContext<TEnv>(
|
|
|
689
881
|
_reportedErrors: new WeakSet<object>(),
|
|
690
882
|
_metricsStore: undefined,
|
|
691
883
|
|
|
884
|
+
_renderBarrier: null as any,
|
|
885
|
+
_resolveRenderBarrier: null as any,
|
|
886
|
+
_renderBarrierSegmentOrder: undefined,
|
|
887
|
+
|
|
692
888
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
693
889
|
};
|
|
694
890
|
|
|
695
|
-
//
|
|
891
|
+
// Lazy allocation: only create Promise when a loader calls rendered().
|
|
892
|
+
let barrierResolved = false;
|
|
893
|
+
let resolveBarrier: (() => void) | undefined;
|
|
894
|
+
ctx._renderBarrier = null as any;
|
|
895
|
+
ctx._resolveRenderBarrier = (
|
|
896
|
+
segments: Array<{ type: string; id: string }>,
|
|
897
|
+
) => {
|
|
898
|
+
if (barrierResolved) return;
|
|
899
|
+
barrierResolved = true;
|
|
900
|
+
const segOrder = segments
|
|
901
|
+
.filter((s) => s.type !== "loader")
|
|
902
|
+
.map((s) => s.id);
|
|
903
|
+
ctx._renderBarrierSegmentOrder = segOrder;
|
|
904
|
+
|
|
905
|
+
const closeGuard = () => {
|
|
906
|
+
ctx._renderBarrierWaiters = undefined;
|
|
907
|
+
ctx._handlerLoaderDeps = undefined;
|
|
908
|
+
ctx._renderBarrierGuardClosed = true;
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
if (ctx._treeHasStreaming) {
|
|
912
|
+
handleStore.settled.then(closeGuard);
|
|
913
|
+
} else {
|
|
914
|
+
ctx._renderBarrierHandleSnapshot = buildHandleSnapshot(
|
|
915
|
+
handleStore,
|
|
916
|
+
segOrder,
|
|
917
|
+
);
|
|
918
|
+
closeGuard();
|
|
919
|
+
}
|
|
920
|
+
if (resolveBarrier) resolveBarrier();
|
|
921
|
+
};
|
|
922
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
923
|
+
get() {
|
|
924
|
+
const p = barrierResolved
|
|
925
|
+
? Promise.resolve()
|
|
926
|
+
: new Promise<void>((resolve) => {
|
|
927
|
+
resolveBarrier = resolve;
|
|
928
|
+
});
|
|
929
|
+
Object.defineProperty(ctx, "_renderBarrier", {
|
|
930
|
+
value: p,
|
|
931
|
+
writable: false,
|
|
932
|
+
configurable: false,
|
|
933
|
+
});
|
|
934
|
+
return p;
|
|
935
|
+
},
|
|
936
|
+
configurable: true,
|
|
937
|
+
});
|
|
938
|
+
|
|
696
939
|
ctx.use = createUseFunction({
|
|
697
940
|
handleStore,
|
|
698
941
|
loaderPromises,
|
|
699
942
|
getContext: () => ctx,
|
|
700
943
|
});
|
|
701
944
|
|
|
702
|
-
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
703
945
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
704
946
|
return ctx;
|
|
705
947
|
}
|
|
706
948
|
|
|
707
|
-
/**
|
|
708
|
-
* Parse Set-Cookie headers from a response into effective cookie state.
|
|
709
|
-
* Returns a map of cookie name -> value (string) or name -> null (deleted).
|
|
710
|
-
* Last-write-wins: later Set-Cookie entries for the same name overwrite earlier ones.
|
|
711
|
-
* Max-Age=0 is treated as a delete.
|
|
712
|
-
*/
|
|
713
949
|
const MAX_AGE_ZERO_RE = /;\s*Max-Age\s*=\s*0/i;
|
|
714
950
|
|
|
715
951
|
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
@@ -717,7 +953,6 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
717
953
|
const setCookies = response.headers.getSetCookie();
|
|
718
954
|
|
|
719
955
|
for (const header of setCookies) {
|
|
720
|
-
// First segment before ';' is the name=value pair
|
|
721
956
|
const semiIdx = header.indexOf(";");
|
|
722
957
|
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
723
958
|
const eqIdx = pair.indexOf("=");
|
|
@@ -729,11 +964,9 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
729
964
|
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
730
965
|
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
731
966
|
} catch {
|
|
732
|
-
// Malformed encoding — skip this entry
|
|
733
967
|
continue;
|
|
734
968
|
}
|
|
735
969
|
|
|
736
|
-
// Max-Age=0 means the cookie is being deleted
|
|
737
970
|
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
738
971
|
result.set(name, isDeleted ? null : value);
|
|
739
972
|
}
|
|
@@ -741,10 +974,10 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
741
974
|
return result;
|
|
742
975
|
}
|
|
743
976
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
function parseCookiesFromHeader(
|
|
977
|
+
// Exported for unit tests; the canonical cookie parse/serialize lives here
|
|
978
|
+
// (a duplicate copy in middleware-cookies.ts was removed). Not part of the
|
|
979
|
+
// public export surface.
|
|
980
|
+
export function parseCookiesFromHeader(
|
|
748
981
|
cookieHeader: string | null,
|
|
749
982
|
): Record<string, string> {
|
|
750
983
|
if (!cookieHeader) return {};
|
|
@@ -759,7 +992,7 @@ function parseCookiesFromHeader(
|
|
|
759
992
|
try {
|
|
760
993
|
cookies[name] = decodeURIComponent(raw);
|
|
761
994
|
} catch {
|
|
762
|
-
// Malformed percent-
|
|
995
|
+
// Malformed percent-encoding: fall back to raw value
|
|
763
996
|
cookies[name] = raw;
|
|
764
997
|
}
|
|
765
998
|
}
|
|
@@ -768,10 +1001,7 @@ function parseCookiesFromHeader(
|
|
|
768
1001
|
return cookies;
|
|
769
1002
|
}
|
|
770
1003
|
|
|
771
|
-
|
|
772
|
-
* Serialize a cookie for Set-Cookie header
|
|
773
|
-
*/
|
|
774
|
-
function serializeCookieValue(
|
|
1004
|
+
export function serializeCookieValue(
|
|
775
1005
|
name: string,
|
|
776
1006
|
value: string,
|
|
777
1007
|
options: CookieOptions = {},
|
|
@@ -798,20 +1028,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
798
1028
|
getContext: () => RequestContext<TEnv>;
|
|
799
1029
|
}
|
|
800
1030
|
|
|
801
|
-
/**
|
|
802
|
-
* Create the use() function for loader and handle composition.
|
|
803
|
-
*
|
|
804
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
805
|
-
* - For loaders: executes and memoizes loader functions
|
|
806
|
-
* - For handles: returns a push function to add handle data
|
|
807
|
-
*/
|
|
808
1031
|
export function createUseFunction<TEnv>(
|
|
809
1032
|
options: CreateUseFunctionOptions<TEnv>,
|
|
810
1033
|
): RequestContext["use"] {
|
|
811
1034
|
const { handleStore, loaderPromises, getContext } = options;
|
|
812
1035
|
|
|
813
1036
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
814
|
-
// Handle case: return a push function
|
|
815
1037
|
if (isHandle(item)) {
|
|
816
1038
|
const handle = item;
|
|
817
1039
|
const ctx = getContext();
|
|
@@ -824,30 +1046,24 @@ export function createUseFunction<TEnv>(
|
|
|
824
1046
|
);
|
|
825
1047
|
}
|
|
826
1048
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
// Push directly - promises will be serialized by RSC and streamed
|
|
838
|
-
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
839
|
-
};
|
|
1049
|
+
return withDefer(
|
|
1050
|
+
(dataOrFn: unknown | Promise<unknown> | (() => Promise<unknown>)) => {
|
|
1051
|
+
const valueOrPromise =
|
|
1052
|
+
typeof dataOrFn === "function"
|
|
1053
|
+
? (dataOrFn as () => Promise<unknown>)()
|
|
1054
|
+
: dataOrFn;
|
|
1055
|
+
|
|
1056
|
+
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
1057
|
+
},
|
|
1058
|
+
);
|
|
840
1059
|
}
|
|
841
1060
|
|
|
842
|
-
// Loader case
|
|
843
1061
|
const loader = item as LoaderDefinition<any, any>;
|
|
844
1062
|
|
|
845
|
-
// Return cached promise if already started
|
|
846
1063
|
if (loaderPromises.has(loader.$$id)) {
|
|
847
1064
|
return loaderPromises.get(loader.$$id);
|
|
848
1065
|
}
|
|
849
1066
|
|
|
850
|
-
// Get loader function - either from loader object or fetchable registry
|
|
851
1067
|
let loaderFn = loader.fn;
|
|
852
1068
|
if (!loaderFn) {
|
|
853
1069
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -864,7 +1080,6 @@ export function createUseFunction<TEnv>(
|
|
|
864
1080
|
|
|
865
1081
|
const ctx = getContext();
|
|
866
1082
|
|
|
867
|
-
// Create loader context with recursive use() support
|
|
868
1083
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
869
1084
|
params: ctx.params,
|
|
870
1085
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
@@ -873,15 +1088,16 @@ export function createUseFunction<TEnv>(
|
|
|
873
1088
|
search: (ctx as any).search ?? {},
|
|
874
1089
|
pathname: ctx.pathname,
|
|
875
1090
|
url: ctx.url,
|
|
1091
|
+
originalUrl: ctx.originalUrl,
|
|
876
1092
|
env: ctx.env as any,
|
|
877
|
-
|
|
1093
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1094
|
+
executionContext: ctx.executionContext,
|
|
878
1095
|
get: ctx.get as any,
|
|
879
|
-
use: <TDep, TDepParams = any>(
|
|
1096
|
+
use: (<TDep, TDepParams = any>(
|
|
880
1097
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
881
1098
|
): Promise<TDep> => {
|
|
882
|
-
// Recursive call - will start dep loader if not already started
|
|
883
1099
|
return ctx.use(dep);
|
|
884
|
-
},
|
|
1100
|
+
}) as LoaderContext["use"],
|
|
885
1101
|
method: "GET",
|
|
886
1102
|
body: undefined,
|
|
887
1103
|
reverse: createReverseFunction(
|
|
@@ -890,15 +1106,19 @@ export function createUseFunction<TEnv>(
|
|
|
890
1106
|
ctx.params as Record<string, string>,
|
|
891
1107
|
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
892
1108
|
),
|
|
1109
|
+
rendered: () => {
|
|
1110
|
+
throw new Error(
|
|
1111
|
+
`ctx.rendered() is only available in DSL loaders (registered via loader() in urls()). ` +
|
|
1112
|
+
`It cannot be used from request-context loaders or server actions.`,
|
|
1113
|
+
);
|
|
1114
|
+
},
|
|
893
1115
|
};
|
|
894
1116
|
|
|
895
|
-
// Start loader execution with tracking
|
|
896
1117
|
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
897
1118
|
const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
|
|
898
1119
|
doneLoader();
|
|
899
1120
|
});
|
|
900
1121
|
|
|
901
|
-
// Memoize for subsequent calls
|
|
902
1122
|
loaderPromises.set(loader.$$id, promise);
|
|
903
1123
|
|
|
904
1124
|
return promise;
|