@rangojs/router 0.0.0-experimental.a769fbe7 → 0.0.0-experimental.ac99d918
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/README.md +211 -43
- package/dist/bin/rango.js +279 -102
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3313 -1160
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +62 -11
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +63 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +222 -30
- package/skills/caching/SKILL.md +263 -8
- 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 +250 -30
- package/skills/host-router/SKILL.md +124 -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 +223 -9
- 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 +77 -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 +66 -5
- package/skills/router-setup/SKILL.md +38 -3
- 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 +319 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/vercel/SKILL.md +107 -0
- 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 +1 -66
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +81 -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/navigation-bridge.ts +66 -14
- package/src/browser/navigation-client.ts +172 -109
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +9 -59
- package/src/browser/partial-update.ts +79 -93
- package/src/browser/prefetch/cache.ts +180 -62
- package/src/browser/prefetch/fetch.ts +252 -39
- package/src/browser/prefetch/queue.ts +42 -8
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +72 -10
- package/src/browser/react/NavigationProvider.tsx +83 -31
- 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 +111 -24
- package/src/browser/scroll-restoration.ts +29 -19
- package/src/browser/segment-reconciler.ts +36 -14
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -62
- package/src/browser/types.ts +60 -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 +6 -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 +333 -94
- 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 +134 -32
- package/src/cache/cache-scope.ts +100 -74
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2256 -241
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +61 -20
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +22 -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/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 +17 -5
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/browser.ts +0 -1
- 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 +69 -10
- package/src/index.ts +112 -14
- 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 +413 -275
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +107 -32
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +50 -8
- 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 +0 -16
- 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 +44 -23
- package/src/router/handler-context.ts +46 -30
- package/src/router/intercept-resolution.ts +23 -23
- package/src/router/lazy-includes.ts +15 -52
- package/src/router/loader-resolution.ts +207 -30
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +40 -42
- 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 +0 -7
- package/src/router/match-middleware/cache-lookup.ts +161 -262
- package/src/router/match-middleware/cache-store.ts +3 -33
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +45 -14
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +87 -39
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +7 -140
- package/src/router/middleware.ts +169 -140
- 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 +0 -27
- 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 +119 -65
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +40 -37
- package/src/router/segment-resolution/revalidation.ts +329 -305
- 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 +0 -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 +9 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +128 -42
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -409
- 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 +18 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +28 -4
- 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 +53 -56
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -69
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +22 -9
- 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 +208 -201
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +211 -52
- 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 +289 -124
- package/src/ssr/index.tsx +22 -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 +124 -70
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +40 -11
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +12 -7
- package/src/types/segments.ts +36 -15
- 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 -4
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +236 -6
- 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 +48 -95
- 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 +65 -207
- 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 +32 -23
- package/src/vite/plugins/virtual-entries.ts +46 -17
- package/src/vite/rango.ts +207 -125
- package/src/vite/router-discovery.ts +931 -133
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +1 -1
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/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 +97 -44
- package/src/browser/action-response-classifier.ts +0 -99
- package/src/browser/debug-channel.ts +0 -93
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/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 {
|
|
@@ -26,12 +33,20 @@ import {
|
|
|
26
33
|
contextSet,
|
|
27
34
|
isNonCacheable,
|
|
28
35
|
} from "../context-var.js";
|
|
29
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
createHandleStore,
|
|
38
|
+
buildHandleSnapshot,
|
|
39
|
+
type HandleStore,
|
|
40
|
+
type HandleData,
|
|
41
|
+
} from "./handle-store.js";
|
|
30
42
|
import { isHandle } from "../handle.js";
|
|
43
|
+
import { withDefer } from "../defer.js";
|
|
31
44
|
import { track, type MetricsStore } from "./context.js";
|
|
32
45
|
import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
33
46
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
34
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";
|
|
35
50
|
import { THEME_COOKIE } from "../theme/constants.js";
|
|
36
51
|
import type { LocationStateEntry } from "../browser/react/location-state-shared.js";
|
|
37
52
|
import { NOCACHE_SYMBOL, assertNotInsideCacheExec } from "../cache/taint.js";
|
|
@@ -53,24 +68,9 @@ import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
|
53
68
|
export interface RequestContext<
|
|
54
69
|
TEnv = DefaultEnv,
|
|
55
70
|
TParams = Record<string, string>,
|
|
56
|
-
> {
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
/** Original HTTP request */
|
|
60
|
-
request: Request;
|
|
61
|
-
/** Parsed URL (with internal `_rsc*` params stripped) */
|
|
62
|
-
url: URL;
|
|
63
|
-
/**
|
|
64
|
-
* The original request URL with all parameters intact, including
|
|
65
|
-
* internal `_rsc*` transport params.
|
|
66
|
-
*/
|
|
67
|
-
originalUrl: URL;
|
|
68
|
-
/** URL pathname */
|
|
69
|
-
pathname: string;
|
|
70
|
-
/** URL search params (with internal `_rsc*` params stripped, same as `url.searchParams`) */
|
|
71
|
-
searchParams: URLSearchParams;
|
|
72
|
-
/** Variables set by middleware (same as ctx.var) */
|
|
73
|
-
var: Record<string, any>;
|
|
71
|
+
> extends RequestScope<TEnv> {
|
|
72
|
+
/** @internal Shared variable backing store for ctx.get()/ctx.set(). */
|
|
73
|
+
_variables: Record<string, any>;
|
|
74
74
|
/** Get a variable set by middleware */
|
|
75
75
|
get: {
|
|
76
76
|
<T>(contextVar: ContextVar<T>): T | undefined;
|
|
@@ -110,6 +110,10 @@ export interface RequestContext<
|
|
|
110
110
|
setStatus(status: number): void;
|
|
111
111
|
/** @internal Set status bypassing cache-exec guard (for framework error handling) */
|
|
112
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;
|
|
113
117
|
|
|
114
118
|
/**
|
|
115
119
|
* Access loader data or push handle data.
|
|
@@ -148,26 +152,31 @@ export interface RequestContext<
|
|
|
148
152
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
149
153
|
_cacheStore?: SegmentCacheStore;
|
|
150
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
|
+
|
|
151
174
|
/** @internal Cache profiles for "use cache" profile resolution (per-router) */
|
|
152
175
|
_cacheProfiles?: Record<
|
|
153
176
|
string,
|
|
154
177
|
import("../cache/profile-registry.js").CacheProfile
|
|
155
178
|
>;
|
|
156
179
|
|
|
157
|
-
/**
|
|
158
|
-
* Schedule work to run after the response is sent.
|
|
159
|
-
* On Cloudflare Workers, uses ctx.waitUntil().
|
|
160
|
-
* On Node.js, runs as fire-and-forget.
|
|
161
|
-
*
|
|
162
|
-
* @example
|
|
163
|
-
* ```typescript
|
|
164
|
-
* ctx.waitUntil(async () => {
|
|
165
|
-
* await cacheStore.set(key, data, ttl);
|
|
166
|
-
* });
|
|
167
|
-
* ```
|
|
168
|
-
*/
|
|
169
|
-
waitUntil(fn: () => Promise<void>): void;
|
|
170
|
-
|
|
171
180
|
/**
|
|
172
181
|
* Register a callback to run when the response is created.
|
|
173
182
|
* Callbacks are sync and receive the response. They can:
|
|
@@ -271,6 +280,68 @@ export interface RequestContext<
|
|
|
271
280
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
272
281
|
_prevRouteKey?: string;
|
|
273
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
|
+
|
|
274
345
|
/** @internal Per-request error dedup set for onError reporting */
|
|
275
346
|
_reportedErrors: WeakSet<object>;
|
|
276
347
|
|
|
@@ -278,9 +349,13 @@ export interface RequestContext<
|
|
|
278
349
|
* @internal Report a non-fatal background error through the router's
|
|
279
350
|
* onError callback. Wired by the RSC handler / router during request
|
|
280
351
|
* creation. Cache-runtime and other subsystems call this to surface
|
|
281
|
-
* 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`).
|
|
282
354
|
*/
|
|
283
|
-
_reportBackgroundError?: (
|
|
355
|
+
_reportBackgroundError?: (
|
|
356
|
+
error: unknown,
|
|
357
|
+
category: CacheErrorCategory,
|
|
358
|
+
) => void;
|
|
284
359
|
|
|
285
360
|
/** @internal Per-request debug performance override (set via ctx.debugPerformance()) */
|
|
286
361
|
_debugPerformance?: boolean;
|
|
@@ -288,11 +363,23 @@ export interface RequestContext<
|
|
|
288
363
|
/** @internal Request-scoped performance metrics store */
|
|
289
364
|
_metricsStore?: MetricsStore;
|
|
290
365
|
|
|
291
|
-
/** @internal
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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[];
|
|
296
383
|
}
|
|
297
384
|
|
|
298
385
|
/**
|
|
@@ -312,6 +399,8 @@ export type PublicRequestContext<
|
|
|
312
399
|
| "deleteCookie"
|
|
313
400
|
| "_handleStore"
|
|
314
401
|
| "_cacheStore"
|
|
402
|
+
| "_explicitTaggedStores"
|
|
403
|
+
| "_requestTags"
|
|
315
404
|
| "_cacheProfiles"
|
|
316
405
|
| "_onResponseCallbacks"
|
|
317
406
|
| "_themeConfig"
|
|
@@ -319,11 +408,24 @@ export type PublicRequestContext<
|
|
|
319
408
|
| "_routeName"
|
|
320
409
|
| "_prevRouteKey"
|
|
321
410
|
| "_reportedErrors"
|
|
411
|
+
| "_renderBarrier"
|
|
412
|
+
| "_resolveRenderBarrier"
|
|
413
|
+
| "_renderBarrierSegmentOrder"
|
|
414
|
+
| "_treeHasStreaming"
|
|
415
|
+
| "_renderBarrierWaiters"
|
|
416
|
+
| "_handlerLoaderDeps"
|
|
417
|
+
| "_renderBarrierHandleSnapshot"
|
|
418
|
+
| "_renderBarrierGuardClosed"
|
|
322
419
|
| "_reportBackgroundError"
|
|
323
420
|
| "_debugPerformance"
|
|
324
421
|
| "_metricsStore"
|
|
325
|
-
| "
|
|
422
|
+
| "_basename"
|
|
326
423
|
| "_setStatus"
|
|
424
|
+
| "_rotateStateCookie"
|
|
425
|
+
| "_setKeepCacheDirective"
|
|
426
|
+
| "_variables"
|
|
427
|
+
| "_classifiedRoute"
|
|
428
|
+
| "_cacheSignal"
|
|
327
429
|
| "res"
|
|
328
430
|
>;
|
|
329
431
|
|
|
@@ -375,6 +477,7 @@ export function _getRequestContext<TEnv = DefaultEnv>():
|
|
|
375
477
|
export function setRequestContextParams(
|
|
376
478
|
params: Record<string, string>,
|
|
377
479
|
routeName?: string,
|
|
480
|
+
routeMap?: Record<string, string>,
|
|
378
481
|
): void {
|
|
379
482
|
const ctx = requestContextStorage.getStore();
|
|
380
483
|
if (ctx) {
|
|
@@ -387,9 +490,13 @@ export function setRequestContextParams(
|
|
|
387
490
|
: undefined
|
|
388
491
|
) as DefaultRouteName | undefined;
|
|
389
492
|
}
|
|
390
|
-
// 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.
|
|
391
498
|
ctx.reverse = createReverseFunction(
|
|
392
|
-
getGlobalRouteMap(),
|
|
499
|
+
routeMap ?? getGlobalRouteMap(),
|
|
393
500
|
routeName,
|
|
394
501
|
params,
|
|
395
502
|
routeName ? isRouteRootScoped(routeName) : undefined,
|
|
@@ -433,13 +540,7 @@ export function requireRequestContext<
|
|
|
433
540
|
return getRequestContext<TEnv>();
|
|
434
541
|
}
|
|
435
542
|
|
|
436
|
-
|
|
437
|
-
* Cloudflare Workers ExecutionContext (subset we need)
|
|
438
|
-
*/
|
|
439
|
-
export interface ExecutionContext {
|
|
440
|
-
waitUntil(promise: Promise<any>): void;
|
|
441
|
-
passThroughOnException(): void;
|
|
442
|
-
}
|
|
543
|
+
export type { ExecutionContext };
|
|
443
544
|
|
|
444
545
|
/**
|
|
445
546
|
* Options for creating a request context
|
|
@@ -453,6 +554,11 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
453
554
|
initialResponse?: Response;
|
|
454
555
|
/** Optional cache store for segment caching (used by CacheScope) */
|
|
455
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>;
|
|
456
562
|
/** Optional cache profiles for "use cache" resolution (per-router) */
|
|
457
563
|
cacheProfiles?: Record<
|
|
458
564
|
string,
|
|
@@ -462,6 +568,10 @@ export interface CreateRequestContextOptions<TEnv> {
|
|
|
462
568
|
executionContext?: ExecutionContext;
|
|
463
569
|
/** Optional theme configuration (enables ctx.theme and ctx.setTheme) */
|
|
464
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;
|
|
465
575
|
}
|
|
466
576
|
|
|
467
577
|
/**
|
|
@@ -482,15 +592,17 @@ export function createRequestContext<TEnv>(
|
|
|
482
592
|
variables,
|
|
483
593
|
initialResponse,
|
|
484
594
|
cacheStore,
|
|
595
|
+
explicitTaggedStores,
|
|
485
596
|
cacheProfiles,
|
|
486
597
|
executionContext,
|
|
487
598
|
themeConfig,
|
|
599
|
+
stateCookieName,
|
|
600
|
+
version: stateVersion,
|
|
488
601
|
} = options;
|
|
489
602
|
const cookieHeader = request.headers.get("Cookie");
|
|
603
|
+
let rangoStateRotated = false;
|
|
490
604
|
let parsedCookies: Record<string, string> | null = null;
|
|
491
605
|
|
|
492
|
-
// Create stub response for collecting headers/cookies.
|
|
493
|
-
// All cookie/header mutations go here; cookie reads derive from it.
|
|
494
606
|
let stubResponse = initialResponse
|
|
495
607
|
? new Response(null, {
|
|
496
608
|
status: initialResponse.status,
|
|
@@ -499,11 +611,9 @@ export function createRequestContext<TEnv>(
|
|
|
499
611
|
})
|
|
500
612
|
: new Response(null, { status: 200 });
|
|
501
613
|
|
|
502
|
-
// Create handle store and loader memoization for this request
|
|
503
614
|
const handleStore = createHandleStore();
|
|
504
615
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
505
616
|
|
|
506
|
-
// Lazy parse cookies from the original Cookie header
|
|
507
617
|
const getParsedCookies = (): Record<string, string> => {
|
|
508
618
|
if (!parsedCookies) {
|
|
509
619
|
parsedCookies = parseCookiesFromHeader(cookieHeader);
|
|
@@ -511,7 +621,6 @@ export function createRequestContext<TEnv>(
|
|
|
511
621
|
return parsedCookies;
|
|
512
622
|
};
|
|
513
623
|
|
|
514
|
-
// Cached response cookie mutations — invalidated on setCookie/deleteCookie/setTheme
|
|
515
624
|
let responseCookieCache: Map<string, string | null> | null = null;
|
|
516
625
|
const getResponseCookies = (): Map<string, string | null> => {
|
|
517
626
|
if (!responseCookieCache) {
|
|
@@ -523,8 +632,6 @@ export function createRequestContext<TEnv>(
|
|
|
523
632
|
responseCookieCache = null;
|
|
524
633
|
};
|
|
525
634
|
|
|
526
|
-
// Guard: throw if a response-level side effect is called inside a cache() scope.
|
|
527
|
-
// Uses ALS to detect the scope (set during segment resolution).
|
|
528
635
|
function assertNotInsideCacheScopeALS(methodName: string): void {
|
|
529
636
|
if (isInsideCacheScope()) {
|
|
530
637
|
throw new Error(
|
|
@@ -535,8 +642,7 @@ export function createRequestContext<TEnv>(
|
|
|
535
642
|
}
|
|
536
643
|
}
|
|
537
644
|
|
|
538
|
-
//
|
|
539
|
-
// The stub IS the source of truth for same-request mutations.
|
|
645
|
+
// Response stub Set-Cookie wins, then original header (source of truth for mutations).
|
|
540
646
|
const effectiveCookie = (name: string): string | undefined => {
|
|
541
647
|
const mutations = getResponseCookies();
|
|
542
648
|
if (mutations.has(name)) {
|
|
@@ -546,14 +652,11 @@ export function createRequestContext<TEnv>(
|
|
|
546
652
|
return getParsedCookies()[name];
|
|
547
653
|
};
|
|
548
654
|
|
|
549
|
-
// Theme helpers (only used when themeConfig is provided)
|
|
550
655
|
const getTheme = (): Theme | undefined => {
|
|
551
656
|
if (!themeConfig) return undefined;
|
|
552
657
|
|
|
553
|
-
// Use overlay-aware read so setTheme() in the same request is reflected
|
|
554
658
|
const stored = effectiveCookie(themeConfig.storageKey);
|
|
555
659
|
if (stored) {
|
|
556
|
-
// Validate stored value
|
|
557
660
|
if (stored === "system" && themeConfig.enableSystem) {
|
|
558
661
|
return "system";
|
|
559
662
|
}
|
|
@@ -567,7 +670,6 @@ export function createRequestContext<TEnv>(
|
|
|
567
670
|
const setTheme = (theme: Theme): void => {
|
|
568
671
|
if (!themeConfig) return;
|
|
569
672
|
|
|
570
|
-
// Validate theme value
|
|
571
673
|
if (theme !== "system" && !themeConfig.themes.includes(theme)) {
|
|
572
674
|
console.warn(
|
|
573
675
|
`[Theme] Invalid theme value: "${theme}". Valid values: system, ${themeConfig.themes.join(", ")}`,
|
|
@@ -575,7 +677,6 @@ export function createRequestContext<TEnv>(
|
|
|
575
677
|
return;
|
|
576
678
|
}
|
|
577
679
|
|
|
578
|
-
// Write to stub — effectiveCookie() will pick it up on next read
|
|
579
680
|
stubResponse.headers.append(
|
|
580
681
|
"Set-Cookie",
|
|
581
682
|
serializeCookieValue(themeConfig.storageKey, theme, {
|
|
@@ -587,10 +688,8 @@ export function createRequestContext<TEnv>(
|
|
|
587
688
|
invalidateResponseCookieCache();
|
|
588
689
|
};
|
|
589
690
|
|
|
590
|
-
// Strip internal _rsc* params so userland sees a clean URL.
|
|
591
691
|
const cleanUrl = stripInternalParams(url);
|
|
592
692
|
|
|
593
|
-
// Build the context object first (without use), then add use
|
|
594
693
|
const ctx: RequestContext<TEnv> = {
|
|
595
694
|
env,
|
|
596
695
|
request,
|
|
@@ -598,7 +697,7 @@ export function createRequestContext<TEnv>(
|
|
|
598
697
|
originalUrl: new URL(request.url),
|
|
599
698
|
pathname: url.pathname,
|
|
600
699
|
searchParams: cleanUrl.searchParams,
|
|
601
|
-
|
|
700
|
+
_variables: variables,
|
|
602
701
|
get: ((keyOrVar: any) => {
|
|
603
702
|
if (isNonCacheable(variables, keyOrVar) && isInsideCacheScope()) {
|
|
604
703
|
throw new Error(
|
|
@@ -676,6 +775,45 @@ export function createRequestContext<TEnv>(
|
|
|
676
775
|
stubResponse.headers.set(name, value);
|
|
677
776
|
},
|
|
678
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
|
+
|
|
679
817
|
setStatus(status: number): void {
|
|
680
818
|
assertNotInsideCacheExec(ctx, "setStatus");
|
|
681
819
|
assertNotInsideCacheScopeALS("setStatus");
|
|
@@ -692,27 +830,26 @@ export function createRequestContext<TEnv>(
|
|
|
692
830
|
});
|
|
693
831
|
},
|
|
694
832
|
|
|
695
|
-
// Placeholder - will be replaced below
|
|
696
833
|
use: null as any,
|
|
697
834
|
|
|
698
835
|
method: request.method,
|
|
699
836
|
|
|
700
837
|
_handleStore: handleStore,
|
|
701
838
|
_cacheStore: cacheStore,
|
|
839
|
+
_explicitTaggedStores: explicitTaggedStores,
|
|
840
|
+
_requestTags: new Set<string>(),
|
|
702
841
|
_cacheProfiles: cacheProfiles,
|
|
703
842
|
|
|
704
843
|
waitUntil(fn: () => Promise<void>): void {
|
|
705
844
|
if (executionContext?.waitUntil) {
|
|
706
|
-
// Cloudflare Workers: use native waitUntil
|
|
707
845
|
executionContext.waitUntil(fn());
|
|
708
846
|
} else {
|
|
709
|
-
|
|
710
|
-
fn().catch((err) =>
|
|
711
|
-
console.error("[waitUntil] Background task failed:", err),
|
|
712
|
-
);
|
|
847
|
+
fireAndForgetWaitUntil(fn);
|
|
713
848
|
}
|
|
714
849
|
},
|
|
715
850
|
|
|
851
|
+
executionContext,
|
|
852
|
+
|
|
716
853
|
_onResponseCallbacks: [],
|
|
717
854
|
|
|
718
855
|
onResponse(callback: (response: Response) => Response): void {
|
|
@@ -721,7 +858,6 @@ export function createRequestContext<TEnv>(
|
|
|
721
858
|
this._onResponseCallbacks.push(callback);
|
|
722
859
|
},
|
|
723
860
|
|
|
724
|
-
// Theme properties (only set when themeConfig is provided)
|
|
725
861
|
get theme() {
|
|
726
862
|
return themeConfig ? getTheme() : undefined;
|
|
727
863
|
},
|
|
@@ -745,27 +881,71 @@ export function createRequestContext<TEnv>(
|
|
|
745
881
|
_reportedErrors: new WeakSet<object>(),
|
|
746
882
|
_metricsStore: undefined,
|
|
747
883
|
|
|
884
|
+
_renderBarrier: null as any,
|
|
885
|
+
_resolveRenderBarrier: null as any,
|
|
886
|
+
_renderBarrierSegmentOrder: undefined,
|
|
887
|
+
|
|
748
888
|
reverse: createReverseFunction(getGlobalRouteMap(), undefined, {}),
|
|
749
889
|
};
|
|
750
890
|
|
|
751
|
-
//
|
|
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
|
+
|
|
752
939
|
ctx.use = createUseFunction({
|
|
753
940
|
handleStore,
|
|
754
941
|
loaderPromises,
|
|
755
942
|
getContext: () => ctx,
|
|
756
943
|
});
|
|
757
944
|
|
|
758
|
-
// Brand with taint symbol so "use cache" excludes ctx from cache keys
|
|
759
945
|
(ctx as any)[NOCACHE_SYMBOL] = true;
|
|
760
946
|
return ctx;
|
|
761
947
|
}
|
|
762
948
|
|
|
763
|
-
/**
|
|
764
|
-
* Parse Set-Cookie headers from a response into effective cookie state.
|
|
765
|
-
* Returns a map of cookie name -> value (string) or name -> null (deleted).
|
|
766
|
-
* Last-write-wins: later Set-Cookie entries for the same name overwrite earlier ones.
|
|
767
|
-
* Max-Age=0 is treated as a delete.
|
|
768
|
-
*/
|
|
769
949
|
const MAX_AGE_ZERO_RE = /;\s*Max-Age\s*=\s*0/i;
|
|
770
950
|
|
|
771
951
|
function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
@@ -773,7 +953,6 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
773
953
|
const setCookies = response.headers.getSetCookie();
|
|
774
954
|
|
|
775
955
|
for (const header of setCookies) {
|
|
776
|
-
// First segment before ';' is the name=value pair
|
|
777
956
|
const semiIdx = header.indexOf(";");
|
|
778
957
|
const pair = semiIdx === -1 ? header : header.substring(0, semiIdx);
|
|
779
958
|
const eqIdx = pair.indexOf("=");
|
|
@@ -785,11 +964,9 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
785
964
|
name = decodeURIComponent(pair.substring(0, eqIdx).trim());
|
|
786
965
|
value = decodeURIComponent(pair.substring(eqIdx + 1).trim());
|
|
787
966
|
} catch {
|
|
788
|
-
// Malformed encoding — skip this entry
|
|
789
967
|
continue;
|
|
790
968
|
}
|
|
791
969
|
|
|
792
|
-
// Max-Age=0 means the cookie is being deleted
|
|
793
970
|
const isDeleted = MAX_AGE_ZERO_RE.test(header);
|
|
794
971
|
result.set(name, isDeleted ? null : value);
|
|
795
972
|
}
|
|
@@ -797,10 +974,10 @@ function parseResponseCookies(response: Response): Map<string, string | null> {
|
|
|
797
974
|
return result;
|
|
798
975
|
}
|
|
799
976
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
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(
|
|
804
981
|
cookieHeader: string | null,
|
|
805
982
|
): Record<string, string> {
|
|
806
983
|
if (!cookieHeader) return {};
|
|
@@ -815,7 +992,7 @@ function parseCookiesFromHeader(
|
|
|
815
992
|
try {
|
|
816
993
|
cookies[name] = decodeURIComponent(raw);
|
|
817
994
|
} catch {
|
|
818
|
-
// Malformed percent-
|
|
995
|
+
// Malformed percent-encoding: fall back to raw value
|
|
819
996
|
cookies[name] = raw;
|
|
820
997
|
}
|
|
821
998
|
}
|
|
@@ -824,10 +1001,7 @@ function parseCookiesFromHeader(
|
|
|
824
1001
|
return cookies;
|
|
825
1002
|
}
|
|
826
1003
|
|
|
827
|
-
|
|
828
|
-
* Serialize a cookie for Set-Cookie header
|
|
829
|
-
*/
|
|
830
|
-
function serializeCookieValue(
|
|
1004
|
+
export function serializeCookieValue(
|
|
831
1005
|
name: string,
|
|
832
1006
|
value: string,
|
|
833
1007
|
options: CookieOptions = {},
|
|
@@ -854,20 +1028,12 @@ export interface CreateUseFunctionOptions<TEnv> {
|
|
|
854
1028
|
getContext: () => RequestContext<TEnv>;
|
|
855
1029
|
}
|
|
856
1030
|
|
|
857
|
-
/**
|
|
858
|
-
* Create the use() function for loader and handle composition.
|
|
859
|
-
*
|
|
860
|
-
* This is the unified implementation used by both RequestContext and HandlerContext.
|
|
861
|
-
* - For loaders: executes and memoizes loader functions
|
|
862
|
-
* - For handles: returns a push function to add handle data
|
|
863
|
-
*/
|
|
864
1031
|
export function createUseFunction<TEnv>(
|
|
865
1032
|
options: CreateUseFunctionOptions<TEnv>,
|
|
866
1033
|
): RequestContext["use"] {
|
|
867
1034
|
const { handleStore, loaderPromises, getContext } = options;
|
|
868
1035
|
|
|
869
1036
|
return ((item: LoaderDefinition<any, any> | Handle<any, any>) => {
|
|
870
|
-
// Handle case: return a push function
|
|
871
1037
|
if (isHandle(item)) {
|
|
872
1038
|
const handle = item;
|
|
873
1039
|
const ctx = getContext();
|
|
@@ -880,30 +1046,24 @@ export function createUseFunction<TEnv>(
|
|
|
880
1046
|
);
|
|
881
1047
|
}
|
|
882
1048
|
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
// Push directly - promises will be serialized by RSC and streamed
|
|
894
|
-
handleStore.push(handle.$$id, segmentId, valueOrPromise);
|
|
895
|
-
};
|
|
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
|
+
);
|
|
896
1059
|
}
|
|
897
1060
|
|
|
898
|
-
// Loader case
|
|
899
1061
|
const loader = item as LoaderDefinition<any, any>;
|
|
900
1062
|
|
|
901
|
-
// Return cached promise if already started
|
|
902
1063
|
if (loaderPromises.has(loader.$$id)) {
|
|
903
1064
|
return loaderPromises.get(loader.$$id);
|
|
904
1065
|
}
|
|
905
1066
|
|
|
906
|
-
// Get loader function - either from loader object or fetchable registry
|
|
907
1067
|
let loaderFn = loader.fn;
|
|
908
1068
|
if (!loaderFn) {
|
|
909
1069
|
const fetchable = getFetchableLoader(loader.$$id);
|
|
@@ -920,7 +1080,6 @@ export function createUseFunction<TEnv>(
|
|
|
920
1080
|
|
|
921
1081
|
const ctx = getContext();
|
|
922
1082
|
|
|
923
|
-
// Create loader context with recursive use() support
|
|
924
1083
|
const loaderCtx: LoaderContext<Record<string, string | undefined>, TEnv> = {
|
|
925
1084
|
params: ctx.params,
|
|
926
1085
|
routeParams: (ctx.params ?? {}) as Record<string, string>,
|
|
@@ -929,15 +1088,16 @@ export function createUseFunction<TEnv>(
|
|
|
929
1088
|
search: (ctx as any).search ?? {},
|
|
930
1089
|
pathname: ctx.pathname,
|
|
931
1090
|
url: ctx.url,
|
|
1091
|
+
originalUrl: ctx.originalUrl,
|
|
932
1092
|
env: ctx.env as any,
|
|
933
|
-
|
|
1093
|
+
waitUntil: ctx.waitUntil.bind(ctx),
|
|
1094
|
+
executionContext: ctx.executionContext,
|
|
934
1095
|
get: ctx.get as any,
|
|
935
|
-
use: <TDep, TDepParams = any>(
|
|
1096
|
+
use: (<TDep, TDepParams = any>(
|
|
936
1097
|
dep: LoaderDefinition<TDep, TDepParams>,
|
|
937
1098
|
): Promise<TDep> => {
|
|
938
|
-
// Recursive call - will start dep loader if not already started
|
|
939
1099
|
return ctx.use(dep);
|
|
940
|
-
},
|
|
1100
|
+
}) as LoaderContext["use"],
|
|
941
1101
|
method: "GET",
|
|
942
1102
|
body: undefined,
|
|
943
1103
|
reverse: createReverseFunction(
|
|
@@ -946,6 +1106,12 @@ export function createUseFunction<TEnv>(
|
|
|
946
1106
|
ctx.params as Record<string, string>,
|
|
947
1107
|
ctx._routeName ? isRouteRootScoped(ctx._routeName) : undefined,
|
|
948
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
|
+
},
|
|
949
1115
|
};
|
|
950
1116
|
|
|
951
1117
|
const doneLoader = track(`loader:${loader.$$id}`, 2);
|
|
@@ -953,7 +1119,6 @@ export function createUseFunction<TEnv>(
|
|
|
953
1119
|
doneLoader();
|
|
954
1120
|
});
|
|
955
1121
|
|
|
956
|
-
// Memoize for subsequent calls
|
|
957
1122
|
loaderPromises.set(loader.$$id, promise);
|
|
958
1123
|
|
|
959
1124
|
return promise;
|