@rangojs/router 0.0.0-experimental.259 → 0.0.0-experimental.26
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 +294 -28
- package/dist/bin/rango.js +355 -47
- package/dist/vite/index.js +1658 -1239
- package/package.json +3 -3
- package/skills/cache-guide/SKILL.md +9 -5
- package/skills/caching/SKILL.md +4 -4
- package/skills/document-cache/SKILL.md +2 -2
- package/skills/hooks/SKILL.md +40 -29
- package/skills/host-router/SKILL.md +218 -0
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +229 -15
- package/skills/middleware/SKILL.md +109 -30
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +189 -19
- package/skills/rango/SKILL.md +1 -2
- package/skills/response-routes/SKILL.md +3 -3
- package/skills/route/SKILL.md +44 -3
- package/skills/router-setup/SKILL.md +80 -3
- package/skills/theme/SKILL.md +5 -4
- package/skills/typesafety/SKILL.md +59 -16
- package/skills/use-cache/SKILL.md +16 -2
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +56 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +29 -48
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +19 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +66 -443
- package/src/browser/navigation-client.ts +34 -62
- package/src/browser/navigation-store.ts +4 -33
- package/src/browser/navigation-transaction.ts +295 -0
- package/src/browser/partial-update.ts +103 -151
- package/src/browser/prefetch/cache.ts +67 -0
- package/src/browser/prefetch/fetch.ts +137 -0
- package/src/browser/prefetch/observer.ts +65 -0
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/prefetch/queue.ts +88 -0
- package/src/browser/rango-state.ts +112 -0
- package/src/browser/react/Link.tsx +154 -44
- package/src/browser/react/NavigationProvider.tsx +32 -0
- package/src/browser/react/context.ts +6 -0
- package/src/browser/react/filter-segment-order.ts +11 -0
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +29 -11
- package/src/browser/react/location-state.ts +6 -4
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/shallow-equal.ts +27 -0
- package/src/browser/react/use-action.ts +23 -45
- package/src/browser/react/use-client-cache.ts +5 -3
- package/src/browser/react/use-handle.ts +21 -64
- package/src/browser/react/use-navigation.ts +7 -32
- package/src/browser/react/use-params.ts +5 -34
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +3 -6
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +75 -114
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +46 -22
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +458 -405
- package/src/browser/types.ts +21 -35
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +38 -13
- package/src/build/generate-route-types.ts +4 -0
- package/src/build/index.ts +1 -0
- package/src/build/route-trie.ts +19 -3
- package/src/build/route-types/codegen.ts +13 -4
- package/src/build/route-types/include-resolution.ts +13 -0
- package/src/build/route-types/per-module-writer.ts +15 -3
- package/src/build/route-types/router-processing.ts +170 -18
- package/src/build/runtime-discovery.ts +13 -1
- package/src/cache/background-task.ts +34 -0
- package/src/cache/cache-key-utils.ts +44 -0
- package/src/cache/cache-policy.ts +125 -0
- package/src/cache/cache-runtime.ts +136 -123
- package/src/cache/cache-scope.ts +76 -83
- package/src/cache/cf/cf-cache-store.ts +12 -7
- package/src/cache/document-cache.ts +93 -69
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/index.ts +0 -15
- package/src/cache/memory-segment-store.ts +43 -69
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +140 -117
- package/src/cache/taint.ts +30 -3
- package/src/cache/types.ts +1 -115
- package/src/client.rsc.tsx +0 -1
- package/src/client.tsx +53 -76
- package/src/errors.ts +6 -1
- package/src/handle.ts +1 -1
- package/src/handles/MetaTags.tsx +5 -2
- package/src/host/cookie-handler.ts +8 -3
- package/src/host/index.ts +0 -3
- package/src/host/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +53 -10
- package/src/index.ts +73 -43
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +60 -18
- package/src/prerender.ts +76 -18
- package/src/reverse.ts +11 -7
- package/src/root-error-boundary.tsx +30 -26
- package/src/route-definition/dsl-helpers.ts +9 -6
- package/src/route-definition/index.ts +0 -3
- package/src/route-definition/redirect.ts +15 -3
- package/src/route-map-builder.ts +38 -2
- package/src/route-name.ts +53 -0
- package/src/route-types.ts +7 -0
- package/src/router/content-negotiation.ts +1 -1
- package/src/router/debug-manifest.ts +16 -3
- package/src/router/handler-context.ts +96 -17
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +6 -11
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +62 -54
- package/src/router/match-context.ts +3 -0
- package/src/router/match-handlers.ts +185 -11
- package/src/router/match-middleware/background-revalidation.ts +65 -85
- package/src/router/match-middleware/cache-lookup.ts +78 -10
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/match-result.ts +0 -9
- package/src/router/metrics.ts +233 -13
- package/src/router/middleware-types.ts +34 -39
- package/src/router/middleware.ts +290 -130
- package/src/router/pattern-matching.ts +61 -10
- package/src/router/prerender-match.ts +36 -6
- package/src/router/preview-match.ts +7 -1
- package/src/router/revalidation.ts +61 -2
- package/src/router/router-context.ts +15 -0
- package/src/router/router-interfaces.ts +158 -40
- package/src/router/router-options.ts +223 -1
- package/src/router/router-registry.ts +5 -2
- package/src/router/segment-resolution/fresh.ts +165 -242
- package/src/router/segment-resolution/helpers.ts +263 -0
- package/src/router/segment-resolution/loader-cache.ts +102 -98
- package/src/router/segment-resolution/revalidation.ts +394 -272
- package/src/router/segment-resolution/static-store.ts +2 -2
- package/src/router/segment-resolution.ts +1 -3
- package/src/router/segment-wrappers.ts +3 -0
- package/src/router/telemetry-otel.ts +299 -0
- package/src/router/telemetry.ts +300 -0
- package/src/router/timeout.ts +148 -0
- package/src/router/trie-matching.ts +20 -2
- package/src/router/types.ts +7 -1
- package/src/router.ts +203 -18
- package/src/rsc/handler-context.ts +13 -2
- package/src/rsc/handler.ts +489 -438
- package/src/rsc/helpers.ts +125 -5
- package/src/rsc/index.ts +0 -20
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/manifest-init.ts +3 -2
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +245 -19
- package/src/rsc/response-route-handler.ts +347 -0
- package/src/rsc/rsc-rendering.ts +47 -43
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +166 -66
- package/src/rsc/ssr-setup.ts +128 -0
- package/src/rsc/types.ts +20 -2
- package/src/search-params.ts +38 -23
- package/src/server/context.ts +61 -7
- package/src/server/cookie-store.ts +190 -0
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +84 -12
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +275 -49
- package/src/server.ts +6 -0
- package/src/ssr/index.tsx +67 -28
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +4 -18
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +6 -1
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +22 -0
- package/src/types/handler-context.ts +103 -16
- package/src/types/index.ts +1 -1
- package/src/types/loader-types.ts +9 -6
- package/src/types/route-config.ts +17 -26
- package/src/types/route-entry.ts +28 -0
- package/src/types/segments.ts +0 -5
- package/src/urls/include-helper.ts +49 -8
- package/src/urls/index.ts +1 -0
- package/src/urls/path-helper-types.ts +30 -12
- package/src/urls/path-helper.ts +17 -2
- package/src/urls/pattern-types.ts +21 -1
- package/src/urls/response-types.ts +29 -7
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +27 -9
- package/src/vite/discovery/bundle-postprocess.ts +32 -52
- package/src/vite/discovery/discover-routers.ts +52 -26
- package/src/vite/discovery/prerender-collection.ts +58 -41
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/state.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/index.ts +10 -51
- package/src/vite/plugins/client-ref-dedup.ts +115 -0
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/expose-internal-ids.ts +4 -3
- package/src/vite/plugins/refresh-cmd.ts +65 -0
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/plugins/version-plugin.ts +188 -18
- package/src/vite/rango.ts +61 -36
- package/src/vite/router-discovery.ts +173 -100
- package/src/vite/utils/prerender-utils.ts +81 -0
- package/src/vite/utils/shared-utils.ts +19 -9
- package/skills/testing/SKILL.md +0 -226
- package/src/browser/lru-cache.ts +0 -61
- package/src/browser/react/prefetch.ts +0 -27
- package/src/browser/request-controller.ts +0 -164
- package/src/cache/memory-store.ts +0 -253
- package/src/href-context.ts +0 -33
- package/src/route-definition/route-function.ts +0 -119
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /package/{CLAUDE.md → AGENTS.md} +0 -0
package/src/router/logging.ts
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { INTERNAL_RANGO_DEBUG } from "../internal-debug.js";
|
|
3
3
|
|
|
4
|
+
// -- Revalidation trace types --
|
|
5
|
+
|
|
6
|
+
export interface RevalidationTraceEntry {
|
|
7
|
+
segmentId: string;
|
|
8
|
+
segmentType: string;
|
|
9
|
+
belongsToRoute: boolean;
|
|
10
|
+
source:
|
|
11
|
+
| "segment-resolution"
|
|
12
|
+
| "cache-hit"
|
|
13
|
+
| "loader"
|
|
14
|
+
| "parallel"
|
|
15
|
+
| "orphan-layout";
|
|
16
|
+
defaultShouldRevalidate: boolean;
|
|
17
|
+
finalShouldRevalidate: boolean;
|
|
18
|
+
reason: string;
|
|
19
|
+
customRevalidators?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface RevalidationTraceMeta {
|
|
23
|
+
method: string;
|
|
24
|
+
prevUrl: string;
|
|
25
|
+
nextUrl: string;
|
|
26
|
+
routeKey: string;
|
|
27
|
+
isAction: boolean;
|
|
28
|
+
stale?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RevalidationTrace {
|
|
32
|
+
meta: RevalidationTraceMeta;
|
|
33
|
+
entries: RevalidationTraceEntry[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// -- Log context --
|
|
37
|
+
|
|
4
38
|
interface RouterLogContext {
|
|
5
39
|
requestId: string;
|
|
6
40
|
transactionId: string;
|
|
7
41
|
depth: number;
|
|
42
|
+
revalidationTrace?: RevalidationTrace;
|
|
8
43
|
}
|
|
9
44
|
|
|
10
45
|
interface RouterLogOptions {
|
|
@@ -94,9 +129,16 @@ export function withRouterLogScope<T>(
|
|
|
94
129
|
try {
|
|
95
130
|
const result = fn();
|
|
96
131
|
if (result && typeof (result as Promise<T>).then === "function") {
|
|
97
|
-
return (result as Promise<T>).
|
|
98
|
-
|
|
99
|
-
|
|
132
|
+
return (result as Promise<T>).then(
|
|
133
|
+
(value) => {
|
|
134
|
+
debugLog(label, "end");
|
|
135
|
+
return value;
|
|
136
|
+
},
|
|
137
|
+
(error) => {
|
|
138
|
+
debugLog(label, "error", { error: String(error) });
|
|
139
|
+
throw error;
|
|
140
|
+
},
|
|
141
|
+
);
|
|
100
142
|
}
|
|
101
143
|
debugLog(label, "end");
|
|
102
144
|
return result;
|
|
@@ -149,3 +191,58 @@ export function debugWarn(
|
|
|
149
191
|
|
|
150
192
|
console.warn(`${prefix} ${message}`);
|
|
151
193
|
}
|
|
194
|
+
|
|
195
|
+
// -- Revalidation trace helpers --
|
|
196
|
+
|
|
197
|
+
export function isTraceActive(): boolean {
|
|
198
|
+
if (!INTERNAL_RANGO_DEBUG) return false;
|
|
199
|
+
const ctx = routerLogContext.getStore();
|
|
200
|
+
return !!ctx?.revalidationTrace;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function startRevalidationTrace(meta: RevalidationTraceMeta): void {
|
|
204
|
+
const ctx = routerLogContext.getStore();
|
|
205
|
+
if (!ctx || !INTERNAL_RANGO_DEBUG) return;
|
|
206
|
+
ctx.revalidationTrace = { meta, entries: [] };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function pushRevalidationTraceEntry(
|
|
210
|
+
entry: RevalidationTraceEntry,
|
|
211
|
+
): void {
|
|
212
|
+
const ctx = routerLogContext.getStore();
|
|
213
|
+
if (!ctx?.revalidationTrace) return;
|
|
214
|
+
ctx.revalidationTrace.entries.push(entry);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function flushRevalidationTrace(): RevalidationTrace | null {
|
|
218
|
+
const ctx = routerLogContext.getStore();
|
|
219
|
+
if (!ctx?.revalidationTrace) return null;
|
|
220
|
+
const trace = ctx.revalidationTrace;
|
|
221
|
+
ctx.revalidationTrace = undefined;
|
|
222
|
+
|
|
223
|
+
if (trace.entries.length === 0) return trace;
|
|
224
|
+
|
|
225
|
+
const revalidated = trace.entries.filter((e) => e.finalShouldRevalidate);
|
|
226
|
+
const skipped = trace.entries.filter((e) => !e.finalShouldRevalidate);
|
|
227
|
+
|
|
228
|
+
debugLog("revalidation-trace", "flush", {
|
|
229
|
+
method: trace.meta.method,
|
|
230
|
+
routeKey: trace.meta.routeKey,
|
|
231
|
+
isAction: trace.meta.isAction,
|
|
232
|
+
stale: trace.meta.stale,
|
|
233
|
+
prevUrl: trace.meta.prevUrl,
|
|
234
|
+
nextUrl: trace.meta.nextUrl,
|
|
235
|
+
total: trace.entries.length,
|
|
236
|
+
revalidated: revalidated.length,
|
|
237
|
+
skipped: skipped.length,
|
|
238
|
+
entries: trace.entries.map((e) => ({
|
|
239
|
+
segmentId: e.segmentId,
|
|
240
|
+
type: e.segmentType,
|
|
241
|
+
source: e.source,
|
|
242
|
+
revalidate: e.finalShouldRevalidate,
|
|
243
|
+
reason: e.reason,
|
|
244
|
+
})),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return trace;
|
|
248
|
+
}
|
package/src/router/manifest.ts
CHANGED
|
@@ -127,6 +127,22 @@ export async function loadManifest(
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
// Propagate cache profiles for DSL-time cache("profileName") resolution.
|
|
131
|
+
// Non-lazy entries carry profiles directly; lazy entries carry them
|
|
132
|
+
// in the captured lazyContext from include() time.
|
|
133
|
+
const entryProfiles =
|
|
134
|
+
entry.cacheProfiles ?? (lazyContext as any)?.cacheProfiles;
|
|
135
|
+
if (entryProfiles) {
|
|
136
|
+
Store.cacheProfiles = entryProfiles;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Propagate rootScoped from lazyContext so that routes inside
|
|
140
|
+
// nested { name: "sub" } under { name: "" } keep inherited root scope
|
|
141
|
+
// when the manifest is rebuilt on each request.
|
|
142
|
+
if (lazyContext && (lazyContext as any).rootScoped !== undefined) {
|
|
143
|
+
Store.rootScoped = (lazyContext as any).rootScoped;
|
|
144
|
+
}
|
|
145
|
+
|
|
130
146
|
const handlerExecStart = performance.now();
|
|
131
147
|
const useItems = await getContext().runWithStore(
|
|
132
148
|
Store,
|
|
@@ -178,15 +194,28 @@ export async function loadManifest(
|
|
|
178
194
|
"default" in load
|
|
179
195
|
) {
|
|
180
196
|
// Promise<{ default: () => Array }> - e.g., dynamic import
|
|
181
|
-
|
|
197
|
+
if (typeof load.default !== "function") {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`[@rangojs/router] Unsupported async handler: { default } must be a function, ` +
|
|
200
|
+
`got ${typeof load.default}. Use () => import('./urls') for lazy loading.`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
182
203
|
return (load.default as (h?: any) => any)(helpers);
|
|
183
204
|
}
|
|
184
205
|
if (typeof load === "function") {
|
|
185
206
|
// Promise<() => Array>
|
|
186
207
|
return (load as (h?: any) => any)(helpers);
|
|
187
208
|
}
|
|
188
|
-
//
|
|
189
|
-
|
|
209
|
+
// Reject unsupported async handler results. Supported shapes are:
|
|
210
|
+
// Promise<{ default: fn }> — dynamic import
|
|
211
|
+
// Promise<fn> — lazy function
|
|
212
|
+
// Direct Promise<Array> is not supported; use a function wrapper.
|
|
213
|
+
throw new Error(
|
|
214
|
+
`[@rangojs/router] Unsupported async handler result (${typeof load}). ` +
|
|
215
|
+
`Lazy route handlers must resolve to a function or { default: fn }, ` +
|
|
216
|
+
`not a direct array. Wrap your handler: () => import('./urls') or ` +
|
|
217
|
+
`() => Promise.resolve((h) => [...])`,
|
|
218
|
+
);
|
|
190
219
|
}
|
|
191
220
|
|
|
192
221
|
// Inline handler - routes were registered with correct parent inside layout
|
package/src/router/match-api.ts
CHANGED
|
@@ -31,7 +31,11 @@ import type { ErrorBoundaryHandler, ErrorInfo, MatchResult } from "../types";
|
|
|
31
31
|
import type { ReactNode } from "react";
|
|
32
32
|
import type { MatchContext } from "./match-context.js";
|
|
33
33
|
import type { MatchApiDeps, ActionContext } from "./types.js";
|
|
34
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
getRequestContext,
|
|
36
|
+
setRequestContextPrevRouteKey,
|
|
37
|
+
} from "../server/request-context.js";
|
|
38
|
+
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
35
39
|
import { debugLog, debugWarn } from "./logging.js";
|
|
36
40
|
|
|
37
41
|
/**
|
|
@@ -87,6 +91,13 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
87
91
|
});
|
|
88
92
|
}
|
|
89
93
|
|
|
94
|
+
if (
|
|
95
|
+
manifestEntry.type === "route" &&
|
|
96
|
+
manifestEntry.prerenderDef?.options?.passthrough === true
|
|
97
|
+
) {
|
|
98
|
+
matched.pt = true;
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
const routeMiddleware = collectRouteMiddleware(
|
|
91
102
|
traverseBack(manifestEntry),
|
|
92
103
|
matched.params,
|
|
@@ -105,6 +116,7 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
105
116
|
deps.getRouteMap(),
|
|
106
117
|
matched.routeKey,
|
|
107
118
|
matched.responseType,
|
|
119
|
+
matched.pt === true,
|
|
108
120
|
);
|
|
109
121
|
|
|
110
122
|
const loaderPromises = new Map<string, Promise<any>>();
|
|
@@ -161,6 +173,10 @@ export async function createMatchContextForFull<TEnv>(
|
|
|
161
173
|
request,
|
|
162
174
|
env,
|
|
163
175
|
segments: { path: [], ids: [] },
|
|
176
|
+
toRouteName:
|
|
177
|
+
matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
|
|
178
|
+
? matched.routeKey
|
|
179
|
+
: undefined,
|
|
164
180
|
},
|
|
165
181
|
isSameRouteNavigation: false,
|
|
166
182
|
interceptResult: null,
|
|
@@ -207,10 +223,21 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
207
223
|
return null;
|
|
208
224
|
}
|
|
209
225
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
226
|
+
let prevUrl: URL;
|
|
227
|
+
try {
|
|
228
|
+
prevUrl = new URL(previousUrl, url.origin);
|
|
229
|
+
} catch {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
let interceptContextUrl: URL;
|
|
234
|
+
try {
|
|
235
|
+
interceptContextUrl = interceptSourceUrl
|
|
236
|
+
? new URL(interceptSourceUrl, url.origin)
|
|
237
|
+
: prevUrl;
|
|
238
|
+
} catch {
|
|
239
|
+
interceptContextUrl = prevUrl;
|
|
240
|
+
}
|
|
214
241
|
|
|
215
242
|
const routeMatchStart = metricsStore ? performance.now() : 0;
|
|
216
243
|
const prevMatch = deps.findMatch(prevUrl.pathname);
|
|
@@ -262,6 +289,13 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
262
289
|
});
|
|
263
290
|
}
|
|
264
291
|
|
|
292
|
+
if (
|
|
293
|
+
manifestEntry.type === "route" &&
|
|
294
|
+
manifestEntry.prerenderDef?.options?.passthrough === true
|
|
295
|
+
) {
|
|
296
|
+
matched.pt = true;
|
|
297
|
+
}
|
|
298
|
+
|
|
265
299
|
const routeMiddleware = collectRouteMiddleware(
|
|
266
300
|
traverseBack(manifestEntry),
|
|
267
301
|
matched.params,
|
|
@@ -280,6 +314,7 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
280
314
|
deps.getRouteMap(),
|
|
281
315
|
matched.routeKey,
|
|
282
316
|
matched.responseType,
|
|
317
|
+
matched.pt === true,
|
|
283
318
|
);
|
|
284
319
|
|
|
285
320
|
const clientSegmentSet = new Set(clientSegmentIds);
|
|
@@ -325,16 +360,35 @@ export async function createMatchContextForPartial<TEnv>(
|
|
|
325
360
|
if (/D\d+\./.test(id)) return false;
|
|
326
361
|
return true;
|
|
327
362
|
});
|
|
363
|
+
const effectiveFromUrl = interceptSourceUrl ? interceptContextUrl : prevUrl;
|
|
364
|
+
const effectiveFromMatch = interceptSourceUrl
|
|
365
|
+
? interceptContextMatch
|
|
366
|
+
: prevMatch;
|
|
367
|
+
|
|
368
|
+
// Store previous route key on the request context for revalidation
|
|
369
|
+
// fromRouteName. Uses effectiveFromMatch so intercept-source navigations
|
|
370
|
+
// see the intercept origin route, not the plain previous URL route.
|
|
371
|
+
setRequestContextPrevRouteKey(effectiveFromMatch?.routeKey);
|
|
372
|
+
|
|
328
373
|
const interceptSelectorContext: InterceptSelectorContext = {
|
|
329
|
-
from:
|
|
374
|
+
from: effectiveFromUrl,
|
|
330
375
|
to: cleanUrl,
|
|
331
376
|
params: matched.params,
|
|
332
377
|
request,
|
|
333
378
|
env,
|
|
334
379
|
segments: {
|
|
335
|
-
path:
|
|
380
|
+
path: effectiveFromUrl.pathname.split("/").filter(Boolean),
|
|
336
381
|
ids: filteredSegmentIds,
|
|
337
382
|
},
|
|
383
|
+
fromRouteName:
|
|
384
|
+
effectiveFromMatch?.routeKey &&
|
|
385
|
+
!isAutoGeneratedRouteName(effectiveFromMatch.routeKey)
|
|
386
|
+
? effectiveFromMatch.routeKey
|
|
387
|
+
: undefined,
|
|
388
|
+
toRouteName:
|
|
389
|
+
matched.routeKey && !isAutoGeneratedRouteName(matched.routeKey)
|
|
390
|
+
? matched.routeKey
|
|
391
|
+
: undefined,
|
|
338
392
|
};
|
|
339
393
|
const isAction = !!actionContext;
|
|
340
394
|
|
|
@@ -537,10 +591,7 @@ export async function matchError<TEnv>(
|
|
|
537
591
|
|
|
538
592
|
const reqCtx = getRequestContext();
|
|
539
593
|
if (reqCtx) {
|
|
540
|
-
reqCtx.
|
|
541
|
-
status: 500,
|
|
542
|
-
headers: reqCtx.res.headers,
|
|
543
|
-
});
|
|
594
|
+
reqCtx.setStatus(500);
|
|
544
595
|
}
|
|
545
596
|
|
|
546
597
|
const effectiveFallback = fallback || DefaultErrorFallback;
|
|
@@ -567,46 +618,3 @@ export async function matchError<TEnv>(
|
|
|
567
618
|
params: matched.params,
|
|
568
619
|
};
|
|
569
620
|
}
|
|
570
|
-
|
|
571
|
-
/**
|
|
572
|
-
* Preview match - returns route middleware without segment resolution.
|
|
573
|
-
*/
|
|
574
|
-
export async function previewMatch<TEnv>(
|
|
575
|
-
request: Request,
|
|
576
|
-
context: TEnv,
|
|
577
|
-
deps: MatchApiDeps<TEnv>,
|
|
578
|
-
): Promise<{
|
|
579
|
-
routeMiddleware?: Array<{
|
|
580
|
-
handler: import("./middleware.js").MiddlewareFn;
|
|
581
|
-
params: Record<string, string>;
|
|
582
|
-
}>;
|
|
583
|
-
} | null> {
|
|
584
|
-
const url = new URL(request.url);
|
|
585
|
-
const pathname = url.pathname;
|
|
586
|
-
|
|
587
|
-
const matched = deps.findMatch(pathname);
|
|
588
|
-
if (!matched) {
|
|
589
|
-
return null;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
if (matched.redirectTo) {
|
|
593
|
-
return { routeMiddleware: undefined };
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
const manifestEntry = await loadManifest(
|
|
597
|
-
matched.entry,
|
|
598
|
-
matched.routeKey,
|
|
599
|
-
pathname,
|
|
600
|
-
undefined,
|
|
601
|
-
false,
|
|
602
|
-
);
|
|
603
|
-
|
|
604
|
-
const routeMiddleware = collectRouteMiddleware(
|
|
605
|
-
traverseBack(manifestEntry),
|
|
606
|
-
matched.params,
|
|
607
|
-
);
|
|
608
|
-
|
|
609
|
-
return {
|
|
610
|
-
routeMiddleware: routeMiddleware.length > 0 ? routeMiddleware : undefined,
|
|
611
|
-
};
|
|
612
|
-
}
|
|
@@ -210,6 +210,9 @@ export interface MatchPipelineState {
|
|
|
210
210
|
// Whether cache should be revalidated (SWR)
|
|
211
211
|
shouldRevalidate?: boolean;
|
|
212
212
|
|
|
213
|
+
// Source of cache hit ("runtime" or "prerender")
|
|
214
|
+
cacheSource?: "runtime" | "prerender";
|
|
215
|
+
|
|
213
216
|
// Resolved segments from pipeline
|
|
214
217
|
segments: ResolvedSegment[];
|
|
215
218
|
matchedIds: string[];
|
|
@@ -22,9 +22,21 @@ import {
|
|
|
22
22
|
matchError as _matchError,
|
|
23
23
|
} from "./match-api.js";
|
|
24
24
|
import { previewMatch as _previewMatch } from "./preview-match.js";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
runWithRouterLogContext,
|
|
27
|
+
withRouterLogScope,
|
|
28
|
+
isRouterDebugEnabled,
|
|
29
|
+
startRevalidationTrace,
|
|
30
|
+
flushRevalidationTrace,
|
|
31
|
+
} from "./logging.js";
|
|
26
32
|
import type { ErrorBoundaryHandler, NotFoundBoundaryHandler } from "../types";
|
|
27
33
|
import type { MiddlewareFn } from "./middleware.js";
|
|
34
|
+
import {
|
|
35
|
+
type TelemetrySink,
|
|
36
|
+
safeEmit,
|
|
37
|
+
resolveSink,
|
|
38
|
+
getRequestId,
|
|
39
|
+
} from "./telemetry.js";
|
|
28
40
|
|
|
29
41
|
export interface MatchHandlerDeps<TEnv = any> {
|
|
30
42
|
buildRouterContext: () => RouterContext<TEnv>;
|
|
@@ -38,6 +50,7 @@ export interface MatchHandlerDeps<TEnv = any> {
|
|
|
38
50
|
selectorContext: InterceptSelectorContext | null,
|
|
39
51
|
isAction: boolean,
|
|
40
52
|
) => { intercept: InterceptEntry; entry: EntryData } | null;
|
|
53
|
+
telemetry?: TelemetrySink;
|
|
41
54
|
}
|
|
42
55
|
|
|
43
56
|
export interface MatchHandlers<TEnv = any> {
|
|
@@ -98,6 +111,8 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
98
111
|
defaultErrorBoundary,
|
|
99
112
|
findInterceptForRoute,
|
|
100
113
|
} = deps;
|
|
114
|
+
const hasTelemetry = !!deps.telemetry;
|
|
115
|
+
const telemetry = resolveSink(deps.telemetry);
|
|
101
116
|
|
|
102
117
|
async function createMatchContextForFull(
|
|
103
118
|
request: Request,
|
|
@@ -140,13 +155,43 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
140
155
|
* - background-revalidation: SWR revalidation
|
|
141
156
|
*/
|
|
142
157
|
async function match(request: Request, env: TEnv): Promise<MatchResult> {
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
const requestId = hasTelemetry ? getRequestId(request) : undefined;
|
|
159
|
+
return runWithRouterLogContext({ request, transaction: "match" }, () => {
|
|
160
|
+
const routerCtx = buildRouterContext();
|
|
161
|
+
routerCtx.requestId = requestId;
|
|
162
|
+
return runWithRouterContext(routerCtx, async () =>
|
|
145
163
|
withRouterLogScope("match", async () => {
|
|
164
|
+
const matchStart = performance.now();
|
|
165
|
+
const pathname = new URL(request.url).pathname;
|
|
166
|
+
if (hasTelemetry) {
|
|
167
|
+
safeEmit(telemetry, {
|
|
168
|
+
type: "request.start",
|
|
169
|
+
timestamp: matchStart,
|
|
170
|
+
requestId,
|
|
171
|
+
method: request.method,
|
|
172
|
+
pathname,
|
|
173
|
+
transaction: "match",
|
|
174
|
+
isPartial: false,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
146
178
|
const result = await createMatchContextForFull(request, env);
|
|
147
179
|
|
|
148
180
|
// Handle redirect case
|
|
149
181
|
if ("type" in result && result.type === "redirect") {
|
|
182
|
+
if (hasTelemetry) {
|
|
183
|
+
safeEmit(telemetry, {
|
|
184
|
+
type: "request.end",
|
|
185
|
+
timestamp: performance.now(),
|
|
186
|
+
requestId,
|
|
187
|
+
method: request.method,
|
|
188
|
+
pathname,
|
|
189
|
+
transaction: "match",
|
|
190
|
+
durationMs: performance.now() - matchStart,
|
|
191
|
+
segmentCount: 0,
|
|
192
|
+
cacheHit: false,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
150
195
|
return {
|
|
151
196
|
segments: [],
|
|
152
197
|
matched: [],
|
|
@@ -161,8 +206,47 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
161
206
|
try {
|
|
162
207
|
const state = createPipelineState();
|
|
163
208
|
const pipeline = createMatchPartialPipeline(ctx, state);
|
|
164
|
-
|
|
209
|
+
const matchResult = await collectMatchResult(pipeline, ctx, state);
|
|
210
|
+
if (hasTelemetry) {
|
|
211
|
+
safeEmit(telemetry, {
|
|
212
|
+
type: "cache.decision",
|
|
213
|
+
timestamp: performance.now(),
|
|
214
|
+
requestId,
|
|
215
|
+
pathname,
|
|
216
|
+
routeKey: ctx.routeKey,
|
|
217
|
+
hit: state.cacheHit,
|
|
218
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
219
|
+
source: state.cacheSource,
|
|
220
|
+
});
|
|
221
|
+
safeEmit(telemetry, {
|
|
222
|
+
type: "request.end",
|
|
223
|
+
timestamp: performance.now(),
|
|
224
|
+
requestId,
|
|
225
|
+
method: request.method,
|
|
226
|
+
pathname,
|
|
227
|
+
transaction: "match",
|
|
228
|
+
durationMs: performance.now() - matchStart,
|
|
229
|
+
segmentCount: matchResult.segments.length,
|
|
230
|
+
cacheHit: state.cacheHit,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
return matchResult;
|
|
165
234
|
} catch (error) {
|
|
235
|
+
if (hasTelemetry) {
|
|
236
|
+
const errorObj =
|
|
237
|
+
error instanceof Error ? error : new Error(String(error));
|
|
238
|
+
safeEmit(telemetry, {
|
|
239
|
+
type: "request.error",
|
|
240
|
+
timestamp: performance.now(),
|
|
241
|
+
requestId,
|
|
242
|
+
method: request.method,
|
|
243
|
+
pathname,
|
|
244
|
+
transaction: "match",
|
|
245
|
+
error: errorObj,
|
|
246
|
+
phase: error instanceof Response ? "redirect" : "routing",
|
|
247
|
+
durationMs: performance.now() - matchStart,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
166
250
|
if (error instanceof Response) throw error;
|
|
167
251
|
// Report unhandled errors during full match pipeline
|
|
168
252
|
callOnError(error, "routing", {
|
|
@@ -175,8 +259,8 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
175
259
|
throw sanitizeError(error);
|
|
176
260
|
}
|
|
177
261
|
}),
|
|
178
|
-
)
|
|
179
|
-
);
|
|
262
|
+
);
|
|
263
|
+
});
|
|
180
264
|
}
|
|
181
265
|
|
|
182
266
|
async function matchError(
|
|
@@ -214,23 +298,112 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
214
298
|
context: TEnv,
|
|
215
299
|
actionContext?: ActionContext,
|
|
216
300
|
): Promise<MatchResult | null> {
|
|
301
|
+
const partialRequestId = hasTelemetry ? getRequestId(request) : undefined;
|
|
217
302
|
return runWithRouterLogContext(
|
|
218
303
|
{ request, transaction: "matchPartial" },
|
|
219
|
-
() =>
|
|
220
|
-
|
|
304
|
+
() => {
|
|
305
|
+
const routerCtx = buildRouterContext();
|
|
306
|
+
routerCtx.requestId = partialRequestId;
|
|
307
|
+
return runWithRouterContext(routerCtx, async () =>
|
|
221
308
|
withRouterLogScope("matchPartial", async () => {
|
|
309
|
+
const matchStart = performance.now();
|
|
310
|
+
const pathname = new URL(request.url).pathname;
|
|
311
|
+
if (hasTelemetry) {
|
|
312
|
+
safeEmit(telemetry, {
|
|
313
|
+
type: "request.start",
|
|
314
|
+
timestamp: matchStart,
|
|
315
|
+
requestId: partialRequestId,
|
|
316
|
+
method: request.method,
|
|
317
|
+
pathname,
|
|
318
|
+
transaction: "matchPartial",
|
|
319
|
+
isPartial: true,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
222
323
|
const ctx = await createMatchContextForPartial(
|
|
223
324
|
request,
|
|
224
325
|
context,
|
|
225
326
|
actionContext,
|
|
226
327
|
);
|
|
227
|
-
if (!ctx)
|
|
328
|
+
if (!ctx) {
|
|
329
|
+
if (hasTelemetry) {
|
|
330
|
+
safeEmit(telemetry, {
|
|
331
|
+
type: "request.end",
|
|
332
|
+
timestamp: performance.now(),
|
|
333
|
+
requestId: partialRequestId,
|
|
334
|
+
method: request.method,
|
|
335
|
+
pathname,
|
|
336
|
+
transaction: "matchPartial",
|
|
337
|
+
durationMs: performance.now() - matchStart,
|
|
338
|
+
segmentCount: 0,
|
|
339
|
+
cacheHit: false,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (isRouterDebugEnabled()) {
|
|
346
|
+
startRevalidationTrace({
|
|
347
|
+
method: request.method,
|
|
348
|
+
prevUrl: ctx.prevUrl.href,
|
|
349
|
+
nextUrl: ctx.url.href,
|
|
350
|
+
routeKey: ctx.routeKey,
|
|
351
|
+
isAction: !!actionContext,
|
|
352
|
+
stale: ctx.stale || undefined,
|
|
353
|
+
});
|
|
354
|
+
}
|
|
228
355
|
|
|
229
356
|
try {
|
|
230
357
|
const state = createPipelineState();
|
|
231
358
|
const pipeline = createMatchPartialPipeline(ctx, state);
|
|
232
|
-
|
|
359
|
+
const matchResult = await collectMatchResult(
|
|
360
|
+
pipeline,
|
|
361
|
+
ctx,
|
|
362
|
+
state,
|
|
363
|
+
);
|
|
364
|
+
flushRevalidationTrace();
|
|
365
|
+
if (hasTelemetry) {
|
|
366
|
+
safeEmit(telemetry, {
|
|
367
|
+
type: "cache.decision",
|
|
368
|
+
timestamp: performance.now(),
|
|
369
|
+
requestId: partialRequestId,
|
|
370
|
+
pathname,
|
|
371
|
+
routeKey: ctx.routeKey,
|
|
372
|
+
hit: state.cacheHit,
|
|
373
|
+
shouldRevalidate: !!state.shouldRevalidate,
|
|
374
|
+
source: state.cacheSource,
|
|
375
|
+
});
|
|
376
|
+
safeEmit(telemetry, {
|
|
377
|
+
type: "request.end",
|
|
378
|
+
timestamp: performance.now(),
|
|
379
|
+
requestId: partialRequestId,
|
|
380
|
+
method: request.method,
|
|
381
|
+
pathname,
|
|
382
|
+
transaction: "matchPartial",
|
|
383
|
+
durationMs: performance.now() - matchStart,
|
|
384
|
+
segmentCount: matchResult.segments.length,
|
|
385
|
+
cacheHit: state.cacheHit,
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
return matchResult;
|
|
233
389
|
} catch (error) {
|
|
390
|
+
flushRevalidationTrace();
|
|
391
|
+
if (hasTelemetry) {
|
|
392
|
+
const errorObj =
|
|
393
|
+
error instanceof Error ? error : new Error(String(error));
|
|
394
|
+
const phase = actionContext ? "action" : "revalidation";
|
|
395
|
+
safeEmit(telemetry, {
|
|
396
|
+
type: "request.error",
|
|
397
|
+
timestamp: performance.now(),
|
|
398
|
+
requestId: partialRequestId,
|
|
399
|
+
method: request.method,
|
|
400
|
+
pathname,
|
|
401
|
+
transaction: "matchPartial",
|
|
402
|
+
error: errorObj,
|
|
403
|
+
phase: error instanceof Response ? "redirect" : phase,
|
|
404
|
+
durationMs: performance.now() - matchStart,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
234
407
|
if (error instanceof Response) throw error;
|
|
235
408
|
// Report unhandled errors during partial match pipeline
|
|
236
409
|
callOnError(error, actionContext ? "action" : "revalidation", {
|
|
@@ -244,7 +417,8 @@ export function createMatchHandlers<TEnv = any>(
|
|
|
244
417
|
throw sanitizeError(error);
|
|
245
418
|
}
|
|
246
419
|
}),
|
|
247
|
-
)
|
|
420
|
+
);
|
|
421
|
+
},
|
|
248
422
|
);
|
|
249
423
|
}
|
|
250
424
|
|