@rangojs/router 0.0.0-experimental.f2337aef → 0.0.0-experimental.fa8a383a
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/dist/bin/rango.js +8 -3
- package/dist/vite/index.js +139 -200
- package/package.json +1 -1
- package/skills/caching/SKILL.md +37 -4
- package/skills/parallel/SKILL.md +59 -0
- package/src/browser/event-controller.ts +5 -0
- package/src/browser/navigation-bridge.ts +1 -3
- package/src/browser/navigation-client.ts +60 -27
- package/src/browser/navigation-transaction.ts +11 -9
- package/src/browser/partial-update.ts +39 -9
- package/src/browser/prefetch/cache.ts +57 -5
- package/src/browser/prefetch/fetch.ts +30 -21
- package/src/browser/prefetch/queue.ts +53 -13
- package/src/browser/react/Link.tsx +9 -1
- package/src/browser/react/NavigationProvider.tsx +27 -0
- package/src/browser/rsc-router.tsx +109 -57
- package/src/browser/scroll-restoration.ts +20 -7
- package/src/browser/segment-reconciler.ts +6 -1
- package/src/browser/types.ts +9 -0
- package/src/build/route-types/router-processing.ts +12 -2
- package/src/cache/cache-scope.ts +2 -2
- package/src/cache/cf/cf-cache-store.ts +453 -11
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/debug.ts +2 -2
- package/src/route-definition/dsl-helpers.ts +32 -7
- package/src/route-definition/redirect.ts +2 -2
- package/src/router/lazy-includes.ts +4 -1
- package/src/router/logging.ts +1 -1
- package/src/router/manifest.ts +9 -3
- package/src/router/match-middleware/background-revalidation.ts +18 -1
- package/src/router/match-middleware/cache-lookup.ts +20 -3
- package/src/router/match-middleware/cache-store.ts +32 -6
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +7 -5
- package/src/router/match-result.ts +11 -1
- package/src/router/middleware.ts +2 -1
- package/src/router/segment-resolution/fresh.ts +104 -14
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +307 -272
- package/src/router.ts +5 -1
- package/src/rsc/handler.ts +9 -0
- package/src/segment-system.tsx +140 -4
- package/src/server/context.ts +90 -13
- package/src/server/request-context.ts +10 -4
- package/src/ssr/index.tsx +1 -0
- package/src/types/route-entry.ts +7 -0
- package/src/types/segments.ts +2 -0
- package/src/urls/path-helper.ts +1 -1
- package/src/vite/discovery/state.ts +0 -2
- package/src/vite/plugin-types.ts +0 -83
- package/src/vite/plugins/expose-action-id.ts +1 -3
- package/src/vite/plugins/version-plugin.ts +13 -1
- package/src/vite/rango.ts +144 -209
- package/src/vite/router-discovery.ts +0 -8
- package/src/vite/utils/banner.ts +3 -3
package/src/cache/index.ts
CHANGED
package/src/debug.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Debug utilities for manifest inspection and comparison
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type
|
|
5
|
+
import { getParallelSlotCount, type EntryData } from "./server/context";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Serialized entry for debug output
|
|
@@ -64,7 +64,7 @@ export function serializeManifest(
|
|
|
64
64
|
hasLoader: entry.loader?.length > 0,
|
|
65
65
|
hasMiddleware: entry.middleware?.length > 0,
|
|
66
66
|
hasErrorBoundary: entry.errorBoundary?.length > 0,
|
|
67
|
-
parallelCount: entry.parallel
|
|
67
|
+
parallelCount: getParallelSlotCount(entry.parallel),
|
|
68
68
|
interceptCount: entry.intercept?.length ?? 0,
|
|
69
69
|
};
|
|
70
70
|
|
|
@@ -282,7 +282,7 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
282
282
|
errorBoundary: [],
|
|
283
283
|
notFoundBoundary: [],
|
|
284
284
|
layout: [],
|
|
285
|
-
parallel:
|
|
285
|
+
parallel: {},
|
|
286
286
|
intercept: [],
|
|
287
287
|
loader: [],
|
|
288
288
|
...(cacheUrlPrefix ? { mountPath: cacheUrlPrefix } : {}),
|
|
@@ -320,7 +320,7 @@ const cache: RouteHelpers<any, any>["cache"] = (
|
|
|
320
320
|
errorBoundary: [],
|
|
321
321
|
notFoundBoundary: [],
|
|
322
322
|
layout: [],
|
|
323
|
-
parallel:
|
|
323
|
+
parallel: {},
|
|
324
324
|
intercept: [],
|
|
325
325
|
loader: [],
|
|
326
326
|
...(cacheUrlPrefix2 ? { mountPath: cacheUrlPrefix2 } : {}),
|
|
@@ -393,6 +393,8 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
393
393
|
"parallel() cannot be nested inside another parallel()",
|
|
394
394
|
);
|
|
395
395
|
|
|
396
|
+
const slotNames = Object.keys(slots as Record<string, any>) as `@${string}`[];
|
|
397
|
+
|
|
396
398
|
const namespace = `${ctx.namespace}.$${store.getNextIndex("parallel")}`;
|
|
397
399
|
|
|
398
400
|
// Unwrap any static handler definitions in parallel slots
|
|
@@ -431,7 +433,7 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
431
433
|
errorBoundary: [],
|
|
432
434
|
notFoundBoundary: [],
|
|
433
435
|
layout: [],
|
|
434
|
-
parallel:
|
|
436
|
+
parallel: {},
|
|
435
437
|
intercept: [],
|
|
436
438
|
loader: [],
|
|
437
439
|
...(parallelUrlPrefix ? { mountPath: parallelUrlPrefix } : {}),
|
|
@@ -454,7 +456,30 @@ const parallel: RouteHelpers<any, any>["parallel"] = (slots, use) => {
|
|
|
454
456
|
);
|
|
455
457
|
}
|
|
456
458
|
|
|
457
|
-
|
|
459
|
+
for (const slotName of slotNames) {
|
|
460
|
+
const slotEntry = {
|
|
461
|
+
...entry,
|
|
462
|
+
handler: { [slotName]: unwrappedSlots[slotName]! },
|
|
463
|
+
middleware: [...entry.middleware],
|
|
464
|
+
revalidate: [...entry.revalidate],
|
|
465
|
+
errorBoundary: [...entry.errorBoundary],
|
|
466
|
+
notFoundBoundary: [...entry.notFoundBoundary],
|
|
467
|
+
layout: [...entry.layout],
|
|
468
|
+
parallel: { ...entry.parallel },
|
|
469
|
+
intercept: [...entry.intercept],
|
|
470
|
+
loader: [...entry.loader],
|
|
471
|
+
...(entry.staticHandlerIds?.[slotName]
|
|
472
|
+
? {
|
|
473
|
+
isStaticPrerender: true as const,
|
|
474
|
+
staticHandlerIds: { [slotName]: entry.staticHandlerIds[slotName]! },
|
|
475
|
+
}
|
|
476
|
+
: {
|
|
477
|
+
isStaticPrerender: undefined,
|
|
478
|
+
staticHandlerIds: undefined,
|
|
479
|
+
}),
|
|
480
|
+
} satisfies EntryData;
|
|
481
|
+
ctx.parent.parallel[slotName] = slotEntry;
|
|
482
|
+
}
|
|
458
483
|
return { name: namespace, type: "parallel" } as ParallelItem;
|
|
459
484
|
};
|
|
460
485
|
|
|
@@ -687,7 +712,7 @@ const transitionFn = (
|
|
|
687
712
|
errorBoundary: [],
|
|
688
713
|
notFoundBoundary: [],
|
|
689
714
|
layout: [],
|
|
690
|
-
parallel:
|
|
715
|
+
parallel: {},
|
|
691
716
|
intercept: [],
|
|
692
717
|
loader: [],
|
|
693
718
|
} as EntryData;
|
|
@@ -734,7 +759,7 @@ const routeFn: RouteHelpers<any, any>["route"] = (name, handler, use) => {
|
|
|
734
759
|
errorBoundary: [],
|
|
735
760
|
notFoundBoundary: [],
|
|
736
761
|
layout: [],
|
|
737
|
-
parallel:
|
|
762
|
+
parallel: {},
|
|
738
763
|
intercept: [],
|
|
739
764
|
loader: [],
|
|
740
765
|
} satisfies EntryData;
|
|
@@ -791,7 +816,7 @@ const layout: RouteHelpers<any, any>["layout"] = (handler, use) => {
|
|
|
791
816
|
revalidate: [],
|
|
792
817
|
errorBoundary: [],
|
|
793
818
|
notFoundBoundary: [],
|
|
794
|
-
parallel:
|
|
819
|
+
parallel: {},
|
|
795
820
|
intercept: [],
|
|
796
821
|
layout: [],
|
|
797
822
|
loader: [],
|
|
@@ -71,9 +71,9 @@ export function redirect(
|
|
|
71
71
|
// actions both deliver state through Flight payloads, so suppress for those.
|
|
72
72
|
if (
|
|
73
73
|
reqCtx &&
|
|
74
|
-
!reqCtx.
|
|
74
|
+
!reqCtx.originalUrl.searchParams.has("_rsc_partial") &&
|
|
75
75
|
!reqCtx.request.headers.has("rsc-action") &&
|
|
76
|
-
!reqCtx.
|
|
76
|
+
!reqCtx.originalUrl.searchParams.has("_rsc_action")
|
|
77
77
|
) {
|
|
78
78
|
console.warn(
|
|
79
79
|
`[Router] redirect() with state during a full-page (SSR) request to "${url}". ` +
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
EntryData,
|
|
5
5
|
RSCRouterContext,
|
|
6
6
|
runWithPrefixes,
|
|
7
|
+
getIsolatedLazyParent,
|
|
7
8
|
} from "../server/context";
|
|
8
9
|
import type { UrlPatterns } from "../urls.js";
|
|
9
10
|
import type { AllUseItems, IncludeItem } from "../route-types.js";
|
|
@@ -14,6 +15,7 @@ export interface LazyEvalDeps<TEnv = any> {
|
|
|
14
15
|
mergedRouteMap: Record<string, string>;
|
|
15
16
|
nextMountIndex: () => number;
|
|
16
17
|
getPrecomputedByPrefix: () => Map<string, Record<string, string>> | null;
|
|
18
|
+
routerId?: string;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
// Detect lazy includes in handler result and create placeholder entries
|
|
@@ -137,7 +139,7 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
137
139
|
patternsByPrefix,
|
|
138
140
|
trailingSlash: trailingSlashMap,
|
|
139
141
|
namespace: "lazy",
|
|
140
|
-
parent: (lazyContext?.parent as EntryData | null)
|
|
142
|
+
parent: getIsolatedLazyParent(lazyContext?.parent as EntryData | null),
|
|
141
143
|
counters: lazyCounters,
|
|
142
144
|
cacheProfiles: (lazyContext as any)?.cacheProfiles,
|
|
143
145
|
rootScoped: (lazyContext as any)?.rootScoped,
|
|
@@ -200,6 +202,7 @@ export function evaluateLazyEntry<TEnv = any>(
|
|
|
200
202
|
trailingSlash: entry.trailingSlash,
|
|
201
203
|
handler: (lazyInclude.patterns as UrlPatterns<TEnv>).handler,
|
|
202
204
|
mountIndex: deps.nextMountIndex(),
|
|
205
|
+
routerId: deps.routerId,
|
|
203
206
|
// Lazy evaluation fields
|
|
204
207
|
lazy: true,
|
|
205
208
|
lazyPatterns: lazyInclude.patterns,
|
package/src/router/logging.ts
CHANGED
|
@@ -74,7 +74,7 @@ function getHeaderRequestId(request: Request): string | null {
|
|
|
74
74
|
return trimmed.length > 0 ? trimmed : null;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function getOrCreateRequestId(request: Request): string {
|
|
77
|
+
export function getOrCreateRequestId(request: Request): string {
|
|
78
78
|
const existing = requestIds.get(request);
|
|
79
79
|
if (existing) return existing;
|
|
80
80
|
|
package/src/router/manifest.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { createRouteHelpers } from "../route-definition";
|
|
|
9
9
|
import {
|
|
10
10
|
getContext,
|
|
11
11
|
runWithPrefixes,
|
|
12
|
+
getIsolatedLazyParent,
|
|
12
13
|
type EntryData,
|
|
13
14
|
type MetricsStore,
|
|
14
15
|
} from "../server/context";
|
|
@@ -65,7 +66,9 @@ export async function loadManifest(
|
|
|
65
66
|
const mountIndex = entry.mountIndex;
|
|
66
67
|
|
|
67
68
|
// Check module-level cache (persists across requests within same isolate)
|
|
68
|
-
|
|
69
|
+
// Include routerId so multi-router setups (host routing) don't share cached
|
|
70
|
+
// EntryData across routers with overlapping mountIndex + routeKey combinations.
|
|
71
|
+
const cacheKey = `${VERSION}:${entry.routerId ?? ""}:${mountIndex ?? ""}:${routeKey}:${isSSR ? 1 : 0}`;
|
|
69
72
|
const cached = manifestModuleCache.get(cacheKey);
|
|
70
73
|
if (cached) {
|
|
71
74
|
const cacheStart = performance.now();
|
|
@@ -112,8 +115,11 @@ export async function loadManifest(
|
|
|
112
115
|
// This ensures routes are registered under the correct layout hierarchy
|
|
113
116
|
const lazyContext =
|
|
114
117
|
entry.lazy && entry.lazyPatterns ? entry.lazyContext : null;
|
|
115
|
-
const parentForContext =
|
|
116
|
-
(
|
|
118
|
+
const parentForContext = lazyContext
|
|
119
|
+
? getIsolatedLazyParent(
|
|
120
|
+
(lazyContext.parent as EntryData | null) ?? Store.parent,
|
|
121
|
+
)
|
|
122
|
+
: Store.parent;
|
|
117
123
|
|
|
118
124
|
// For lazy entries, merge captured counters from include() so the
|
|
119
125
|
// handler's entries get shortCode indices after sibling entries that
|
|
@@ -103,7 +103,8 @@ import type { ResolvedSegment } from "../../types.js";
|
|
|
103
103
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
104
104
|
import { getRouterContext } from "../router-context.js";
|
|
105
105
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
106
|
-
import { debugLog, debugWarn } from "../logging.js";
|
|
106
|
+
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
107
|
+
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
107
108
|
|
|
108
109
|
/**
|
|
109
110
|
* Creates background revalidation middleware
|
|
@@ -143,8 +144,12 @@ export function withBackgroundRevalidation<TEnv>(
|
|
|
143
144
|
|
|
144
145
|
const requestCtx = getRequestContext();
|
|
145
146
|
const cacheScope = ctx.cacheScope;
|
|
147
|
+
const reqId = INTERNAL_RANGO_DEBUG
|
|
148
|
+
? getOrCreateRequestId(ctx.request)
|
|
149
|
+
: undefined;
|
|
146
150
|
|
|
147
151
|
requestCtx?.waitUntil(async () => {
|
|
152
|
+
const start = performance.now();
|
|
148
153
|
debugLog("backgroundRevalidation", "revalidating stale route", {
|
|
149
154
|
pathname: ctx.pathname,
|
|
150
155
|
fullMatch: ctx.isFullMatch,
|
|
@@ -207,10 +212,22 @@ export function withBackgroundRevalidation<TEnv>(
|
|
|
207
212
|
completeSegments,
|
|
208
213
|
ctx.isIntercept,
|
|
209
214
|
);
|
|
215
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
216
|
+
const dur = performance.now() - start;
|
|
217
|
+
console.log(
|
|
218
|
+
`[RSC Background][req:${reqId}] SWR revalidation ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${completeSegments.length}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
210
221
|
debugLog("backgroundRevalidation", "revalidation complete", {
|
|
211
222
|
pathname: ctx.pathname,
|
|
212
223
|
});
|
|
213
224
|
} catch (error) {
|
|
225
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
226
|
+
const dur = performance.now() - start;
|
|
227
|
+
console.log(
|
|
228
|
+
`[RSC Background][req:${reqId}] SWR revalidation ${ctx.pathname} FAILED (${dur.toFixed(2)}ms) error=${String(error)}`,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
214
231
|
debugWarn("backgroundRevalidation", "revalidation failed", {
|
|
215
232
|
pathname: ctx.pathname,
|
|
216
233
|
error: String(error),
|
|
@@ -210,6 +210,9 @@ async function* yieldFromStore<TEnv>(
|
|
|
210
210
|
}
|
|
211
211
|
|
|
212
212
|
// Resolve loaders fresh (loaders are never pre-rendered/cached)
|
|
213
|
+
const ms = ctx.metricsStore;
|
|
214
|
+
const loaderStart = performance.now();
|
|
215
|
+
|
|
213
216
|
if (ctx.isFullMatch) {
|
|
214
217
|
if (resolveLoadersOnly) {
|
|
215
218
|
const loaderSegments = await ctx.Store.run(() =>
|
|
@@ -249,11 +252,17 @@ async function* yieldFromStore<TEnv>(
|
|
|
249
252
|
}
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
const ms = ctx.metricsStore;
|
|
253
255
|
if (ms) {
|
|
256
|
+
const loaderEnd = performance.now();
|
|
257
|
+
ms.metrics.push({
|
|
258
|
+
label: "pipeline:loader-resolve",
|
|
259
|
+
duration: loaderEnd - loaderStart,
|
|
260
|
+
startTime: loaderStart - ms.requestStart,
|
|
261
|
+
depth: 1,
|
|
262
|
+
});
|
|
254
263
|
ms.metrics.push({
|
|
255
264
|
label: "pipeline:cache-lookup",
|
|
256
|
-
duration:
|
|
265
|
+
duration: loaderEnd - pipelineStart,
|
|
257
266
|
startTime: pipelineStart - ms.requestStart,
|
|
258
267
|
});
|
|
259
268
|
}
|
|
@@ -573,6 +582,7 @@ export function withCacheLookup<TEnv>(
|
|
|
573
582
|
// Resolve loaders fresh (loaders are NOT cached by default)
|
|
574
583
|
// This ensures fresh data even on cache hit
|
|
575
584
|
const Store = ctx.Store;
|
|
585
|
+
const loaderStart = performance.now();
|
|
576
586
|
|
|
577
587
|
if (ctx.isFullMatch) {
|
|
578
588
|
// Full match (document request) - simple loader resolution without revalidation
|
|
@@ -624,9 +634,16 @@ export function withCacheLookup<TEnv>(
|
|
|
624
634
|
}
|
|
625
635
|
}
|
|
626
636
|
if (ms) {
|
|
637
|
+
const loaderEnd = performance.now();
|
|
638
|
+
ms.metrics.push({
|
|
639
|
+
label: "pipeline:loader-resolve",
|
|
640
|
+
duration: loaderEnd - loaderStart,
|
|
641
|
+
startTime: loaderStart - ms.requestStart,
|
|
642
|
+
depth: 1,
|
|
643
|
+
});
|
|
627
644
|
ms.metrics.push({
|
|
628
645
|
label: "pipeline:cache-lookup",
|
|
629
|
-
duration:
|
|
646
|
+
duration: loaderEnd - pipelineStart,
|
|
630
647
|
startTime: pipelineStart - ms.requestStart,
|
|
631
648
|
});
|
|
632
649
|
}
|
|
@@ -104,7 +104,8 @@ import type { ResolvedSegment } from "../../types.js";
|
|
|
104
104
|
import { getRequestContext } from "../../server/request-context.js";
|
|
105
105
|
import type { MatchContext, MatchPipelineState } from "../match-context.js";
|
|
106
106
|
import { getRouterContext } from "../router-context.js";
|
|
107
|
-
import { debugLog, debugWarn } from "../logging.js";
|
|
107
|
+
import { debugLog, debugWarn, getOrCreateRequestId } from "../logging.js";
|
|
108
|
+
import { INTERNAL_RANGO_DEBUG } from "../../internal-debug.js";
|
|
108
109
|
import type { GeneratorMiddleware } from "./cache-lookup.js";
|
|
109
110
|
|
|
110
111
|
/**
|
|
@@ -120,7 +121,6 @@ export function withCacheStore<TEnv>(
|
|
|
120
121
|
return async function* (
|
|
121
122
|
source: AsyncGenerator<ResolvedSegment>,
|
|
122
123
|
): AsyncGenerator<ResolvedSegment> {
|
|
123
|
-
const pipelineStart = performance.now();
|
|
124
124
|
const ms = ctx.metricsStore;
|
|
125
125
|
|
|
126
126
|
// Collect all segments while passing them through
|
|
@@ -130,6 +130,9 @@ export function withCacheStore<TEnv>(
|
|
|
130
130
|
yield segment;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
// Measure own work only (after source iteration completes)
|
|
134
|
+
const ownStart = performance.now();
|
|
135
|
+
|
|
133
136
|
// Skip caching if:
|
|
134
137
|
// 1. Cache miss but cache scope is disabled
|
|
135
138
|
// 2. This is an action (actions don't cache)
|
|
@@ -144,8 +147,8 @@ export function withCacheStore<TEnv>(
|
|
|
144
147
|
if (ms) {
|
|
145
148
|
ms.metrics.push({
|
|
146
149
|
label: "pipeline:cache-store",
|
|
147
|
-
duration: performance.now() -
|
|
148
|
-
startTime:
|
|
150
|
+
duration: performance.now() - ownStart,
|
|
151
|
+
startTime: ownStart - ms.requestStart,
|
|
149
152
|
});
|
|
150
153
|
}
|
|
151
154
|
return;
|
|
@@ -172,6 +175,9 @@ export function withCacheStore<TEnv>(
|
|
|
172
175
|
if (!requestCtx) return;
|
|
173
176
|
|
|
174
177
|
const cacheScope = ctx.cacheScope;
|
|
178
|
+
const reqId = INTERNAL_RANGO_DEBUG
|
|
179
|
+
? getOrCreateRequestId(ctx.request)
|
|
180
|
+
: undefined;
|
|
175
181
|
|
|
176
182
|
// Register onResponse callback to skip caching for non-200 responses
|
|
177
183
|
// Note: error/notFound status codes are set elsewhere (not caching-specific)
|
|
@@ -189,6 +195,7 @@ export function withCacheStore<TEnv>(
|
|
|
189
195
|
// Proactive caching: render all segments fresh in background
|
|
190
196
|
// This ensures cache has complete components for future requests
|
|
191
197
|
requestCtx.waitUntil(async () => {
|
|
198
|
+
const start = performance.now();
|
|
192
199
|
debugLog("cacheStore", "proactive caching started", {
|
|
193
200
|
pathname: ctx.pathname,
|
|
194
201
|
});
|
|
@@ -256,10 +263,22 @@ export function withCacheStore<TEnv>(
|
|
|
256
263
|
completeSegments,
|
|
257
264
|
ctx.isIntercept,
|
|
258
265
|
);
|
|
266
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
267
|
+
const dur = performance.now() - start;
|
|
268
|
+
console.log(
|
|
269
|
+
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${completeSegments.length}`,
|
|
270
|
+
);
|
|
271
|
+
}
|
|
259
272
|
debugLog("cacheStore", "proactive caching complete", {
|
|
260
273
|
pathname: ctx.pathname,
|
|
261
274
|
});
|
|
262
275
|
} catch (error) {
|
|
276
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
277
|
+
const dur = performance.now() - start;
|
|
278
|
+
console.log(
|
|
279
|
+
`[RSC Background][req:${reqId}] Proactive cache ${ctx.pathname} FAILED (${dur.toFixed(2)}ms) error=${String(error)}`,
|
|
280
|
+
);
|
|
281
|
+
}
|
|
263
282
|
debugWarn("cacheStore", "proactive caching failed", {
|
|
264
283
|
pathname: ctx.pathname,
|
|
265
284
|
error: String(error),
|
|
@@ -272,12 +291,19 @@ export function withCacheStore<TEnv>(
|
|
|
272
291
|
// All segments have components - cache directly
|
|
273
292
|
// Schedule caching in waitUntil since cacheRoute is now async (key resolution)
|
|
274
293
|
requestCtx.waitUntil(async () => {
|
|
294
|
+
const start = performance.now();
|
|
275
295
|
await cacheScope.cacheRoute(
|
|
276
296
|
ctx.pathname,
|
|
277
297
|
ctx.matched.params,
|
|
278
298
|
allSegmentsToCache,
|
|
279
299
|
ctx.isIntercept,
|
|
280
300
|
);
|
|
301
|
+
if (INTERNAL_RANGO_DEBUG) {
|
|
302
|
+
const dur = performance.now() - start;
|
|
303
|
+
console.log(
|
|
304
|
+
`[RSC Background][req:${reqId}] Cache store ${ctx.pathname} (${dur.toFixed(2)}ms) segments=${allSegmentsToCache.length}`,
|
|
305
|
+
);
|
|
306
|
+
}
|
|
281
307
|
});
|
|
282
308
|
}
|
|
283
309
|
|
|
@@ -287,8 +313,8 @@ export function withCacheStore<TEnv>(
|
|
|
287
313
|
if (ms) {
|
|
288
314
|
ms.metrics.push({
|
|
289
315
|
label: "pipeline:cache-store",
|
|
290
|
-
duration: performance.now() -
|
|
291
|
-
startTime:
|
|
316
|
+
duration: performance.now() - ownStart,
|
|
317
|
+
startTime: ownStart - ms.requestStart,
|
|
292
318
|
});
|
|
293
319
|
}
|
|
294
320
|
};
|
|
@@ -123,7 +123,6 @@ export function withInterceptResolution<TEnv>(
|
|
|
123
123
|
return async function* (
|
|
124
124
|
source: AsyncGenerator<ResolvedSegment>,
|
|
125
125
|
): AsyncGenerator<ResolvedSegment> {
|
|
126
|
-
const pipelineStart = performance.now();
|
|
127
126
|
const ms = ctx.metricsStore;
|
|
128
127
|
|
|
129
128
|
// First, yield all segments from the source (main segment resolution or cache)
|
|
@@ -133,13 +132,16 @@ export function withInterceptResolution<TEnv>(
|
|
|
133
132
|
yield segment;
|
|
134
133
|
}
|
|
135
134
|
|
|
135
|
+
// Measure own work only (after source iteration completes)
|
|
136
|
+
const ownStart = performance.now();
|
|
137
|
+
|
|
136
138
|
// Skip intercept resolution for full match (document requests don't have intercepts)
|
|
137
139
|
if (ctx.isFullMatch) {
|
|
138
140
|
if (ms) {
|
|
139
141
|
ms.metrics.push({
|
|
140
142
|
label: "pipeline:intercept",
|
|
141
|
-
duration: performance.now() -
|
|
142
|
-
startTime:
|
|
143
|
+
duration: performance.now() - ownStart,
|
|
144
|
+
startTime: ownStart - ms.requestStart,
|
|
143
145
|
});
|
|
144
146
|
}
|
|
145
147
|
return;
|
|
@@ -163,8 +165,8 @@ export function withInterceptResolution<TEnv>(
|
|
|
163
165
|
if (ms) {
|
|
164
166
|
ms.metrics.push({
|
|
165
167
|
label: "pipeline:intercept",
|
|
166
|
-
duration: performance.now() -
|
|
167
|
-
startTime:
|
|
168
|
+
duration: performance.now() - ownStart,
|
|
169
|
+
startTime: ownStart - ms.requestStart,
|
|
168
170
|
});
|
|
169
171
|
}
|
|
170
172
|
return;
|
|
@@ -216,8 +218,8 @@ export function withInterceptResolution<TEnv>(
|
|
|
216
218
|
if (ms) {
|
|
217
219
|
ms.metrics.push({
|
|
218
220
|
label: "pipeline:intercept",
|
|
219
|
-
duration: performance.now() -
|
|
220
|
-
startTime:
|
|
221
|
+
duration: performance.now() - ownStart,
|
|
222
|
+
startTime: ownStart - ms.requestStart,
|
|
221
223
|
});
|
|
222
224
|
}
|
|
223
225
|
};
|
|
@@ -104,7 +104,6 @@ export function withSegmentResolution<TEnv>(
|
|
|
104
104
|
return async function* (
|
|
105
105
|
source: AsyncGenerator<ResolvedSegment>,
|
|
106
106
|
): AsyncGenerator<ResolvedSegment> {
|
|
107
|
-
const pipelineStart = performance.now();
|
|
108
107
|
const ms = ctx.metricsStore;
|
|
109
108
|
|
|
110
109
|
// IMPORTANT: Always iterate source first to give cache-lookup a chance
|
|
@@ -113,13 +112,16 @@ export function withSegmentResolution<TEnv>(
|
|
|
113
112
|
yield segment;
|
|
114
113
|
}
|
|
115
114
|
|
|
115
|
+
// Measure own work only (after source iteration completes)
|
|
116
|
+
const ownStart = performance.now();
|
|
117
|
+
|
|
116
118
|
// If cache hit, segments were already yielded by cache lookup
|
|
117
119
|
if (state.cacheHit) {
|
|
118
120
|
if (ms) {
|
|
119
121
|
ms.metrics.push({
|
|
120
122
|
label: "pipeline:segment-resolve",
|
|
121
|
-
duration: performance.now() -
|
|
122
|
-
startTime:
|
|
123
|
+
duration: performance.now() - ownStart,
|
|
124
|
+
startTime: ownStart - ms.requestStart,
|
|
123
125
|
});
|
|
124
126
|
}
|
|
125
127
|
return;
|
|
@@ -185,8 +187,8 @@ export function withSegmentResolution<TEnv>(
|
|
|
185
187
|
if (ms) {
|
|
186
188
|
ms.metrics.push({
|
|
187
189
|
label: "pipeline:segment-resolve",
|
|
188
|
-
duration: performance.now() -
|
|
189
|
-
startTime:
|
|
190
|
+
duration: performance.now() - ownStart,
|
|
191
|
+
startTime: ownStart - ms.requestStart,
|
|
190
192
|
});
|
|
191
193
|
}
|
|
192
194
|
};
|
|
@@ -109,6 +109,7 @@
|
|
|
109
109
|
import type { MatchResult, ResolvedSegment } from "../types.js";
|
|
110
110
|
import type { MatchContext, MatchPipelineState } from "./match-context.js";
|
|
111
111
|
import { debugLog } from "./logging.js";
|
|
112
|
+
import { appendMetric } from "./metrics.js";
|
|
112
113
|
|
|
113
114
|
/**
|
|
114
115
|
* Collect all segments from an async generator
|
|
@@ -210,10 +211,19 @@ export async function collectMatchResult<TEnv>(
|
|
|
210
211
|
): Promise<MatchResult> {
|
|
211
212
|
const allSegments = await collectSegments(pipeline);
|
|
212
213
|
|
|
214
|
+
const buildStart = performance.now();
|
|
215
|
+
|
|
213
216
|
// Update state with collected segments if not already set
|
|
214
217
|
if (state.segments.length === 0) {
|
|
215
218
|
state.segments = allSegments;
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
|
|
221
|
+
const result = buildMatchResult(allSegments, ctx, state);
|
|
222
|
+
appendMetric(
|
|
223
|
+
ctx.metricsStore,
|
|
224
|
+
"collect-result",
|
|
225
|
+
buildStart,
|
|
226
|
+
performance.now() - buildStart,
|
|
227
|
+
);
|
|
228
|
+
return result;
|
|
219
229
|
}
|
package/src/router/middleware.ts
CHANGED
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
import { _getRequestContext } from "../server/request-context.js";
|
|
22
22
|
import { isAutoGeneratedRouteName } from "../route-name.js";
|
|
23
23
|
import { appendMetric, createMetricsStore } from "./metrics.js";
|
|
24
|
+
import { stripInternalParams } from "./handler-context.js";
|
|
24
25
|
|
|
25
26
|
// Re-export types and cookie utilities for backward compatibility
|
|
26
27
|
export type {
|
|
@@ -147,7 +148,7 @@ export function createMiddlewareContext<TEnv>(
|
|
|
147
148
|
search?: Record<string, unknown>,
|
|
148
149
|
) => string,
|
|
149
150
|
): MiddlewareContext<TEnv> {
|
|
150
|
-
const url = new URL(request.url);
|
|
151
|
+
const url = stripInternalParams(new URL(request.url));
|
|
151
152
|
|
|
152
153
|
// Track the initial response to detect pre/post-next() phase.
|
|
153
154
|
// Before next(): responseHolder.response === initialResponse (the stub).
|