@rangojs/router 0.0.0-experimental.124 → 0.0.0-experimental.126
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 +6 -4
- package/dist/bin/rango.js +3 -4
- package/dist/vite/index.js +315 -68
- package/package.json +19 -18
- package/skills/breadcrumbs/SKILL.md +60 -0
- package/skills/hooks/SKILL.md +2 -2
- package/skills/route/SKILL.md +6 -0
- package/skills/server-actions/SKILL.md +25 -1
- package/skills/testing/SKILL.md +17 -17
- package/skills/testing/cache-prerender.md +29 -3
- package/skills/testing/flight.md +13 -10
- package/skills/testing/render-handler.md +3 -0
- package/skills/testing/server-tree.md +1 -1
- package/skills/testing/setup.md +1 -1
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +10 -0
- package/src/browser/event-controller.ts +1 -83
- package/src/browser/navigation-store-handle.ts +3 -4
- package/src/browser/navigation-store.ts +0 -39
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/partial-update.ts +23 -84
- package/src/browser/prefetch/cache.ts +6 -45
- package/src/browser/prefetch/queue.ts +6 -3
- package/src/browser/rango-state.ts +2 -23
- package/src/browser/react/Link.tsx +0 -2
- package/src/browser/react/NavigationProvider.tsx +2 -1
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +0 -2
- package/src/browser/react/index.ts +0 -45
- package/src/browser/react/location-state-shared.ts +0 -13
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +0 -3
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +2 -1
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/rsc-router.tsx +10 -3
- package/src/browser/server-action-bridge.ts +51 -3
- package/src/browser/types.ts +23 -5
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/index.ts +8 -9
- package/src/build/route-trie.ts +46 -11
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/router-processing.ts +0 -8
- package/src/cache/cache-policy.ts +0 -54
- package/src/cache/cache-runtime.ts +48 -24
- package/src/cache/cache-scope.ts +0 -27
- package/src/cache/cache-tag.ts +0 -37
- package/src/cache/cf/cf-cache-store.ts +72 -45
- package/src/cache/cf/index.ts +0 -24
- package/src/cache/document-cache.ts +10 -36
- package/src/cache/handle-snapshot.ts +0 -40
- package/src/cache/index.ts +0 -27
- package/src/cache/memory-segment-store.ts +0 -52
- 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/types.ts +0 -98
- package/src/client.rsc.tsx +4 -22
- package/src/client.tsx +19 -32
- package/src/context-var.ts +12 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/handle.ts +2 -12
- 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 +6 -0
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +0 -16
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +0 -4
- package/src/index.rsc.ts +27 -2
- package/src/index.ts +7 -0
- package/src/internal-debug.ts +2 -4
- package/src/loader.rsc.ts +4 -15
- package/src/loader.ts +3 -9
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +23 -30
- package/src/prerender.ts +34 -0
- package/src/redirect-origin.ts +100 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +1 -44
- package/src/route-definition/dsl-helpers.ts +7 -19
- package/src/route-definition/helpers-types.ts +3 -3
- package/src/route-definition/redirect.ts +43 -9
- package/src/route-definition/resolve-handler-use.ts +6 -0
- package/src/route-map-builder.ts +0 -16
- package/src/router/content-negotiation.ts +0 -13
- package/src/router/error-handling.ts +12 -16
- package/src/router/find-match.ts +4 -31
- package/src/router/intercept-resolution.ts +10 -1
- package/src/router/lazy-includes.ts +1 -57
- package/src/router/loader-resolution.ts +25 -23
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +1 -25
- package/src/router/match-api.ts +0 -20
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +0 -43
- package/src/router/match-middleware/background-revalidation.ts +0 -7
- package/src/router/match-middleware/cache-lookup.ts +96 -179
- package/src/router/match-middleware/cache-store.ts +0 -31
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +1 -52
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +77 -60
- package/src/router/navigation-snapshot.ts +0 -51
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +5 -56
- package/src/router/prerender-match.ts +56 -51
- package/src/router/request-classification.ts +1 -38
- package/src/router/revalidation.ts +14 -62
- package/src/router/route-snapshot.ts +0 -1
- package/src/router/router-context.ts +0 -27
- package/src/router/router-interfaces.ts +10 -0
- package/src/router/segment-resolution/fresh.ts +25 -57
- package/src/router/segment-resolution/helpers.ts +34 -0
- package/src/router/segment-resolution/loader-cache.ts +35 -23
- package/src/router/segment-resolution/revalidation.ts +188 -283
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +0 -3
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +0 -22
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +66 -45
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +0 -5
- package/src/router.ts +8 -11
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +20 -4
- package/src/rsc/helpers.ts +71 -3
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/origin-guard.ts +9 -15
- package/src/rsc/progressive-enhancement.ts +10 -1
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-route-handler.ts +23 -18
- package/src/rsc/rsc-rendering.ts +2 -7
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +34 -29
- package/src/rsc/types.ts +6 -3
- package/src/search-params.ts +0 -16
- package/src/segment-loader-promise.ts +14 -2
- package/src/segment-system.tsx +79 -88
- package/src/server/handle-store.ts +7 -24
- package/src/server/loader-registry.ts +5 -24
- package/src/server/request-context.ts +29 -92
- package/src/ssr/index.tsx +14 -14
- package/src/static-handler.ts +2 -27
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +1 -24
- package/src/testing/dispatch.ts +43 -6
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/flight-matchers.ts +0 -13
- package/src/testing/flight-normalize.ts +3 -30
- package/src/testing/flight.ts +46 -48
- package/src/testing/generated-routes.ts +1 -41
- package/src/testing/index.ts +1 -21
- package/src/testing/internal/context.ts +3 -45
- package/src/testing/internal/seed-vars.ts +0 -26
- package/src/testing/render-handler.ts +31 -61
- package/src/testing/render-route.tsx +75 -103
- package/src/testing/run-loader.ts +0 -96
- package/src/testing/run-middleware.ts +0 -26
- 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/error-types.ts +25 -89
- package/src/types/global-namespace.ts +4 -14
- package/src/types/handler-context.ts +28 -9
- package/src/types/index.ts +0 -10
- package/src/types/request-scope.ts +0 -19
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +0 -13
- package/src/urls/include-helper.ts +0 -4
- package/src/urls/index.ts +0 -6
- package/src/urls/path-helper-types.ts +2 -2
- package/src/urls/path-helper.ts +0 -54
- package/src/urls/urls-function.ts +0 -13
- package/src/use-loader.tsx +0 -186
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/discover-routers.ts +28 -18
- package/src/vite/discovery/prerender-collection.ts +2 -4
- package/src/vite/discovery/state.ts +5 -0
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/plugin-types.ts +35 -9
- package/src/vite/plugins/cjs-to-esm.ts +0 -11
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +0 -55
- package/src/vite/plugins/expose-ids/export-analysis.ts +0 -38
- package/src/vite/plugins/expose-ids/handler-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/loader-transform.ts +0 -15
- package/src/vite/plugins/expose-ids/router-transform.ts +0 -13
- package/src/vite/plugins/expose-internal-ids.ts +10 -0
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/version-injector.ts +0 -20
- package/src/vite/plugins/version-plugin.ts +1 -49
- package/src/vite/plugins/virtual-entries.ts +0 -15
- package/src/vite/rango.ts +2 -108
- package/src/vite/router-discovery.ts +9 -1
- package/src/vite/utils/ast-handler-extract.ts +0 -16
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +0 -4
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +0 -35
- package/src/vite/utils/shared-utils.ts +3 -35
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -5,13 +5,13 @@ import {
|
|
|
5
5
|
runWithRequestContext,
|
|
6
6
|
type RequestContext,
|
|
7
7
|
} from "../server/request-context.js";
|
|
8
|
-
import { contextGet, contextSet } from "../context-var.js";
|
|
8
|
+
import { contextGet, contextSet, hasContextVars } from "../context-var.js";
|
|
9
9
|
import {
|
|
10
10
|
createPrerenderContext,
|
|
11
11
|
createStaticContext,
|
|
12
12
|
createReverseFunction,
|
|
13
13
|
} from "./handler-context.js";
|
|
14
|
-
import {
|
|
14
|
+
import { detectPrerenderPassthrough } from "../prerender.js";
|
|
15
15
|
import { isRouteRootScoped } from "../route-map-builder.js";
|
|
16
16
|
import { setupBuildUse } from "./loader-resolution.js";
|
|
17
17
|
import { loadManifest } from "./manifest.js";
|
|
@@ -82,6 +82,17 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
82
82
|
// Build RouterContext for loadManifest/traverseBack
|
|
83
83
|
const routerCtx = deps.buildRouterContext();
|
|
84
84
|
|
|
85
|
+
// Passthrough sentinel result: an unknown-param Passthrough route falls
|
|
86
|
+
// through to the live handler at runtime, so no artifact is baked. A fresh
|
|
87
|
+
// object is returned per call (no site mutates or identity-compares it).
|
|
88
|
+
const passthroughResult = () => ({
|
|
89
|
+
segments: [],
|
|
90
|
+
handles: "",
|
|
91
|
+
routeName: matched.routeKey,
|
|
92
|
+
params: matchedParams,
|
|
93
|
+
passthrough: true as const,
|
|
94
|
+
});
|
|
95
|
+
|
|
85
96
|
return runWithRouterContext(routerCtx, async () => {
|
|
86
97
|
// 2. Load the manifest entry tree
|
|
87
98
|
const manifestEntry = await loadManifest(
|
|
@@ -145,19 +156,10 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
145
156
|
);
|
|
146
157
|
});
|
|
147
158
|
if (!isKnown) {
|
|
148
|
-
return
|
|
149
|
-
segments: [],
|
|
150
|
-
handles: "",
|
|
151
|
-
routeName: matched.routeKey,
|
|
152
|
-
params: matchedParams,
|
|
153
|
-
passthrough: true as const,
|
|
154
|
-
};
|
|
159
|
+
return passthroughResult();
|
|
155
160
|
}
|
|
156
161
|
// Preserve vars set by getParams() for the render context
|
|
157
|
-
if (
|
|
158
|
-
Object.keys(probeBuildVars).length > 0 ||
|
|
159
|
-
Object.getOwnPropertySymbols(probeBuildVars).length > 0
|
|
160
|
-
) {
|
|
162
|
+
if (hasContextVars(probeBuildVars)) {
|
|
161
163
|
devProbeBuildVars = probeBuildVars;
|
|
162
164
|
}
|
|
163
165
|
} catch (err: any) {
|
|
@@ -165,13 +167,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
165
167
|
// Skip errors are intentional — treat as passthrough.
|
|
166
168
|
// All other errors propagate so dev surfaces them.
|
|
167
169
|
if (err?.name === "Skip") {
|
|
168
|
-
return
|
|
169
|
-
segments: [],
|
|
170
|
-
handles: "",
|
|
171
|
-
routeName: matched.routeKey,
|
|
172
|
-
params: matchedParams,
|
|
173
|
-
passthrough: true as const,
|
|
174
|
-
};
|
|
170
|
+
return passthroughResult();
|
|
175
171
|
}
|
|
176
172
|
throw err;
|
|
177
173
|
}
|
|
@@ -264,17 +260,13 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
264
260
|
{ skipLoaders: true },
|
|
265
261
|
);
|
|
266
262
|
|
|
267
|
-
// 9. Detect passthrough sentinel: handler returned ctx.passthrough()
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
params: matchedParams,
|
|
275
|
-
passthrough: true as const,
|
|
276
|
-
};
|
|
277
|
-
}
|
|
263
|
+
// 9. Detect passthrough sentinel: handler returned ctx.passthrough().
|
|
264
|
+
// When the route declares loading(), the handler result is deferred so the
|
|
265
|
+
// component is a thenable resolving to the sentinel — detectPrerenderPassthrough
|
|
266
|
+
// resolves thenables before testing (a sync check would miss it and bake a
|
|
267
|
+
// corrupt artifact).
|
|
268
|
+
if (await detectPrerenderPassthrough(allSegments)) {
|
|
269
|
+
return passthroughResult();
|
|
278
270
|
}
|
|
279
271
|
|
|
280
272
|
// 10. Filter out any loader segments (belt-and-suspenders)
|
|
@@ -319,24 +311,14 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
319
311
|
}[] = [];
|
|
320
312
|
let current: EntryData | null = manifestEntry;
|
|
321
313
|
while (current) {
|
|
322
|
-
|
|
323
|
-
|
|
314
|
+
// Flatten the entry and its sibling layouts into one source list, the
|
|
315
|
+
// same traversal findInterceptForRoute uses; the build keeps ALL matches
|
|
316
|
+
// (not just the innermost) and skips when(). intercept/layout are
|
|
317
|
+
// non-optional arrays, so empty ones are a no-op here.
|
|
318
|
+
for (const source of [current, ...current.layout]) {
|
|
319
|
+
for (const ic of source.intercept) {
|
|
324
320
|
if (ic.routeName === matched.routeKey) {
|
|
325
|
-
foundIntercepts.push({ intercept: ic, entry:
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
if (current.layout && current.layout.length > 0) {
|
|
330
|
-
for (const siblingLayout of current.layout) {
|
|
331
|
-
if (siblingLayout.intercept && siblingLayout.intercept.length > 0) {
|
|
332
|
-
for (const ic of siblingLayout.intercept) {
|
|
333
|
-
if (ic.routeName === matched.routeKey) {
|
|
334
|
-
foundIntercepts.push({
|
|
335
|
-
intercept: ic,
|
|
336
|
-
entry: siblingLayout,
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
321
|
+
foundIntercepts.push({ intercept: ic, entry: source });
|
|
340
322
|
}
|
|
341
323
|
}
|
|
342
324
|
}
|
|
@@ -347,6 +329,18 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
347
329
|
const interceptResolvedSegments: typeof nonLoaderSegments = [];
|
|
348
330
|
|
|
349
331
|
for (const { intercept, entry: parentEntry } of foundIntercepts) {
|
|
332
|
+
// setupBuildUse keys handle pushes by ctx._currentSegmentId. The main
|
|
333
|
+
// resolveAllSegments pass left it on the route's segment id, so pin it
|
|
334
|
+
// to THIS intercept's slot id before resolving the handler/layout --
|
|
335
|
+
// otherwise the intercept's ctx.use() handle pushes land in the wrong
|
|
336
|
+
// bucket and getDataForSegment(seg.id) below drops them from the baked
|
|
337
|
+
// artifact. Re-pinned per iteration so multiple intercepts targeting
|
|
338
|
+
// the same route each get their own id (mirrors fresh.ts parallel-slot
|
|
339
|
+
// pinning).
|
|
340
|
+
const interceptSegmentId = `${parentEntry.shortCode}.${intercept.slotName}`;
|
|
341
|
+
(buildCtx as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
342
|
+
interceptSegmentId;
|
|
343
|
+
|
|
350
344
|
// Resolve handler
|
|
351
345
|
const handlerRaw =
|
|
352
346
|
typeof intercept.handler === "function"
|
|
@@ -373,7 +367,7 @@ export async function matchForPrerender<TEnv = any>(
|
|
|
373
367
|
}
|
|
374
368
|
|
|
375
369
|
interceptResolvedSegments.push({
|
|
376
|
-
id:
|
|
370
|
+
id: interceptSegmentId,
|
|
377
371
|
namespace: `intercept:${intercept.routeName}`,
|
|
378
372
|
type: "parallel" as const,
|
|
379
373
|
index: 0,
|
|
@@ -503,14 +497,25 @@ export async function renderStaticSegment<TEnv = any>(
|
|
|
503
497
|
setupBuildUse(buildCtx);
|
|
504
498
|
|
|
505
499
|
const raw = await handler(buildCtx);
|
|
506
|
-
|
|
500
|
+
|
|
501
|
+
// Static handlers must return a ReactNode. A returned Response (e.g. an
|
|
502
|
+
// accidental redirect()) would otherwise be serialized as a corrupt build
|
|
503
|
+
// artifact; fail loudly instead. The fresh/revalidation paths route handler
|
|
504
|
+
// results through handleHandlerResult, which throws Responses; the static
|
|
505
|
+
// build path bypasses that, so guard here.
|
|
506
|
+
if (raw instanceof Response) {
|
|
507
|
+
throw new TypeError(
|
|
508
|
+
`Static handler "${routeName}" returned a Response. Static handlers must return a ReactNode; ` +
|
|
509
|
+
`Responses (redirects, file responses) are not supported during static pre-rendering.`,
|
|
510
|
+
);
|
|
511
|
+
}
|
|
507
512
|
|
|
508
513
|
const segment: ResolvedSegment = {
|
|
509
514
|
id: handlerId,
|
|
510
515
|
namespace: handlerId,
|
|
511
516
|
type: "layout",
|
|
512
517
|
index: 0,
|
|
513
|
-
component,
|
|
518
|
+
component: raw,
|
|
514
519
|
params: {},
|
|
515
520
|
belongsToRoute: false,
|
|
516
521
|
};
|
|
@@ -23,10 +23,6 @@ import { negotiateRoute } from "./content-negotiation.js";
|
|
|
23
23
|
import { stripInternalParams } from "./handler-context.js";
|
|
24
24
|
import { resolveRoute, type RouteSnapshot } from "./route-snapshot.js";
|
|
25
25
|
|
|
26
|
-
// ---------------------------------------------------------------------------
|
|
27
|
-
// RequestPlan — discriminated union
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
|
|
30
26
|
interface RedirectPlan<TEnv = any> {
|
|
31
27
|
mode: "redirect";
|
|
32
28
|
route: RouteSnapshot<TEnv>;
|
|
@@ -124,10 +120,6 @@ export type {
|
|
|
124
120
|
PartialRenderPlan,
|
|
125
121
|
};
|
|
126
122
|
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// classifyRequest — the single authoritative classification step
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
|
-
|
|
131
123
|
export interface ClassifyRequestDeps<TEnv = any> {
|
|
132
124
|
findMatch: (pathname: string) => RouteMatchResult<TEnv> | null;
|
|
133
125
|
routerVersion: string;
|
|
@@ -157,15 +149,12 @@ export async function classifyRequest<TEnv = any>(
|
|
|
157
149
|
const isAction =
|
|
158
150
|
request.headers.has("rsc-action") || url.searchParams.has("_rsc_action");
|
|
159
151
|
|
|
160
|
-
// Version mismatch — runs BEFORE route resolution so stale clients
|
|
161
|
-
// requesting removed routes get a reload, not a 404.
|
|
162
152
|
const clientVersion = url.searchParams.get("_rsc_v");
|
|
163
153
|
if (
|
|
164
154
|
deps.routerVersion &&
|
|
165
155
|
clientVersion &&
|
|
166
156
|
clientVersion !== deps.routerVersion
|
|
167
157
|
) {
|
|
168
|
-
// Strip internal _rsc_* params so the browser reloads to a clean URL
|
|
169
158
|
let reloadUrl = stripInternalParams(url).toString();
|
|
170
159
|
if (isAction) {
|
|
171
160
|
const referer = request.headers.get("referer");
|
|
@@ -175,9 +164,7 @@ export async function classifyRequest<TEnv = any>(
|
|
|
175
164
|
if (refererUrl.origin === url.origin) {
|
|
176
165
|
reloadUrl = referer;
|
|
177
166
|
}
|
|
178
|
-
} catch {
|
|
179
|
-
// Malformed referer, fall back to stripped url
|
|
180
|
-
}
|
|
167
|
+
} catch {}
|
|
181
168
|
}
|
|
182
169
|
}
|
|
183
170
|
|
|
@@ -187,18 +174,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
187
174
|
};
|
|
188
175
|
}
|
|
189
176
|
|
|
190
|
-
// App switch — also runs BEFORE route resolution (like version-mismatch
|
|
191
|
-
// above), and for the same reason: a cross-app SPA navigation must reload
|
|
192
|
-
// even when the target route does NOT exist in the target app. If we resolved
|
|
193
|
-
// first, a missing route would throw RouteNotFoundError and the 404 would
|
|
194
|
-
// render in-place under the SOURCE app's document — violating the invariant
|
|
195
|
-
// that crossing a host-router app boundary is always a full document load.
|
|
196
|
-
// A mismatched routerId (_rsc_rid) means the navigation crossed an app
|
|
197
|
-
// boundary; force a real document navigation. A soft swap can't faithfully
|
|
198
|
-
// re-establish the target app's document (stylesheets shared across apps are
|
|
199
|
-
// dropped by React 19's by-href dedup; theme/warmup/prefetch-TTL are
|
|
200
|
-
// document-lifetime — see browser/app-shell.ts). Only SPA (`_rsc_partial`)
|
|
201
|
-
// requests need this; a direct full load already IS the document navigation.
|
|
202
177
|
const clientRouterId = url.searchParams.get("_rsc_rid");
|
|
203
178
|
if (
|
|
204
179
|
clientRouterId &&
|
|
@@ -211,9 +186,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
211
186
|
};
|
|
212
187
|
}
|
|
213
188
|
|
|
214
|
-
// No metricsStore — classification is a lightweight gating step.
|
|
215
|
-
// Route-matching and manifest-loading metrics belong in the match path
|
|
216
|
-
// (createMatchContextForFull/Partial) which runs the authoritative resolution.
|
|
217
189
|
const result = await resolveRoute<TEnv>(pathname, {
|
|
218
190
|
findMatch: deps.findMatch,
|
|
219
191
|
lite: true,
|
|
@@ -225,7 +197,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
225
197
|
});
|
|
226
198
|
}
|
|
227
199
|
|
|
228
|
-
// Redirect
|
|
229
200
|
if (result.type === "redirect") {
|
|
230
201
|
const snapshot: RouteSnapshot<TEnv> = {
|
|
231
202
|
matched: result as any,
|
|
@@ -247,7 +218,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
247
218
|
|
|
248
219
|
const snapshot = result.snapshot;
|
|
249
220
|
|
|
250
|
-
// Response route — non-RSC short-circuit (JSON, streaming, etc.)
|
|
251
221
|
const responseResult = await classifyResponseRoute(
|
|
252
222
|
request,
|
|
253
223
|
pathname,
|
|
@@ -257,7 +227,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
257
227
|
return responseResult;
|
|
258
228
|
}
|
|
259
229
|
|
|
260
|
-
// Mode detection from request signals
|
|
261
230
|
const actionId =
|
|
262
231
|
request.headers.get("rsc-action") || url.searchParams.get("_rsc_action");
|
|
263
232
|
const isLoaderFetch = url.searchParams.has("_rsc_loader");
|
|
@@ -275,7 +244,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
275
244
|
return { mode: "loader", route: snapshot };
|
|
276
245
|
}
|
|
277
246
|
|
|
278
|
-
// PE detection: POST with form content-type, but not a server action
|
|
279
247
|
const contentType = request.headers.get("content-type") || "";
|
|
280
248
|
const isFormSubmission =
|
|
281
249
|
contentType.includes("multipart/form-data") ||
|
|
@@ -291,10 +259,6 @@ export async function classifyRequest<TEnv = any>(
|
|
|
291
259
|
return { mode: "full-render", route: snapshot, negotiated };
|
|
292
260
|
}
|
|
293
261
|
|
|
294
|
-
// ---------------------------------------------------------------------------
|
|
295
|
-
// Content negotiation for response routes
|
|
296
|
-
// ---------------------------------------------------------------------------
|
|
297
|
-
|
|
298
262
|
/**
|
|
299
263
|
* Check if the route is a response route and perform content negotiation
|
|
300
264
|
* if negotiate variants exist. Returns a ResponseRoutePlan if the route
|
|
@@ -305,7 +269,6 @@ async function classifyResponseRoute<TEnv>(
|
|
|
305
269
|
pathname: string,
|
|
306
270
|
snapshot: RouteSnapshot<TEnv>,
|
|
307
271
|
): Promise<ResponseRoutePlan<TEnv> | null> {
|
|
308
|
-
// negotiateRoute returns the response plan (variant or plain) or null for RSC.
|
|
309
272
|
const negotiation = await negotiateRoute(request, pathname, snapshot);
|
|
310
273
|
return negotiation
|
|
311
274
|
? { mode: "response", route: snapshot, ...negotiation }
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
import type { RevalidationTraceEntry } from "./logging.js";
|
|
15
15
|
import { _getRequestContext } from "../server/request-context.js";
|
|
16
16
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
17
|
+
import { paramsEqual } from "./params-util.js";
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* Resolve a server-action reference's stable id, mirroring how the action
|
|
@@ -32,16 +33,21 @@ function resolveActionRefId(ref: unknown): string | undefined {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
|
-
* Build the `isAction()` helper bound to the current action's id.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
36
|
+
* Build the `isAction()` helper bound to the current action's id. Called with no
|
|
37
|
+
* arguments it answers "is this request an action at all?" (any action) — `true`
|
|
38
|
+
* during action handling, `false` on plain navigation. Called with one or more
|
|
39
|
+
* action references it narrows to those: a single imported action, several
|
|
40
|
+
* (variadic), or any export of a namespace import (`import * as Mod`). Returns
|
|
41
|
+
* `false` when there is no action (plain navigation) or nothing matches.
|
|
39
42
|
*/
|
|
40
43
|
function makeIsAction(
|
|
41
44
|
currentActionId: string | undefined,
|
|
42
45
|
): (...actions: ActionRef[]) => boolean {
|
|
43
46
|
return (...actions: ActionRef[]): boolean => {
|
|
44
47
|
if (!currentActionId) return false;
|
|
48
|
+
// Bare isAction(): an action is in flight (currentActionId is set) and the
|
|
49
|
+
// caller did not narrow to a specific action, so this is "any action".
|
|
50
|
+
if (actions.length === 0) return true;
|
|
45
51
|
for (const action of actions) {
|
|
46
52
|
if (typeof action === "function") {
|
|
47
53
|
if (resolveActionRefId(action) === currentActionId) return true;
|
|
@@ -56,22 +62,6 @@ function makeIsAction(
|
|
|
56
62
|
};
|
|
57
63
|
}
|
|
58
64
|
|
|
59
|
-
function paramsEqual(
|
|
60
|
-
a: Record<string, string>,
|
|
61
|
-
b: Record<string, string>,
|
|
62
|
-
): boolean {
|
|
63
|
-
if (a === b) return true;
|
|
64
|
-
|
|
65
|
-
const keysA = Object.keys(a);
|
|
66
|
-
if (keysA.length !== Object.keys(b).length) return false;
|
|
67
|
-
|
|
68
|
-
for (const key of keysA) {
|
|
69
|
-
if (a[key] !== b[key]) return false;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
65
|
/**
|
|
76
66
|
* Options for revalidation evaluation
|
|
77
67
|
*/
|
|
@@ -136,8 +126,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
136
126
|
const paramsChanged = !paramsEqual(nextParams, prevParams);
|
|
137
127
|
const searchChanged = prevUrl.search !== nextUrl.search;
|
|
138
128
|
|
|
139
|
-
// Trace helper: push a structured entry to the request-scoped trace buffer.
|
|
140
|
-
// Guarded by isTraceActive() so object construction is skipped in production.
|
|
141
129
|
function pushTrace(
|
|
142
130
|
defaultVal: boolean,
|
|
143
131
|
finalVal: boolean,
|
|
@@ -156,43 +144,28 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
156
144
|
});
|
|
157
145
|
}
|
|
158
146
|
|
|
159
|
-
// Calculate default revalidation based on segment type and request method
|
|
160
147
|
let defaultShouldRevalidate: boolean;
|
|
161
148
|
let defaultReason: string;
|
|
162
149
|
|
|
163
150
|
if (defaultOverride) {
|
|
164
|
-
// Caller injected the seed (e.g. parallel slot not in clientSegmentIds).
|
|
165
|
-
// Skip the type-derived heuristic — caller knows better in this context.
|
|
166
151
|
defaultShouldRevalidate = defaultOverride.value;
|
|
167
152
|
defaultReason = defaultOverride.reason;
|
|
168
153
|
} else if (request.method === "POST") {
|
|
169
|
-
// Actions: revalidate segments that belong to the route, skip parent chain
|
|
170
154
|
if (segment.type === "route") {
|
|
171
|
-
// Route segment always revalidates on actions
|
|
172
155
|
defaultShouldRevalidate = true;
|
|
173
156
|
defaultReason = "action:route-segment";
|
|
174
157
|
} else if (segment.type === "loader") {
|
|
175
|
-
// Loaders always revalidate on actions - they often contain action-sensitive data
|
|
176
|
-
// (e.g., cart count after add-to-cart action)
|
|
177
158
|
defaultShouldRevalidate = true;
|
|
178
159
|
defaultReason = "action:loader-segment";
|
|
179
160
|
} else if (segment.belongsToRoute) {
|
|
180
|
-
// Segment belongs to route (orphan layouts/parallels) - revalidate
|
|
181
161
|
defaultShouldRevalidate = true;
|
|
182
162
|
defaultReason = "action:belongs-to-route";
|
|
183
163
|
} else {
|
|
184
|
-
// Parent chain segment (shared layouts/parallels) - don't revalidate
|
|
185
164
|
defaultShouldRevalidate = false;
|
|
186
165
|
defaultReason = "action:parent-chain-skip";
|
|
187
166
|
}
|
|
188
167
|
} else {
|
|
189
|
-
// Navigation (GET): Conservative defaults to minimize unnecessary revalidations
|
|
190
|
-
// Only the route segment revalidates by default - all others require explicit opt-in
|
|
191
|
-
|
|
192
168
|
if (segment.type === "route") {
|
|
193
|
-
// Route segments revalidate when path params OR search params change.
|
|
194
|
-
// Search params (e.g., ?page=2&sort=price) are server-parsed via ctx.search,
|
|
195
|
-
// so the handler must re-execute to produce updated content.
|
|
196
169
|
const routeChanged = paramsChanged || searchChanged;
|
|
197
170
|
defaultShouldRevalidate = routeChanged;
|
|
198
171
|
defaultReason = paramsChanged
|
|
@@ -208,8 +181,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
208
181
|
});
|
|
209
182
|
}
|
|
210
183
|
} else if (segment.belongsToRoute && (paramsChanged || searchChanged)) {
|
|
211
|
-
// Children of the route path (loaders, orphan layouts/parallels)
|
|
212
|
-
// revalidate when path params or search params change
|
|
213
184
|
defaultShouldRevalidate = true;
|
|
214
185
|
defaultReason = paramsChanged
|
|
215
186
|
? "nav:route-child-params-changed"
|
|
@@ -221,9 +192,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
221
192
|
searchChanged,
|
|
222
193
|
});
|
|
223
194
|
} else {
|
|
224
|
-
// Parent layouts and parallels default to no revalidation
|
|
225
|
-
// Cannot assume these segments depend on params without explicit declaration
|
|
226
|
-
// Use custom revalidation functions to opt-in when needed
|
|
227
195
|
defaultShouldRevalidate = false;
|
|
228
196
|
defaultReason = "nav:non-route-skip";
|
|
229
197
|
debugLog("revalidation", "non-route segment skipped by default", {
|
|
@@ -233,7 +201,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
233
201
|
}
|
|
234
202
|
}
|
|
235
203
|
|
|
236
|
-
// No custom revalidations defined - return default behavior without prev segment
|
|
237
204
|
if (revalidations.length === 0) {
|
|
238
205
|
if (defaultShouldRevalidate) {
|
|
239
206
|
debugLog("revalidation", "default revalidate=true", {
|
|
@@ -250,14 +217,10 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
250
217
|
return defaultShouldRevalidate;
|
|
251
218
|
}
|
|
252
219
|
|
|
253
|
-
// Custom revalidations exist - may need full prev segment
|
|
254
|
-
// Lazy load prev segment only if getPrevSegment provided
|
|
255
220
|
const prevSegment = getPrevSegment ? await getPrevSegment() : null;
|
|
256
221
|
|
|
257
|
-
// Execute revalidation functions with soft/hard decision pattern
|
|
258
222
|
let currentSuggestion = defaultShouldRevalidate;
|
|
259
223
|
|
|
260
|
-
// Compute public route names (filtered: undefined for auto-generated routes)
|
|
261
224
|
const toRouteName =
|
|
262
225
|
routeKey && !isAutoGeneratedRouteName(routeKey) ? routeKey : undefined;
|
|
263
226
|
const reqCtx = _getRequestContext();
|
|
@@ -285,20 +248,14 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
285
248
|
actionUrl: actionContext?.actionUrl,
|
|
286
249
|
actionResult: actionContext?.actionResult,
|
|
287
250
|
formData: actionContext?.formData,
|
|
288
|
-
method: request.method,
|
|
289
|
-
routeName: toRouteName,
|
|
290
|
-
fromRouteName,
|
|
291
|
-
toRouteName,
|
|
292
|
-
// Stale cache context (only true for background revalidation after stale cache render)
|
|
251
|
+
method: request.method,
|
|
252
|
+
routeName: toRouteName,
|
|
253
|
+
fromRouteName,
|
|
254
|
+
toRouteName,
|
|
293
255
|
stale,
|
|
294
256
|
});
|
|
295
257
|
|
|
296
|
-
// Check return type:
|
|
297
|
-
// - boolean: hard decision, short-circuit immediately
|
|
298
|
-
// - { defaultShouldRevalidate: boolean }: soft decision, update suggestion and continue
|
|
299
|
-
// - null/undefined: use default behavior (equivalent to returning { defaultShouldRevalidate })
|
|
300
258
|
if (typeof result === "boolean") {
|
|
301
|
-
// Hard decision - short-circuit
|
|
302
259
|
debugLog("revalidation", "hard decision", {
|
|
303
260
|
segmentId: segment.id,
|
|
304
261
|
revalidator: name,
|
|
@@ -311,7 +268,6 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
311
268
|
typeof result === "object" &&
|
|
312
269
|
"defaultShouldRevalidate" in result
|
|
313
270
|
) {
|
|
314
|
-
// Soft decision - update suggestion and continue
|
|
315
271
|
currentSuggestion = result.defaultShouldRevalidate;
|
|
316
272
|
debugLog("revalidation", "soft decision", {
|
|
317
273
|
segmentId: segment.id,
|
|
@@ -319,18 +275,14 @@ export async function evaluateRevalidation<TEnv>(
|
|
|
319
275
|
revalidate: currentSuggestion,
|
|
320
276
|
});
|
|
321
277
|
} else if (result === null || result === undefined) {
|
|
322
|
-
// Defer to default - equivalent to { defaultShouldRevalidate: currentSuggestion }
|
|
323
|
-
// This means "I don't care, use whatever the default is"
|
|
324
278
|
debugLog("revalidation", "deferred to current default", {
|
|
325
279
|
segmentId: segment.id,
|
|
326
280
|
revalidator: name,
|
|
327
281
|
revalidate: currentSuggestion,
|
|
328
282
|
});
|
|
329
|
-
// currentSuggestion stays the same, continue to next function
|
|
330
283
|
}
|
|
331
284
|
}
|
|
332
285
|
|
|
333
|
-
// All revalidators completed - use final suggestion
|
|
334
286
|
debugLog("revalidation", "final decision", {
|
|
335
287
|
segmentId: segment.id,
|
|
336
288
|
revalidate: currentSuggestion,
|
|
@@ -230,7 +230,6 @@ export function createRouteSnapshot<TEnv = any>(
|
|
|
230
230
|
entry: {} as any,
|
|
231
231
|
routeKey: "test",
|
|
232
232
|
params: {},
|
|
233
|
-
optionalParams: new Set(),
|
|
234
233
|
} as RouteMatchResult<TEnv>,
|
|
235
234
|
manifestEntry: { type: "route", shortCode: "R0", parent: null } as any,
|
|
236
235
|
entries: [],
|
|
@@ -54,10 +54,8 @@ export interface InterceptResult {
|
|
|
54
54
|
* Instead of passing 20+ parameters, middleware calls getRouterContext() to access them.
|
|
55
55
|
*/
|
|
56
56
|
export interface RouterContext<TEnv = any> {
|
|
57
|
-
// Route matching
|
|
58
57
|
findMatch: (pathname: string) => RouteMatchResult | null;
|
|
59
58
|
|
|
60
|
-
// Manifest loading
|
|
61
59
|
loadManifest: (
|
|
62
60
|
entry: any,
|
|
63
61
|
routeKey: string,
|
|
@@ -66,10 +64,8 @@ export interface RouterContext<TEnv = any> {
|
|
|
66
64
|
isSSR?: boolean,
|
|
67
65
|
) => Promise<EntryData>;
|
|
68
66
|
|
|
69
|
-
// Entry traversal
|
|
70
67
|
traverseBack: (entry: EntryData) => Generator<EntryData>;
|
|
71
68
|
|
|
72
|
-
// Handler context creation
|
|
73
69
|
createHandlerContext: (
|
|
74
70
|
params: Record<string, string>,
|
|
75
71
|
request: Request,
|
|
@@ -83,7 +79,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
83
79
|
isPassthroughRoute?: boolean,
|
|
84
80
|
) => HandlerContext<any, TEnv>;
|
|
85
81
|
|
|
86
|
-
// Loader setup
|
|
87
82
|
setupLoaderAccess: (
|
|
88
83
|
ctx: HandlerContext<any, TEnv>,
|
|
89
84
|
loaderPromises: Map<string, Promise<any>>,
|
|
@@ -94,7 +89,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
94
89
|
loaderPromises: Map<string, Promise<any>>,
|
|
95
90
|
) => void;
|
|
96
91
|
|
|
97
|
-
// Context access
|
|
98
92
|
getContext: () => {
|
|
99
93
|
getOrCreateStore: (key: string) => any;
|
|
100
94
|
runWithStore: <T>(
|
|
@@ -105,16 +99,13 @@ export interface RouterContext<TEnv = any> {
|
|
|
105
99
|
) => T;
|
|
106
100
|
};
|
|
107
101
|
|
|
108
|
-
// Metrics
|
|
109
102
|
getMetricsStore: () => MetricsStore | undefined;
|
|
110
103
|
|
|
111
|
-
// Cache
|
|
112
104
|
createCacheScope: (
|
|
113
105
|
cacheConfig: any,
|
|
114
106
|
parent: CacheScope | null,
|
|
115
107
|
) => CacheScope | null;
|
|
116
108
|
|
|
117
|
-
// Intercept detection
|
|
118
109
|
findInterceptForRoute: (
|
|
119
110
|
routeKey: string,
|
|
120
111
|
parentEntry: EntryData | null,
|
|
@@ -122,7 +113,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
122
113
|
isAction: boolean,
|
|
123
114
|
) => InterceptResult | null;
|
|
124
115
|
|
|
125
|
-
// Segment resolution (with revalidation)
|
|
126
116
|
resolveAllSegmentsWithRevalidation: (
|
|
127
117
|
entries: EntryData[],
|
|
128
118
|
routeKey: string,
|
|
@@ -133,7 +123,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
133
123
|
request: Request,
|
|
134
124
|
prevUrl: URL,
|
|
135
125
|
nextUrl: URL,
|
|
136
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
137
126
|
actionContext: any | undefined,
|
|
138
127
|
interceptResult: InterceptResult | null,
|
|
139
128
|
localRouteName: string,
|
|
@@ -166,12 +155,10 @@ export interface RouterContext<TEnv = any> {
|
|
|
166
155
|
revalidationContext?: RevalidationContext,
|
|
167
156
|
) => Promise<ResolvedSegment[]>;
|
|
168
157
|
|
|
169
|
-
// Collect with markers
|
|
170
158
|
collectWithMarkers?: <T>(
|
|
171
159
|
gen: AsyncGenerator<T | { __type: "id"; id: string }>,
|
|
172
160
|
) => Promise<{ items: T[]; matchedIds: string[] }>;
|
|
173
161
|
|
|
174
|
-
// Revalidation evaluation
|
|
175
162
|
evaluateRevalidation: (params: {
|
|
176
163
|
segment: ResolvedSegment;
|
|
177
164
|
prevParams: Record<string, string>;
|
|
@@ -195,7 +182,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
195
182
|
| "intercept-loader";
|
|
196
183
|
}) => Promise<boolean>;
|
|
197
184
|
|
|
198
|
-
// Request context
|
|
199
185
|
getRequestContext: () =>
|
|
200
186
|
| {
|
|
201
187
|
waitUntil: (fn: () => Promise<void>) => void;
|
|
@@ -203,7 +189,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
203
189
|
}
|
|
204
190
|
| undefined;
|
|
205
191
|
|
|
206
|
-
// Simple segment resolution (without revalidation - for full match)
|
|
207
192
|
resolveAllSegments: (
|
|
208
193
|
entries: EntryData[],
|
|
209
194
|
routeKey: string,
|
|
@@ -213,7 +198,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
213
198
|
options?: { skipLoaders?: boolean },
|
|
214
199
|
) => Promise<ResolvedSegment[]>;
|
|
215
200
|
|
|
216
|
-
// Generator-based simple resolution
|
|
217
201
|
resolveAllSegmentsGenerator?: (
|
|
218
202
|
entries: EntryData[],
|
|
219
203
|
routeKey: string,
|
|
@@ -222,21 +206,17 @@ export interface RouterContext<TEnv = any> {
|
|
|
222
206
|
loaderPromises: Map<string, Promise<any>>,
|
|
223
207
|
) => AsyncGenerator<ResolvedSegment | { __type: "id"; id: string }>;
|
|
224
208
|
|
|
225
|
-
// Collect segments from generator
|
|
226
209
|
collectSegmentsFromGenerator?: <T>(
|
|
227
210
|
gen: AsyncGenerator<T | { __type: "id"; id: string }>,
|
|
228
211
|
) => Promise<T[]>;
|
|
229
212
|
|
|
230
|
-
// Handle store
|
|
231
213
|
createHandleStore: () => any;
|
|
232
214
|
|
|
233
|
-
// Loaders-only resolution (for full match cache hit - no revalidation)
|
|
234
215
|
resolveLoadersOnly?: (
|
|
235
216
|
entries: EntryData[],
|
|
236
217
|
handlerContext: HandlerContext<any, TEnv>,
|
|
237
218
|
) => Promise<ResolvedSegment[]>;
|
|
238
219
|
|
|
239
|
-
// Loaders-only resolution (for cache hit scenarios)
|
|
240
220
|
resolveLoadersOnlyWithRevalidation?: (
|
|
241
221
|
entries: EntryData[],
|
|
242
222
|
handlerContext: HandlerContext<any, TEnv>,
|
|
@@ -258,10 +238,8 @@ export interface RouterContext<TEnv = any> {
|
|
|
258
238
|
// Telemetry sink (optional, no-op when undefined)
|
|
259
239
|
telemetry?: TelemetrySink;
|
|
260
240
|
|
|
261
|
-
// Request ID for telemetry span correlation (set per-request in match handlers)
|
|
262
241
|
requestId?: string;
|
|
263
242
|
|
|
264
|
-
// Intercept loaders only (for cache hit + intercept scenarios)
|
|
265
243
|
resolveInterceptLoadersOnly?: (
|
|
266
244
|
intercept: InterceptEntry,
|
|
267
245
|
entry: EntryData,
|
|
@@ -284,7 +262,6 @@ export interface RouterContext<TEnv = any> {
|
|
|
284
262
|
} | null>;
|
|
285
263
|
}
|
|
286
264
|
|
|
287
|
-
// AsyncLocalStorage instance for router context
|
|
288
265
|
const routerContext = new AsyncLocalStorage<RouterContext<any>>();
|
|
289
266
|
|
|
290
267
|
/**
|
|
@@ -308,10 +285,6 @@ export function getRouterContext<TEnv = any>(): RouterContext<TEnv> {
|
|
|
308
285
|
*
|
|
309
286
|
* All async code within fn() can call getRouterContext() to access router closures.
|
|
310
287
|
* This works across async boundaries thanks to AsyncLocalStorage.
|
|
311
|
-
*
|
|
312
|
-
* @param deps Router dependencies to make available
|
|
313
|
-
* @param fn Function to run with dependencies available
|
|
314
|
-
* @returns Result of fn()
|
|
315
288
|
*/
|
|
316
289
|
export function runWithRouterContext<T, TEnv = any>(
|
|
317
290
|
deps: RouterContext<TEnv>,
|