@rangojs/router 0.0.0-experimental.32 → 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 +120 -204
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +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 +190 -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 +63 -24
- 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 +338 -126
- 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)
|
|
@@ -97,6 +110,10 @@ export interface RequestContext<
|
|
|
97
110
|
setStatus(status: number): void;
|
|
98
111
|
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
99
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;
|
|
100
117
|
|
|
101
118
|
/**
|
|
102
119
|
* Access loader data or push handle data.
|
|
@@ -135,26 +152,31 @@ export interface RequestContext<
|
|
|
135
152
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
136
153
|
_cacheStore?: SegmentCacheStore;
|
|
137
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
|
+
|
|
138
174
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
139
175
|
_cacheProfiles?: Record<
|
|
140
176
|
string,
|
|
141
177
|
import("../cache/profile-registry.js").CacheProfile
|
|
142
178
|
>;
|
|
143
179
|
|
|
144
|
-
/**
|
|
145
|
-
* Schedule work to run after the response is sent.
|
|
146
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
147
|
-
* On Node.js, runs as fire-and-forget.
|
|
148
|
-
*
|
|
149
|
-
* @example
|
|
150
|
-
* ```typescript
|
|
151
|
-
* ctx.waitUntil(async () => {
|
|
152
|
-
* await cacheStore.set(key, data, ttl);
|
|
153
|
-
* });
|
|
154
|
-
* ```
|
|
155
|
-
*/
|
|
156
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
157
|
-
|
|
158
180
|
/**
|
|
159
181
|
* Register a callback to run when the response is created.
|
|
160
182
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -258,6 +280,68 @@ export interface RequestContext<
|
|
|
258
280
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
259
281
|
_prevRouteKey?: string;
|
|
260
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
|
+
|
|
261
345
|
/** @internal Per-request error dedup set for onError reporting */
|
|
262
346
|
_reportedErrors: WeakSet<object>;
|
|
263
347
|
|
|
@@ -265,15 +349,37 @@ export interface RequestContext<
|
|
|
265
349
|
* @internal Report a non-fatal background error through the router's
|
|
266
350
|
* onError callback. Wired by the RSC handler / router during request
|
|
267
351
|
* creation. Cache-runtime and other subsystems call this to surface
|
|
268
|
-
* 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`).
|
|
269
354
|
*/
|
|
270
|
-
_reportBackgroundError?: (
|
|
355
|
+
_reportBackgroundError?: (
|
|
356
|
+
error: unknown,
|
|
357
|
+
category: CacheErrorCategory,
|
|
358
|
+
) => void;
|
|
271
359
|
|
|
272
360
|
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
273
361
|
_debugPerformance?: boolean;
|
|
274
362
|
|
|
275
363
|
/** @internal Request-scoped performance metrics store */
|
|
276
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[];
|
|
277
383
|
}
|
|
278
384
|
|
|
279
385
|
/**
|
|
@@ -293,6 +399,8 @@ export type PublicRequestContext<
|
|
|
293
399
|
| "deleteCookie"
|
|
294
400
|
| "_handleStore"
|
|
295
401
|
| "_cacheStore"
|
|
402
|
+
| "_explicitTaggedStores"
|
|
403
|
+
| "_requestTags"
|
|
296
404
|
| "_cacheProfiles"
|
|
297
405
|
| "_onResponseCallbacks"
|
|
298
406
|
| "_themeConfig"
|
|
@@ -300,10 +408,24 @@ export type PublicRequestContext<
|
|
|
300
408
|
| "_routeName"
|
|
301
409
|
| "_prevRouteKey"
|
|
302
410
|
| "_reportedErrors"
|
|
411
|
+
| "_renderBarrier"
|
|
412
|
+
| "_resolveRenderBarrier"
|
|
413
|
+
| "_renderBarrierSegmentOrder"
|
|
414
|
+
| "_treeHasStreaming"
|
|
415
|
+
| "_renderBarrierWaiters"
|
|
416
|
+
| "_handlerLoaderDeps"
|
|
417
|
+
| "_renderBarrierHandleSnapshot"
|
|
418
|
+
| "_renderBarrierGuardClosed"
|
|
303
419
|
| "_reportBackgroundError"
|
|
304
420
|
| "_debugPerformance"
|
|
305
421
|
| "_metricsStore"
|
|
422
|
+
| "_basename"
|
|
306
423
|
| "_setStatus"
|
|
424
|
+
| "_rotateStateCookie"
|
|
425
|
+
| "_setKeepCacheDirective"
|
|
426
|
+
| "_variables"
|
|
427
|
+
| "_classifiedRoute"
|
|
428
|
+
| "_cacheSignal"
|
|
307
429
|
| "res"
|
|
308
430
|
>;
|
|
309
431
|
|
|
@@ -355,6 +477,7 @@ export function _getRequestContext<TEnv = DefaultEnv>():
|
|
|
355
477
|
export function setRequestContextParams(
|
|
356
478
|
params: Record<string, string>,
|
|
357
479
|
routeName?: string,
|
|
480
|
+
routeMap?: Record<string, string>,
|
|
358
481
|
): void {
|
|
359
482
|
const ctx = requestContextStorage.getStore();
|
|
360
483
|
if (ctx) {
|
|
@@ -367,9 +490,13 @@ export function setRequestContextParams(
|
|
|
367
490
|
: undefined
|
|
368
491
|
) as DefaultRouteName | undefined;
|
|
369
492
|
}
|
|
370
|
-
// 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.
|
|
371
498
|
ctx.reverse = createReverseFunction(
|
|
372
|
-
getGlobalRouteMap(),
|
|
499
|
+
routeMap ?? getGlobalRouteMap(),
|
|
373
500
|
routeName,
|
|
374
501
|
params,
|
|
375
502
|
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
@@ -413,13 +540,7 @@ export function requireRequestContext<
|
|
|
413
540
|
return getRequestContext<TEnv>();
|
|
414
541
|
}
|
|
415
542
|
|
|
416
|
-
|
|
417
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
418
|
-
*/
|
|
419
|
-
export interface ExecutionContext {
|
|
420
|
-
waitUntil(promise: Promise<any>): void;
|
|
421
|
-
passThroughOnException(): void;
|
|
422
|
-
}
|
|
543
|
+
export type { ExecutionContext };
|
|
423
544
|
|
|
424
545
|
/**
|
|
425
546
|
* Options for creating a request context
|
|
@@ -433,6 +554,11 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
433
554
|
initialResponse?: Response;
|
|
434
555
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
435
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>;
|
|
436
562
|
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
437
563
|
cacheProfiles?: Record<
|
|
438
564
|
string,
|
|
@@ -442,6 +568,10 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
442
568
|
executionContext?: ExecutionContext;
|
|
443
569
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
444
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;
|
|
445
575
|
}
|
|
446
576
|
|
|
447
577
|
/**
|
|
@@ -462,15 +592,17 @@ export function createRequestContext<TEnv>(
|
|
|
462
592
|
variables,
|
|
463
593
|
initialResponse,
|
|
464
594
|
cacheStore,
|
|
595
|
+
explicitTaggedStores,
|
|
465
596
|
cacheProfiles,
|
|
466
597
|
executionContext,
|
|
467
598
|
themeConfig,
|
|
599
|
+
stateCookieName,
|
|
600
|
+
version: stateVersion,
|
|
468
601
|
} = options;
|
|
469
602
|
const cookieHeader = request.headers.get("Cookie");
|
|
603
|
+
let rangoStateRotated = false;
|
|
470
604
|
let parsedCookies: Record<string, string> | null = null;
|
|
471
605
|
|
|
472
|
-
// Create stub response for collecting headers/cookies.
|
|
473
|
-
// All cookie/header mutations go here; cookie reads derive from it.
|
|
474
606
|
let stubResponse = initialResponse
|
|
475
607
|
? new Response(null, {
|
|
476
608
|
status: initialResponse.status,
|
|
@@ -479,11 +611,9 @@ export function createRequestContext<TEnv>(
|
|
|
479
611
|
})
|
|
480
612
|
: new Response(null, { status: 200 });
|
|
481
613
|
|
|
482
|
-
// Create handle store and loader memoization for this request
|
|
483
614
|
const handleStore = createHandleStore();
|
|
484
615
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
485
616
|
|
|
486
|
-
// Lazy parse cookies from the original Cookie header
|
|
487
617
|
const getParsedCookies = (): Record<string, string> => {
|
|
488
618
|
if (!parsedCookies) {
|
|
489
619
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -491,7 +621,6 @@ export function createRequestContext<TEnv>(
|
|
|
491
621
|
return parsedCookies;
|
|
492
622
|
};
|
|
493
623
|
|
|
494
|
-
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
495
624
|
let responseCookieCache: Map<string, string | null> | null = null;
|
|
496
625
|
const getResponseCookies = (): Map<string, string | null> => {
|
|
497
626
|
if (!responseCookieCache) {
|
|
@@ -503,8 +632,17 @@ export function createRequestContext<TEnv>(
|
|
|
503
632
|
responseCookieCache = null;
|
|
504
633
|
};
|
|
505
634
|
|
|
506
|
-
|
|
507
|
-
|
|
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).
|
|
508
646
|
const effectiveCookie = (name: string): string | undefined => {
|
|
509
647
|
const mutations = getResponseCookies();
|
|
510
648
|
if (mutations.has(name)) {
|
|
@@ -514,14 +652,11 @@ export function createRequestContext<TEnv>(
|
|
|
514
652
|
return getParsedCookies()[name];
|
|
515
653
|
};
|
|
516
654
|
|
|
517
|
-
// Theme helpers (only used when themeConfig is provided)
|
|
518
655
|
const getTheme = (): Theme | undefined => {
|
|
519
656
|
if (!themeConfig) return undefined;
|
|
520
657
|
|
|
521
|
-
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
522
658
|
const stored = effectiveCookie(themeConfig.storageKey);
|
|
523
659
|
if (stored) {
|
|
524
|
-
// Validate stored value
|
|
525
660
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
526
661
|
return "system";
|
|
527
662
|
}
|
|
@@ -535,7 +670,6 @@ export function createRequestContext<TEnv>(
|
|
|
535
670
|
const setTheme = (theme: Theme): void => {
|
|
536
671
|
if (!themeConfig) return;
|
|
537
672
|
|
|
538
|
-
// Validate theme value
|
|
539
673
|
if (theme !== "system" && !themeConfig.themes.includes(theme)) {
|
|
540
674
|
console.warn(
|
|
541
675
|
`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`,
|
|
@@ -543,7 +677,6 @@ export function createRequestContext<TEnv>(
|
|
|
543
677
|
return;
|
|
544
678
|
}
|
|
545
679
|
|
|
546
|
-
// Write to stub — effectiveCookie() will pick it up on next read
|
|
547
680
|
stubResponse.headers.append(
|
|
548
681
|
"Set-Cookie",
|
|
549
682
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
@@ -555,20 +688,29 @@ export function createRequestContext<TEnv>(
|
|
|
555
688
|
invalidateResponseCookieCache();
|
|
556
689
|
};
|
|
557
690
|
|
|
558
|
-
|
|
691
|
+
const cleanUrl = stripInternalParams(url);
|
|
692
|
+
|
|
559
693
|
const ctx: RequestContext<TEnv> = {
|
|
560
694
|
env,
|
|
561
695
|
request,
|
|
562
|
-
url,
|
|
696
|
+
url: cleanUrl,
|
|
563
697
|
originalUrl: new URL(request.url),
|
|
564
698
|
pathname: url.pathname,
|
|
565
|
-
searchParams:
|
|
566
|
-
|
|
567
|
-
get: ((keyOrVar: any) =>
|
|
568
|
-
|
|
569
|
-
|
|
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) => {
|
|
570
712
|
assertNotInsideCacheExec(ctx, "set");
|
|
571
|
-
contextSet(variables, keyOrVar, value);
|
|
713
|
+
contextSet(variables, keyOrVar, value, options);
|
|
572
714
|
}) as RequestContext<TEnv>["set"],
|
|
573
715
|
params: {} as Record<string, string>,
|
|
574
716
|
|
|
@@ -606,6 +748,7 @@ export function createRequestContext<TEnv>(
|
|
|
606
748
|
|
|
607
749
|
setCookie(name: string, value: string, options?: CookieOptions): void {
|
|
608
750
|
assertNotInsideCacheExec(ctx, "setCookie");
|
|
751
|
+
assertNotInsideCacheScopeALS("setCookie");
|
|
609
752
|
stubResponse.headers.append(
|
|
610
753
|
"Set-Cookie",
|
|
611
754
|
serializeCookieValue(name, value, options),
|
|
@@ -618,6 +761,7 @@ export function createRequestContext<TEnv>(
|
|
|
618
761
|
options?: Pick<CookieOptions, "domain" | "path">,
|
|
619
762
|
): void {
|
|
620
763
|
assertNotInsideCacheExec(ctx, "deleteCookie");
|
|
764
|
+
assertNotInsideCacheScopeALS("deleteCookie");
|
|
621
765
|
stubResponse.headers.append(
|
|
622
766
|
"Set-Cookie",
|
|
623
767
|
serializeCookieValue(name, "", { ...options, maxAge: 0 }),
|
|
@@ -627,11 +771,52 @@ export function createRequestContext<TEnv>(
|
|
|
627
771
|
|
|
628
772
|
header(name: string, value: string): void {
|
|
629
773
|
assertNotInsideCacheExec(ctx, "header");
|
|
774
|
+
assertNotInsideCacheScopeALS("header");
|
|
630
775
|
stubResponse.headers.set(name, value);
|
|
631
776
|
},
|
|
632
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
|
+
|
|
633
817
|
setStatus(status: number): void {
|
|
634
818
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
819
|
+
assertNotInsideCacheScopeALS("setStatus");
|
|
635
820
|
stubResponse = new Response(null, {
|
|
636
821
|
status,
|
|
637
822
|
headers: stubResponse.headers,
|
|
@@ -645,35 +830,34 @@ export function createRequestContext<TEnv>(
|
|
|
645
830
|
});
|
|
646
831
|
},
|
|
647
832
|
|
|
648
|
-
// Placeholder - will be replaced below
|
|
649
833
|
use: null as any,
|
|
650
834
|
|
|
651
835
|
method: request.method,
|
|
652
836
|
|
|
653
837
|
_handleStore: handleStore,
|
|
654
838
|
_cacheStore: cacheStore,
|
|
839
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
840
|
+
_requestTags: new Set<string>(),
|
|
655
841
|
_cacheProfiles: cacheProfiles,
|
|
656
842
|
|
|
657
843
|
waitUntil(fn: () => Promise<void>): void {
|
|
658
844
|
if (executionContext?.waitUntil) {
|
|
659
|
-
// Cloudflare Workers: use native waitUntil
|
|
660
845
|
executionContext.waitUntil(fn());
|
|
661
846
|
} else {
|
|
662
|
-
|
|
663
|
-
fn().catch((err) =>
|
|
664
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
665
|
-
);
|
|
847
|
+
fireAndForgetWaitUntil(fn);
|
|
666
848
|
}
|
|
667
849
|
},
|
|
668
850
|
|
|
851
|
+
executionContext,
|
|
852
|
+
|
|
669
853
|
_onResponseCallbacks: [],
|
|
670
854
|
|
|
671
855
|
onResponse(callback: (response: Response) => Response): void {
|
|
672
856
|
assertNotInsideCacheExec(ctx, "onResponse");
|
|
857
|
+
assertNotInsideCacheScopeALS("onResponse");
|
|
673
858
|
this._onResponseCallbacks.push(callback);
|
|
674
859
|
},
|
|
675
860
|
|
|
676
|
-
// Theme properties (only set when themeConfig is provided)
|
|
677
861
|
get theme() {
|
|
678
862
|
return themeConfig ? getTheme() : undefined;
|
|
679
863
|
},
|
|
@@ -697,27 +881,71 @@ export function createRequestContext<TEnv>(
|
|
|
697
881
|
_reportedErrors: new WeakSet<object>(),
|
|
698
882
|
_metricsStore: undefined,
|
|
699
883
|
|
|
884
|
+
_renderBarrier: null as any,
|
|
885
|
+
_resolveRenderBarrier: null as any,
|
|
886
|
+
_renderBarrierSegmentOrder: undefined,
|
|
887
|
+
|
|
700
888
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
701
889
|
};
|
|
702
890
|
|
|
703
|
-
//
|
|
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
|
+
|
|
704
939
|
ctx.use = createUseFunction({
|
|
705
940
|
handleStore,
|
|
706
941
|
loaderPromises,
|
|
707
942
|
getContext: () => ctx,
|
|
708
943
|
});
|
|
709
944
|
|
|
710
|
-
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
711
945
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
712
946
|
return ctx;
|
|
713
947
|
}
|
|
714
948
|
|
|
715
|
-
/**
|
|
716
|
-
* Parse Set-Cookie headers from a response into effective cookie state.
|
|
717
|
-
* Returns a map of cookie name -> value (string) or name -> null (deleted).
|
|
718
|
-
* Last-write-wins: later Set-Cookie entries for the same name overwrite earlier ones.
|
|
719
|
-
* Max-Age=0 is treated as a delete.
|
|
720
|
-
*/
|
|
721
949
|
const MAX_AGE_ZERO_RE = /;\s*Max-Age\s*=\s*0/i;
|
|
722
950
|
|
|
723
951
|
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
@@ -725,7 +953,6 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
725
953
|
const setCookies = response.headers.getSetCookie();
|
|
726
954
|
|
|
727
955
|
for (const header of setCookies) {
|
|
728
|
-
// First segment before ';' is the name=value pair
|
|
729
956
|
const semiIdx = header.indexOf(";");
|
|
730
957
|
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
731
958
|
const eqIdx = pair.indexOf("=");
|
|
@@ -737,11 +964,9 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
737
964
|
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
738
965
|
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
739
966
|
} catch {
|
|
740
|
-
// Malformed encoding — skip this entry
|
|
741
967
|
continue;
|
|
742
968
|
}
|
|
743
969
|
|
|
744
|
-
// Max-Age=0 means the cookie is being deleted
|
|
745
970
|
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
746
971
|
result.set(name, isDeleted ? null : value);
|
|
747
972
|
}
|
|
@@ -749,10 +974,10 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
749
974
|
return result;
|
|
750
975
|
}
|
|
751
976
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
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(
|
|
756
981
|
cookieHeader: string | null,
|
|
757
982
|
): Record<string, string> {
|
|
758
983
|
if (!cookieHeader) return {};
|
|
@@ -767,7 +992,7 @@ function parseCookiesFromHeader(
|
|
|
767
992
|
try {
|
|
768
993
|
cookies[name] = decodeURIComponent(raw);
|
|
769
994
|
} catch {
|
|
770
|
-
// Malformed percent-
|
|
995
|
+
// Malformed percent-encoding: fall back to raw value
|
|
771
996
|
cookies[name] = raw;
|
|
772
997
|
}
|
|
773
998
|
}
|
|
@@ -776,10 +1001,7 @@ function parseCookiesFromHeader(
|
|
|
776
1001
|
return cookies;
|
|
777
1002
|
}
|
|
778
1003
|
|
|
779
|
-
|
|
780
|
-
* Serialize a cookie for Set-Cookie header
|
|
781
|
-
*/
|
|
782
|
-
function serializeCookieValue(
|
|
1004
|
+
export function serializeCookieValue(
|
|
783
1005
|
name: string,
|
|
784
1006
|
value: string,
|
|
785
1007
|
options: CookieOptions = {},
|
|
@@ -806,20 +1028,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
806
1028
|
getContext: () => RequestContext<TEnv>;
|
|
807
1029
|
}
|
|
808
1030
|
|
|
809
|
-
/**
|
|
810
|
-
* Create the use() function for loader and handle composition.
|
|
811
|
-
*
|
|
812
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
813
|
-
* - For loaders: executes and memoizes loader functions
|
|
814
|
-
* - For handles: returns a push function to add handle data
|
|
815
|
-
*/
|
|
816
1031
|
export function createUseFunction<TEnv>(
|
|
817
1032
|
options: CreateUseFunctionOptions<TEnv>,
|
|
818
1033
|
): RequestContext["use"] {
|
|
819
1034
|
const { handleStore, loaderPromises, getContext } = options;
|
|
820
1035
|
|
|
821
1036
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
822
|
-
// Handle case: return a push function
|
|
823
1037
|
if (isHandle(item)) {
|
|
824
1038
|
const handle = item;
|
|
825
1039
|
const ctx = getContext();
|
|
@@ -832,30 +1046,24 @@ export function createUseFunction<TEnv>(
|
|
|
832
1046
|
);
|
|
833
1047
|
}
|
|
834
1048
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
// Push directly - promises will be serialized by RSC and streamed
|
|
846
|
-
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
847
|
-
};
|
|
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
|
+
);
|
|
848
1059
|
}
|
|
849
1060
|
|
|
850
|
-
// Loader case
|
|
851
1061
|
const loader = item as LoaderDefinition<any, any>;
|
|
852
1062
|
|
|
853
|
-
// Return cached promise if already started
|
|
854
1063
|
if (loaderPromises.has(loader.$$id)) {
|
|
855
1064
|
return loaderPromises.get(loader.$$id);
|
|
856
1065
|
}
|
|
857
1066
|
|
|
858
|
-
// Get loader function - either from loader object or fetchable registry
|
|
859
1067
|
let loaderFn = loader.fn;
|
|
860
1068
|
if (!loaderFn) {
|
|
861
1069
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -872,7 +1080,6 @@ export function createUseFunction<TEnv>(
|
|
|
872
1080
|
|
|
873
1081
|
const ctx = getContext();
|
|
874
1082
|
|
|
875
|
-
// Create loader context with recursive use() support
|
|
876
1083
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
877
1084
|
params: ctx.params,
|
|
878
1085
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
@@ -881,15 +1088,16 @@ export function createUseFunction<TEnv>(
|
|
|
881
1088
|
search: (ctx as any).search ?? {},
|
|
882
1089
|
pathname: ctx.pathname,
|
|
883
1090
|
url: ctx.url,
|
|
1091
|
+
originalUrl: ctx.originalUrl,
|
|
884
1092
|
env: ctx.env as any,
|
|
885
|
-
|
|
1093
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1094
|
+
executionContext: ctx.executionContext,
|
|
886
1095
|
get: ctx.get as any,
|
|
887
|
-
use: <TDep, TDepParams = any>(
|
|
1096
|
+
use: (<TDep, TDepParams = any>(
|
|
888
1097
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
889
1098
|
): Promise<TDep> => {
|
|
890
|
-
// Recursive call - will start dep loader if not already started
|
|
891
1099
|
return ctx.use(dep);
|
|
892
|
-
},
|
|
1100
|
+
}) as LoaderContext["use"],
|
|
893
1101
|
method: "GET",
|
|
894
1102
|
body: undefined,
|
|
895
1103
|
reverse: createReverseFunction(
|
|
@@ -898,15 +1106,19 @@ export function createUseFunction<TEnv>(
|
|
|
898
1106
|
ctx.params as Record<string, string>,
|
|
899
1107
|
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
900
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
|
+
},
|
|
901
1115
|
};
|
|
902
1116
|
|
|
903
|
-
// Start loader execution with tracking
|
|
904
1117
|
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
905
1118
|
const promise = Promise.resolve(loaderFn(loaderCtx)).finally(() => {
|
|
906
1119
|
doneLoader();
|
|
907
1120
|
});
|
|
908
1121
|
|
|
909
|
-
// Memoize for subsequent calls
|
|
910
1122
|
loaderPromises.set(loader.$$id, promise);
|
|
911
1123
|
|
|
912
1124
|
return promise;
|