@rangojs/router 0.0.0-experimental.e9c0b2f2 → 0.0.0-experimental.ea9f40f2
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 +6 -10
- package/README.md +289 -938
- package/dist/bin/rango.js +271 -46
- package/dist/vite/index.js +673 -193
- package/package.json +10 -8
- package/skills/api-client/SKILL.md +1 -1
- package/skills/breadcrumbs/SKILL.md +31 -14
- package/skills/cache-guide/SKILL.md +5 -2
- package/skills/caching/SKILL.md +59 -4
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/debug-manifest/SKILL.md +1 -1
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +9 -1
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +8 -8
- package/skills/hooks/SKILL.md +54 -892
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +4 -4
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +46 -14
- package/skills/layout/SKILL.md +27 -10
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +23 -1
- package/skills/middleware/SKILL.md +7 -3
- package/skills/migrate-nextjs/SKILL.md +167 -6
- package/skills/migrate-react-router/SKILL.md +59 -677
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +1 -1
- package/skills/observability/SKILL.md +9 -1
- package/skills/parallel/SKILL.md +23 -4
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +28 -18
- package/skills/rango/SKILL.md +84 -25
- package/skills/response-routes/SKILL.md +15 -1
- package/skills/route/SKILL.md +71 -4
- package/skills/router-setup/SKILL.md +14 -3
- package/skills/scripts/SKILL.md +1 -1
- package/skills/server-actions/SKILL.md +3 -2
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +1 -1
- package/skills/testing/SKILL.md +2 -1
- package/skills/testing/handles.md +4 -2
- package/skills/testing/render-handler.md +15 -14
- package/skills/testing/reverse-and-types.md +8 -7
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -919
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +30 -3
- package/skills/vercel/SKILL.md +1 -1
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/browser/event-controller.ts +62 -10
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +65 -16
- package/src/browser/navigation-client.ts +32 -2
- package/src/browser/navigation-store.ts +128 -14
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +76 -17
- package/src/browser/prefetch/cache.ts +51 -11
- package/src/browser/prefetch/fetch.ts +59 -21
- package/src/browser/prefetch/queue.ts +19 -4
- package/src/browser/react/Link.tsx +13 -3
- package/src/browser/react/NavigationProvider.tsx +108 -4
- package/src/browser/response-adapter.ts +38 -9
- package/src/browser/rsc-router.tsx +54 -4
- package/src/browser/scroll-restoration.ts +7 -5
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +22 -10
- package/src/browser/types.ts +54 -1
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/index.ts +3 -1
- package/src/build/route-trie.ts +35 -7
- package/src/build/route-types/include-resolution.ts +347 -47
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-key-utils.ts +29 -0
- package/src/cache/cache-runtime.ts +262 -71
- package/src/cache/cache-scope.ts +2 -17
- package/src/cache/cache-tag.ts +60 -14
- package/src/cache/cf/cf-cache-store.ts +243 -20
- package/src/cache/document-cache.ts +54 -21
- package/src/cache/index.ts +1 -0
- package/src/cache/memory-segment-store.ts +110 -3
- package/src/cache/profile-registry.ts +15 -0
- package/src/cache/read-through-swr.ts +15 -1
- package/src/cache/segment-codec.ts +4 -4
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/types.ts +158 -0
- package/src/cache/vercel/vercel-cache-store.ts +401 -124
- package/src/client.rsc.tsx +0 -3
- package/src/client.tsx +0 -3
- package/src/cloudflare/tracing.ts +7 -8
- package/src/defer.ts +11 -22
- package/src/handle.ts +37 -15
- package/src/handles/MetaTags.tsx +16 -82
- package/src/handles/breadcrumbs.ts +12 -14
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +7 -8
- package/src/handles/meta.ts +7 -44
- package/src/host/errors.ts +15 -0
- package/src/host/index.ts +1 -0
- package/src/index.rsc.ts +8 -2
- package/src/index.ts +19 -13
- package/src/internal-debug.ts +11 -8
- package/src/prerender.ts +17 -4
- package/src/redirect-origin.ts +14 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/route-content-wrapper.tsx +12 -5
- package/src/route-definition/dsl-helpers.ts +21 -32
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +43 -43
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/resolve-handler-use.ts +0 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -4
- package/src/route-types.ts +0 -5
- package/src/router/find-match.ts +86 -8
- package/src/router/instrument.ts +9 -4
- package/src/router/lazy-includes.ts +72 -12
- package/src/router/loader-resolution.ts +14 -2
- package/src/router/manifest.ts +56 -11
- package/src/router/match-api.ts +76 -32
- package/src/router/match-handlers.ts +181 -135
- package/src/router/match-middleware/background-revalidation.ts +40 -23
- package/src/router/match-middleware/cache-store.ts +39 -24
- package/src/router/match-result.ts +35 -15
- package/src/router/middleware.ts +64 -38
- package/src/router/navigation-snapshot.ts +7 -5
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +53 -64
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +11 -5
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +23 -8
- package/src/router/route-snapshot.ts +14 -2
- package/src/router/router-context.ts +3 -1
- package/src/router/router-interfaces.ts +32 -1
- package/src/router/router-options.ts +30 -0
- package/src/router/segment-resolution/fresh.ts +39 -3
- package/src/router/segment-resolution/loader-cache.ts +93 -2
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +3 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +6 -8
- package/src/router/telemetry.ts +9 -1
- package/src/router/tracing.ts +14 -5
- package/src/router/trie-matching.ts +19 -11
- package/src/router/url-params.ts +13 -0
- package/src/router.ts +47 -16
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler.ts +60 -33
- package/src/rsc/manifest-init.ts +1 -1
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/progressive-enhancement.ts +61 -4
- package/src/rsc/redirect-guard.ts +2 -1
- package/src/rsc/rsc-rendering.ts +429 -37
- package/src/rsc/server-action.ts +25 -2
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +30 -0
- package/src/segment-loader-promise.ts +18 -0
- package/src/segment-system.tsx +149 -14
- package/src/server/context.ts +67 -9
- package/src/server/cookie-store.ts +73 -1
- package/src/server/loader-registry.ts +13 -1
- package/src/server/request-context.ts +169 -10
- package/src/ssr/index.tsx +462 -178
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/testing/collect-handle.ts +14 -8
- package/src/testing/dispatch.ts +152 -40
- package/src/testing/generated-routes.ts +27 -11
- package/src/testing/index.ts +6 -0
- package/src/testing/render-handler.ts +14 -0
- package/src/testing/render-route.tsx +13 -10
- package/src/testing/run-transition-when.ts +164 -0
- package/src/theme/ThemeProvider.tsx +36 -26
- package/src/types/handler-context.ts +1 -1
- package/src/types/index.ts +2 -0
- package/src/types/route-config.ts +19 -7
- package/src/types/segments.ts +100 -0
- package/src/urls/include-helper.ts +10 -8
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +44 -12
- package/src/urls/path-helper.ts +5 -0
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +43 -18
- package/src/urls/urls-function.ts +0 -1
- package/src/vercel/tracing.ts +7 -7
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +1 -1
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/index.ts +7 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugins/vercel-output.ts +114 -25
- package/src/vite/plugins/version-injector.ts +22 -7
- package/src/vite/plugins/virtual-entries.ts +80 -22
- package/src/vite/rango.ts +29 -19
- package/src/vite/router-discovery.ts +171 -43
- package/src/vite/utils/prerender-utils.ts +17 -4
- package/src/vite/utils/shared-utils.ts +47 -0
- package/src/network-error-thrower.tsx +0 -18
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import type { CookieOptions } from "../router/middleware-types.js";
|
|
11
11
|
import { getRequestContext, _getRequestContext } from "./request-context.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
isInsideCacheScope,
|
|
14
|
+
getCurrentLoaderBodyId,
|
|
15
|
+
isInsideHandlerInvokedLoaderBody,
|
|
16
|
+
} from "./context.js";
|
|
13
17
|
import { INSIDE_CACHE_EXEC } from "../cache/taint.js";
|
|
14
18
|
|
|
15
19
|
/**
|
|
@@ -62,6 +66,7 @@ export interface CookieStore {
|
|
|
62
66
|
export function cookies(): CookieStore {
|
|
63
67
|
const ctx = getRequestContext();
|
|
64
68
|
assertNotInsideCacheContext(ctx, "cookies");
|
|
69
|
+
assertNotInsideShellCapture(ctx, "cookies");
|
|
65
70
|
return createCookieStore(ctx);
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -132,6 +137,72 @@ function assertNotInsideCacheContext(ctx: unknown, fnName: string): void {
|
|
|
132
137
|
}
|
|
133
138
|
}
|
|
134
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Throw if called during the ACTIVE background shell-capture render
|
|
142
|
+
* (`_shellCaptureRun` true on the derived request context built by
|
|
143
|
+
* shell-capture.ts). The captured shell prelude is shared across every user
|
|
144
|
+
* hitting the URL, so a request-scoped read here would bake one user's
|
|
145
|
+
* cookies/headers into markup served to others — same hazard as the cache
|
|
146
|
+
* scopes above, at the document tier. DSL segment loaders need no exemption:
|
|
147
|
+
* the live lane is masked (never executed) during capture, and the bake lane
|
|
148
|
+
* is exactly what this guard exists for.
|
|
149
|
+
*
|
|
150
|
+
* HANDLER-INVOKED loader bodies (`await ctx.use(Loader)` from a handler) are
|
|
151
|
+
* EXEMPT — the consumption-lane rule: handler consumption yields a BAKED
|
|
152
|
+
* shared copy in every artifact tier, and the cache-purity guards above
|
|
153
|
+
* already permit identity reads there (cache()/"use cache" bake the same
|
|
154
|
+
* reads today). Guarding only the PPR tier made the same code legal under
|
|
155
|
+
* cache() but capture-refusing under ppr (issue #672 / #674). The trade is
|
|
156
|
+
* documented: an identity read in a handler-consumed loader bakes the CAPTURE
|
|
157
|
+
* request's value into the shared shell; client-side consumption (useLoader)
|
|
158
|
+
* is the live lane.
|
|
159
|
+
*
|
|
160
|
+
* Keys off `_shellCaptureRun`, NOT the `_shellCapture` descriptor: the descriptor
|
|
161
|
+
* is also present during the FOREGROUND render (it means "a capture is wanted"),
|
|
162
|
+
* and the foreground must read cookies/headers normally to serve the real user.
|
|
163
|
+
* Only the derived capture context sets `_shellCaptureRun`.
|
|
164
|
+
*
|
|
165
|
+
* Applies only to the READ surfaces (cookies(), headers()) whose values
|
|
166
|
+
* become markup. Response directives (invalidateClientCache(),
|
|
167
|
+
* keepClientCache()) stay callable: during capture they are header effects on
|
|
168
|
+
* a discarded response, and on the live HIT path the full pipeline runs so their
|
|
169
|
+
* headers flow to the client normally.
|
|
170
|
+
*
|
|
171
|
+
* The throw makes such a route PPR-ineligible by construction: the capture
|
|
172
|
+
* render errors, nothing is stored, and every request keeps getting the
|
|
173
|
+
* normal axis-1 render.
|
|
174
|
+
*/
|
|
175
|
+
function assertNotInsideShellCapture(ctx: unknown, fnName: string): void {
|
|
176
|
+
if (
|
|
177
|
+
ctx !== null &&
|
|
178
|
+
typeof ctx === "object" &&
|
|
179
|
+
(ctx as { _shellCaptureRun?: unknown })._shellCaptureRun === true
|
|
180
|
+
) {
|
|
181
|
+
if (isInsideHandlerInvokedLoaderBody()) return;
|
|
182
|
+
// Flag the capture context BEFORE throwing: inside an executing bake-lane
|
|
183
|
+
// loader this throw is swallowed by wrapLoaderPromise into per-loader error
|
|
184
|
+
// UI, which would bake silently into the shared shell. The capture checks
|
|
185
|
+
// the flag after the render and refuses (shell-capture.ts). Also record
|
|
186
|
+
// WHICH loader body (if any) made the read, so the refusal warning can
|
|
187
|
+
// name the real source instead of hardcoding a lane — the read may come
|
|
188
|
+
// from a bake-lane loader OR from handler/render code (issue #672).
|
|
189
|
+
(ctx as { _shellCaptureGuardTripped?: string })._shellCaptureGuardTripped =
|
|
190
|
+
fnName;
|
|
191
|
+
(
|
|
192
|
+
ctx as { _shellCaptureGuardTrippedLoaderId?: string }
|
|
193
|
+
)._shellCaptureGuardTrippedLoaderId = getCurrentLoaderBodyId();
|
|
194
|
+
throw new Error(
|
|
195
|
+
`${fnName}() cannot be called while capturing a shared shell ` +
|
|
196
|
+
`(shell-cache middleware). The captured shell is served to every user ` +
|
|
197
|
+
`of this URL, so request-scoped data read here would leak one user's ` +
|
|
198
|
+
`${fnName === "cookies" ? "cookies" : "headers"} to others. Read it ` +
|
|
199
|
+
`inside a loader instead — loaders are never captured and always run ` +
|
|
200
|
+
`fresh per request:\n\n` +
|
|
201
|
+
` loader("user", () => getUser(cookies().get("session")?.value));`,
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
135
206
|
const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
|
|
136
207
|
|
|
137
208
|
/**
|
|
@@ -152,6 +223,7 @@ const HEADERS_MUTATION_METHODS = new Set(["set", "append", "delete"]);
|
|
|
152
223
|
export function headers(): ReadonlyHeaders {
|
|
153
224
|
const ctx = getRequestContext();
|
|
154
225
|
assertNotInsideCacheContext(ctx, "headers");
|
|
226
|
+
assertNotInsideShellCapture(ctx, "headers");
|
|
155
227
|
return new Proxy(ctx.request.headers, {
|
|
156
228
|
get(target, prop, receiver) {
|
|
157
229
|
if (typeof prop === "string" && HEADERS_MUTATION_METHODS.has(prop)) {
|
|
@@ -67,7 +67,19 @@ export async function getLoaderLazy(
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
//
|
|
70
|
+
// The remaining dev fallback (parse the id as "src/path/file.ts#ExportName"
|
|
71
|
+
// and import it by path) only makes sense in dev, where ids ARE file paths
|
|
72
|
+
// and the dev loader manifest is intentionally empty. In production ids are
|
|
73
|
+
// hashed ("<hash>#ExportName") and every resolvable loader is reached above
|
|
74
|
+
// via the in-memory registry or the lazy import manifest. The hash is not a
|
|
75
|
+
// path, so a production fall-through would run import("/<hash>") and throw a
|
|
76
|
+
// misleading "No such module <hash>" 500 instead of reporting the loader as
|
|
77
|
+
// unregistered. Return undefined in production so a genuinely unknown loader
|
|
78
|
+
// is a clean 404 "not found in registry" from handleLoaderFetch.
|
|
79
|
+
if (process.env.NODE_ENV === "production") {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
|
|
71
83
|
const hashIndex = id.indexOf("#");
|
|
72
84
|
if (hashIndex !== -1) {
|
|
73
85
|
const filePath = id.slice(0, hashIndex);
|
|
@@ -48,8 +48,8 @@ import { getFetchableLoader } from "./fetchable-loader-store.js";
|
|
|
48
48
|
import type { SegmentCacheStore } from "../cache/types.js";
|
|
49
49
|
import type { Theme, ResolvedThemeConfig } from "../theme/types.js";
|
|
50
50
|
import type { ExecutionContext, RequestScope } from "../types/request-scope.js";
|
|
51
|
+
import type { TransitionWhenFn } from "../types/segments.js";
|
|
51
52
|
import type { ResolvedTracing } from "../router/tracing.js";
|
|
52
|
-
import { fireAndForgetWaitUntil } from "../types/request-scope.js";
|
|
53
53
|
import {
|
|
54
54
|
THEME_COOKIE,
|
|
55
55
|
isValidTheme,
|
|
@@ -161,9 +161,75 @@ export interface RequestContext<
|
|
|
161
161
|
/** @internal Handle store for tracking handle data across segments */
|
|
162
162
|
_handleStore: HandleStore;
|
|
163
163
|
|
|
164
|
+
/**
|
|
165
|
+
* @internal transition({ when }) predicates for segments matched this request,
|
|
166
|
+
* keyed by segment id. Collected during resolution (the function is stripped
|
|
167
|
+
* from the serialized segment config), then evaluated post-handler in
|
|
168
|
+
* rsc-rendering — outside any cache scope — to drop the transition of any
|
|
169
|
+
* segment whose predicate returns false.
|
|
170
|
+
*/
|
|
171
|
+
_transitionWhen?: Array<{ id: string; when: TransitionWhenFn }>;
|
|
172
|
+
|
|
164
173
|
/** @internal Cache store for segment caching (optional, used by CacheScope) */
|
|
165
174
|
_cacheStore?: SegmentCacheStore;
|
|
166
175
|
|
|
176
|
+
/**
|
|
177
|
+
* @internal PPR shell-capture ACTIVE marker. True ONLY inside the background
|
|
178
|
+
* capture task's derived request context (built by shell-capture.ts). This is
|
|
179
|
+
* the switch every capture-specific behavior reads: loader masking
|
|
180
|
+
* (loader-mask.ts isShellCaptureActive / fresh.ts emitStreaming) and the
|
|
181
|
+
* cookies()/headers() capture guard (cookie-store.ts
|
|
182
|
+
* assertNotInsideShellCapture). The foreground render never sets it, so the
|
|
183
|
+
* served response is byte-identical to axis 1. The capture descriptor itself
|
|
184
|
+
* (key/ttl/swr/tags/store) is NOT threaded through the request context — the
|
|
185
|
+
* integrated PPR serve path (rsc/shell-serve.ts + rsc-rendering.ts) builds it
|
|
186
|
+
* locally and passes it to scheduleShellCapture directly.
|
|
187
|
+
*/
|
|
188
|
+
_shellCaptureRun?: boolean;
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @internal Bake-lane loader containers collected DURING a shell capture:
|
|
192
|
+
* segment-key -> the loader's (pre-wrap) result promise. Populated by
|
|
193
|
+
* resolveLoaderData for loaders on entries with no renderable loading() (the
|
|
194
|
+
* bake lane — they execute at capture instead of being masked; see
|
|
195
|
+
* docs/design/loader-container-bake.md). Drained by captureAndStoreShell
|
|
196
|
+
* after the shell quiesces: settled containers are promise-elided,
|
|
197
|
+
* Flight-serialized, and pinned into the snapshot's loader family; a
|
|
198
|
+
* REJECTED container refuses the capture (error UI must never bake into the
|
|
199
|
+
* shared shell). Own property of the capture's derived context only.
|
|
200
|
+
*/
|
|
201
|
+
_shellCaptureLoaderRecords?: Map<string, Promise<unknown>>;
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* @internal Loader-family snapshot seed for a shell HIT's tail render:
|
|
205
|
+
* segment-key -> the capture's elided container (already Flight-deserialized
|
|
206
|
+
* by serveShellHit). resolveLoaderData overlays it onto the fresh run's
|
|
207
|
+
* container (recorded paths pinned, hole-marker paths keep the fresh nested
|
|
208
|
+
* promises) so the payload's baked bytes match the frozen prelude. Own
|
|
209
|
+
* property of the HIT tail's derived context only.
|
|
210
|
+
*/
|
|
211
|
+
_shellLoaderSeed?: Map<string, unknown>;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @internal Set (to the offending fn name) by the cookies()/headers()
|
|
215
|
+
* capture guard when it throws DURING a capture render. Load-bearing for the
|
|
216
|
+
* bake lane: a guard throw inside an executing loader is swallowed by
|
|
217
|
+
* wrapLoaderPromise into per-loader error UI, which would otherwise bake
|
|
218
|
+
* silently into the shared shell — the capture checks this flag after the
|
|
219
|
+
* render and refuses instead. Deterministic, so the capture does not retry.
|
|
220
|
+
*/
|
|
221
|
+
_shellCaptureGuardTripped?: string;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @internal The loader $$id whose BODY was executing when the capture guard
|
|
225
|
+
* tripped (read off the loader-body ALS scope at trip time), or undefined
|
|
226
|
+
* when the read came from handler/render code. Only used to make the
|
|
227
|
+
* once-per-key refusal warning name the real source — the old text
|
|
228
|
+
* hardcoded "a bake-lane loader", which misattributed handler-land reads
|
|
229
|
+
* and sent users debugging the wrong lane (issue #672, secondary).
|
|
230
|
+
*/
|
|
231
|
+
_shellCaptureGuardTrippedLoaderId?: string;
|
|
232
|
+
|
|
167
233
|
/**
|
|
168
234
|
* @internal Handler-owned registry of explicit per-scope stores from
|
|
169
235
|
* cache({ store }). Created once per createRSCHandler() and threaded into
|
|
@@ -211,6 +277,19 @@ export interface RequestContext<
|
|
|
211
277
|
/** @internal Registered onResponse callbacks */
|
|
212
278
|
_onResponseCallbacks: Array<(response: Response) => Response>;
|
|
213
279
|
|
|
280
|
+
/**
|
|
281
|
+
* @internal Promises of the background tasks scheduled via this context's
|
|
282
|
+
* waitUntil (deferred cache writes, revalidations, consumer tasks). The PPR
|
|
283
|
+
* shell capture drains this list BEFORE its match/render as an ORDERING EDGE:
|
|
284
|
+
* every foreground deferred cache write is scheduled here before the capture
|
|
285
|
+
* task is, so settling the list first guarantees the capture's cache reads
|
|
286
|
+
* observe the foreground's generation instead of racing it (see
|
|
287
|
+
* shell-capture.ts). Tasks whose scheduling fn carries
|
|
288
|
+
* UNTRACKED_BACKGROUND_TASK are not tracked (the capture task itself —
|
|
289
|
+
* tracking it would make that drain await its own promise).
|
|
290
|
+
*/
|
|
291
|
+
_pendingBackgroundTasks?: Array<Promise<unknown>>;
|
|
292
|
+
|
|
214
293
|
/**
|
|
215
294
|
* Current theme setting (only available when theme is enabled in router config)
|
|
216
295
|
*
|
|
@@ -292,6 +371,30 @@ export interface RequestContext<
|
|
|
292
371
|
/** @internal Previous route key (from the navigation source), used for revalidation */
|
|
293
372
|
_prevRouteKey?: string;
|
|
294
373
|
|
|
374
|
+
/**
|
|
375
|
+
* @internal Navigation/action source data the transition({ when }) gate reads
|
|
376
|
+
* to build its ShouldRevalidateFn-shaped predicate context. currentUrl/Params
|
|
377
|
+
* come from the navigation snapshot (set at match time); action* are stashed
|
|
378
|
+
* at the action-bearing gate call sites. All undefined when there is no source
|
|
379
|
+
* (initial full load) or no action (plain navigation).
|
|
380
|
+
*/
|
|
381
|
+
_gateCurrentUrl?: URL;
|
|
382
|
+
_gateCurrentParams?: Record<string, string>;
|
|
383
|
+
_gateActionId?: string;
|
|
384
|
+
_gateActionUrl?: URL;
|
|
385
|
+
_gateActionResult?: unknown;
|
|
386
|
+
_gateFormData?: FormData;
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* @internal True while the post-action revalidation render is running (set by
|
|
390
|
+
* revalidateAfterAction). The "use cache" runtime reads this to prefer
|
|
391
|
+
* freshness over a fast stale response during an action: a stale entry
|
|
392
|
+
* re-executes in the foreground (so the action response reflects the refreshed
|
|
393
|
+
* value) with only the store write deferred, instead of serving stale and
|
|
394
|
+
* revalidating in the background. A plain navigation (flag unset) keeps SWR.
|
|
395
|
+
*/
|
|
396
|
+
_inActionRevalidation?: boolean;
|
|
397
|
+
|
|
295
398
|
/**
|
|
296
399
|
* @internal Render barrier for experimental `rendered()` API.
|
|
297
400
|
* Resolves when all non-loader segments have settled and handle data
|
|
@@ -375,6 +478,15 @@ export interface RequestContext<
|
|
|
375
478
|
/** @internal Request-scoped performance metrics store */
|
|
376
479
|
_metricsStore?: MetricsStore;
|
|
377
480
|
|
|
481
|
+
/**
|
|
482
|
+
* @internal True request entry timestamp (performance.now() at handler entry).
|
|
483
|
+
* Set once at request-context creation (rsc/handler.ts) so a metrics store
|
|
484
|
+
* created MID-request — ctx.debugPerformance() or the getMetricsStore wrapper —
|
|
485
|
+
* anchors its timeline to the real request start instead of the opt-in moment,
|
|
486
|
+
* keeping phases that began before the opt-in at their true (non-negative) offset.
|
|
487
|
+
*/
|
|
488
|
+
_handlerStart?: number;
|
|
489
|
+
|
|
378
490
|
/** @internal Resolved platform phase-span tracing for this request (Cloudflare or OTel) */
|
|
379
491
|
_tracing?: ResolvedTracing;
|
|
380
492
|
|
|
@@ -413,7 +525,10 @@ export type PublicRequestContext<
|
|
|
413
525
|
| "setCookie"
|
|
414
526
|
| "deleteCookie"
|
|
415
527
|
| "_handleStore"
|
|
528
|
+
| "_transitionWhen"
|
|
416
529
|
| "_cacheStore"
|
|
530
|
+
| "_shellCaptureRun"
|
|
531
|
+
| "_shellCaptureGuardTrippedLoaderId"
|
|
417
532
|
| "_explicitTaggedStores"
|
|
418
533
|
| "_requestTags"
|
|
419
534
|
| "_cacheProfiles"
|
|
@@ -422,6 +537,13 @@ export type PublicRequestContext<
|
|
|
422
537
|
| "_locationState"
|
|
423
538
|
| "_routeName"
|
|
424
539
|
| "_prevRouteKey"
|
|
540
|
+
| "_gateCurrentUrl"
|
|
541
|
+
| "_gateCurrentParams"
|
|
542
|
+
| "_gateActionId"
|
|
543
|
+
| "_gateActionUrl"
|
|
544
|
+
| "_gateActionResult"
|
|
545
|
+
| "_gateFormData"
|
|
546
|
+
| "_inActionRevalidation"
|
|
425
547
|
| "_reportedErrors"
|
|
426
548
|
| "_renderBarrier"
|
|
427
549
|
| "_resolveRenderBarrier"
|
|
@@ -434,6 +556,7 @@ export type PublicRequestContext<
|
|
|
434
556
|
| "_reportBackgroundError"
|
|
435
557
|
| "_debugPerformance"
|
|
436
558
|
| "_metricsStore"
|
|
559
|
+
| "_handlerStart"
|
|
437
560
|
| "_basename"
|
|
438
561
|
| "_setStatus"
|
|
439
562
|
| "_rotateStateCookie"
|
|
@@ -444,6 +567,17 @@ export type PublicRequestContext<
|
|
|
444
567
|
| "res"
|
|
445
568
|
>;
|
|
446
569
|
|
|
570
|
+
/**
|
|
571
|
+
* Marker for a waitUntil-scheduled fn whose task promise must NOT enter
|
|
572
|
+
* _pendingBackgroundTasks. Used by the PPR shell capture for its own task:
|
|
573
|
+
* the capture's pre-render write barrier settles that list, so tracking the
|
|
574
|
+
* capture itself would make the drain wait on its own (still-running) promise.
|
|
575
|
+
* @internal
|
|
576
|
+
*/
|
|
577
|
+
export const UNTRACKED_BACKGROUND_TASK: unique symbol = Symbol.for(
|
|
578
|
+
"rango.untrackedBackgroundTask",
|
|
579
|
+
);
|
|
580
|
+
|
|
447
581
|
// AsyncLocalStorage instance for request context
|
|
448
582
|
const requestContextStorage = new AsyncLocalStorage<RequestContext<any>>();
|
|
449
583
|
|
|
@@ -527,11 +661,17 @@ export function setRequestContextParams(
|
|
|
527
661
|
*/
|
|
528
662
|
export function setRequestContextPrevRouteKey(
|
|
529
663
|
prevRouteKey: string | undefined,
|
|
664
|
+
currentUrl?: URL,
|
|
665
|
+
currentParams?: Record<string, string>,
|
|
530
666
|
): void {
|
|
531
667
|
const ctx = requestContextStorage.getStore();
|
|
532
|
-
if (ctx
|
|
533
|
-
|
|
534
|
-
}
|
|
668
|
+
if (!ctx) return;
|
|
669
|
+
if (prevRouteKey !== undefined) ctx._prevRouteKey = prevRouteKey;
|
|
670
|
+
// Source URL/params for the transition({ when }) gate (effectiveFromUrl /
|
|
671
|
+
// effectiveFromMatch.params from the navigation snapshot). Same write point as
|
|
672
|
+
// _prevRouteKey, which doubles as fromRouteName.
|
|
673
|
+
if (currentUrl !== undefined) ctx._gateCurrentUrl = currentUrl;
|
|
674
|
+
if (currentParams !== undefined) ctx._gateCurrentParams = currentParams;
|
|
535
675
|
}
|
|
536
676
|
|
|
537
677
|
/**
|
|
@@ -842,26 +982,44 @@ export function createRequestContext<TEnv>(
|
|
|
842
982
|
method: request.method,
|
|
843
983
|
|
|
844
984
|
_handleStore: handleStore,
|
|
985
|
+
_transitionWhen: [],
|
|
845
986
|
_cacheStore: cacheStore,
|
|
846
987
|
_explicitTaggedStores: explicitTaggedStores,
|
|
847
988
|
_requestTags: new Set<string>(),
|
|
848
989
|
_cacheProfiles: cacheProfiles,
|
|
849
990
|
|
|
850
991
|
waitUntil(fn: () => Promise<void>): void {
|
|
992
|
+
// Wrap in Promise.resolve().then(fn) so a SYNCHRONOUS throw in a
|
|
993
|
+
// non-async callback becomes a rejected promise handed to the host's
|
|
994
|
+
// waitUntil (logged as a background failure), instead of escaping into
|
|
995
|
+
// the request flow. Mirrors fireAndForgetWaitUntil's deferral.
|
|
996
|
+
const task = Promise.resolve().then(fn);
|
|
997
|
+
// Track the task promise so the PPR shell capture can settle the
|
|
998
|
+
// foreground's deferred cache writes before its own match/render (the
|
|
999
|
+
// ordering edge; see _pendingBackgroundTasks). The capture task itself
|
|
1000
|
+
// opts out via the marker — the drain must never await its own promise.
|
|
1001
|
+
if (
|
|
1002
|
+
!(fn as { [UNTRACKED_BACKGROUND_TASK]?: boolean })[
|
|
1003
|
+
UNTRACKED_BACKGROUND_TASK
|
|
1004
|
+
]
|
|
1005
|
+
) {
|
|
1006
|
+
ctx._pendingBackgroundTasks?.push(task);
|
|
1007
|
+
}
|
|
851
1008
|
if (executionContext?.waitUntil) {
|
|
852
|
-
|
|
853
|
-
// non-async callback becomes a rejected promise handed to the host's
|
|
854
|
-
// waitUntil (logged as a background failure), instead of escaping into
|
|
855
|
-
// the request flow. Mirrors fireAndForgetWaitUntil's deferral.
|
|
856
|
-
executionContext.waitUntil(Promise.resolve().then(fn));
|
|
1009
|
+
executionContext.waitUntil(task);
|
|
857
1010
|
} else {
|
|
858
|
-
|
|
1011
|
+
// Node/dev fallback: fire-and-forget with error logging (the same
|
|
1012
|
+
// policy fireAndForgetWaitUntil applies).
|
|
1013
|
+
task.catch((err) =>
|
|
1014
|
+
console.error("[waitUntil] Background task failed:", err),
|
|
1015
|
+
);
|
|
859
1016
|
}
|
|
860
1017
|
},
|
|
861
1018
|
|
|
862
1019
|
executionContext,
|
|
863
1020
|
|
|
864
1021
|
_onResponseCallbacks: [],
|
|
1022
|
+
_pendingBackgroundTasks: [],
|
|
865
1023
|
|
|
866
1024
|
onResponse(callback: (response: Response) => Response): void {
|
|
867
1025
|
assertNotInsideCacheExec(ctx, "onResponse");
|
|
@@ -891,6 +1049,7 @@ export function createRequestContext<TEnv>(
|
|
|
891
1049
|
|
|
892
1050
|
_reportedErrors: new WeakSet<object>(),
|
|
893
1051
|
_metricsStore: undefined,
|
|
1052
|
+
_handlerStart: undefined,
|
|
894
1053
|
|
|
895
1054
|
_renderBarrier: null as any,
|
|
896
1055
|
_resolveRenderBarrier: null as any,
|