@rangojs/router 0.0.0-experimental.10
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/CLAUDE.md +43 -0
- package/README.md +19 -0
- package/dist/bin/rango.js +227 -0
- package/dist/vite/index.js +3039 -0
- package/package.json +171 -0
- package/skills/caching/SKILL.md +191 -0
- package/skills/debug-manifest/SKILL.md +108 -0
- package/skills/document-cache/SKILL.md +180 -0
- package/skills/fonts/SKILL.md +165 -0
- package/skills/hooks/SKILL.md +442 -0
- package/skills/intercept/SKILL.md +190 -0
- package/skills/layout/SKILL.md +213 -0
- package/skills/links/SKILL.md +180 -0
- package/skills/loader/SKILL.md +246 -0
- package/skills/middleware/SKILL.md +202 -0
- package/skills/mime-routes/SKILL.md +124 -0
- package/skills/parallel/SKILL.md +228 -0
- package/skills/prerender/SKILL.md +283 -0
- package/skills/rango/SKILL.md +54 -0
- package/skills/response-routes/SKILL.md +358 -0
- package/skills/route/SKILL.md +173 -0
- package/skills/router-setup/SKILL.md +346 -0
- package/skills/tailwind/SKILL.md +129 -0
- package/skills/theme/SKILL.md +78 -0
- package/skills/typesafety/SKILL.md +394 -0
- package/src/__internal.ts +175 -0
- package/src/bin/rango.ts +24 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +913 -0
- package/src/browser/navigation-client.ts +165 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +600 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +346 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/mount-context.ts +32 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +203 -0
- package/src/browser/react/use-href.tsx +40 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-mount.ts +31 -0
- package/src/browser/react/use-navigation.ts +140 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +352 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/segment-structure-assert.ts +67 -0
- package/src/browser/server-action-bridge.ts +762 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +478 -0
- package/src/build/generate-manifest.ts +377 -0
- package/src/build/generate-route-types.ts +828 -0
- package/src/build/index.ts +36 -0
- package/src/build/route-trie.ts +239 -0
- package/src/cache/cache-scope.ts +563 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +392 -0
- package/src/client.rsc.tsx +83 -0
- package/src/client.tsx +643 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/debug.ts +233 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +295 -0
- package/src/handle.ts +130 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/host/cookie-handler.ts +159 -0
- package/src/host/errors.ts +97 -0
- package/src/host/index.ts +56 -0
- package/src/host/pattern-matcher.ts +214 -0
- package/src/host/router.ts +330 -0
- package/src/host/testing.ts +79 -0
- package/src/host/types.ts +138 -0
- package/src/host/utils.ts +25 -0
- package/src/href-client.ts +202 -0
- package/src/href-context.ts +33 -0
- package/src/index.rsc.ts +121 -0
- package/src/index.ts +165 -0
- package/src/loader.rsc.ts +207 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/prerender/param-hash.ts +35 -0
- package/src/prerender/store.ts +40 -0
- package/src/prerender.ts +156 -0
- package/src/reverse.ts +267 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +193 -0
- package/src/route-definition.ts +1431 -0
- package/src/route-map-builder.ts +242 -0
- package/src/route-types.ts +220 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/intercept-resolution.ts +387 -0
- package/src/router/loader-resolution.ts +327 -0
- package/src/router/manifest.ts +216 -0
- package/src/router/match-api.ts +621 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +382 -0
- package/src/router/match-middleware/cache-store.ts +276 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +281 -0
- package/src/router/match-middleware/segment-resolution.ts +184 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +213 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.ts +791 -0
- package/src/router/pattern-matching.ts +407 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +301 -0
- package/src/router/segment-resolution.ts +1315 -0
- package/src/router/trie-matching.ts +172 -0
- package/src/router/types.ts +163 -0
- package/src/router.gen.ts +6 -0
- package/src/router.ts +2423 -0
- package/src/rsc/handler.ts +1443 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +236 -0
- package/src/segment-system.tsx +442 -0
- package/src/server/context.ts +466 -0
- package/src/server/handle-store.ts +229 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +171 -0
- package/src/ssr/index.tsx +296 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/constants.ts +59 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1757 -0
- package/src/urls.gen.ts +8 -0
- package/src/urls.ts +1282 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +426 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/expose-prerender-handler-id.ts +429 -0
- package/src/vite/index.ts +2068 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +114 -0
|
@@ -0,0 +1,1315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Segment Resolution
|
|
3
|
+
*
|
|
4
|
+
* Extracted from createRouter closure. Contains all segment resolution functions
|
|
5
|
+
* for both fresh (full match) and revalidation (partial match) paths.
|
|
6
|
+
*
|
|
7
|
+
* Functions receive a `deps` parameter for closure-bound helpers from createRouter.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from "react";
|
|
11
|
+
import {
|
|
12
|
+
DataNotFoundError,
|
|
13
|
+
invariant,
|
|
14
|
+
} from "../errors";
|
|
15
|
+
import {
|
|
16
|
+
createErrorInfo,
|
|
17
|
+
createErrorSegment,
|
|
18
|
+
createNotFoundInfo,
|
|
19
|
+
createNotFoundSegment,
|
|
20
|
+
} from "./error-handling.js";
|
|
21
|
+
import {
|
|
22
|
+
revalidate,
|
|
23
|
+
} from "./loader-resolution.js";
|
|
24
|
+
import { evaluateRevalidation } from "./revalidation.js";
|
|
25
|
+
import { getRequestContext } from "../server/request-context.js";
|
|
26
|
+
import { DefaultErrorFallback } from "../default-error-boundary.js";
|
|
27
|
+
import type { EntryData } from "../server/context";
|
|
28
|
+
import type {
|
|
29
|
+
HandlerContext,
|
|
30
|
+
InternalHandlerContext,
|
|
31
|
+
ResolvedSegment,
|
|
32
|
+
ErrorInfo,
|
|
33
|
+
ShouldRevalidateFn,
|
|
34
|
+
} from "../types";
|
|
35
|
+
import type {
|
|
36
|
+
SegmentResolutionDeps,
|
|
37
|
+
SegmentRevalidationResult,
|
|
38
|
+
ActionContext,
|
|
39
|
+
} from "./types.js";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Handle Response returns from handlers.
|
|
43
|
+
* When a handler returns a Response (e.g., redirect), throw it to trigger
|
|
44
|
+
* the short-circuit mechanism. Otherwise return the ReactNode.
|
|
45
|
+
*/
|
|
46
|
+
export function handleHandlerResult(
|
|
47
|
+
result: ReactNode | Response | Promise<ReactNode> | Promise<Response>,
|
|
48
|
+
): ReactNode {
|
|
49
|
+
if (result instanceof Response) {
|
|
50
|
+
throw result;
|
|
51
|
+
}
|
|
52
|
+
if (result instanceof Promise) {
|
|
53
|
+
return result.then((resolved) => {
|
|
54
|
+
if (resolved instanceof Response) {
|
|
55
|
+
throw resolved;
|
|
56
|
+
}
|
|
57
|
+
return resolved;
|
|
58
|
+
}) as ReactNode;
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Fresh path (full match, no revalidation)
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resolve loaders for an entry and emit segments.
|
|
69
|
+
* Loaders are run lazily via ctx.use() and memoized for parallel execution.
|
|
70
|
+
*/
|
|
71
|
+
export async function resolveLoaders<TEnv>(
|
|
72
|
+
entry: EntryData,
|
|
73
|
+
ctx: HandlerContext<any, TEnv>,
|
|
74
|
+
belongsToRoute: boolean,
|
|
75
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
76
|
+
shortCodeOverride?: string,
|
|
77
|
+
): Promise<ResolvedSegment[]> {
|
|
78
|
+
const loaderEntries = entry.loader ?? [];
|
|
79
|
+
if (loaderEntries.length === 0) return [];
|
|
80
|
+
|
|
81
|
+
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
82
|
+
const hasLoading = "loading" in entry && entry.loading !== undefined;
|
|
83
|
+
const loadingDisabled = hasLoading && entry.loading === false;
|
|
84
|
+
|
|
85
|
+
return Promise.all(
|
|
86
|
+
loaderEntries.map(async ({ loader }, i) => {
|
|
87
|
+
const segmentId = `${shortCode}D${i}.${loader.$$id}`;
|
|
88
|
+
return {
|
|
89
|
+
id: segmentId,
|
|
90
|
+
namespace: entry.id,
|
|
91
|
+
type: "loader" as const,
|
|
92
|
+
index: i,
|
|
93
|
+
component: null,
|
|
94
|
+
params: ctx.params,
|
|
95
|
+
loaderId: loader.$$id,
|
|
96
|
+
loaderData: deps.wrapLoaderPromise(
|
|
97
|
+
loadingDisabled ? await ctx.use(loader) : ctx.use(loader),
|
|
98
|
+
entry,
|
|
99
|
+
segmentId,
|
|
100
|
+
ctx.pathname,
|
|
101
|
+
),
|
|
102
|
+
belongsToRoute,
|
|
103
|
+
};
|
|
104
|
+
}),
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve segments from EntryData.
|
|
110
|
+
* Executes middlewares, loaders, parallels, and handlers in correct order.
|
|
111
|
+
* Returns array: [main segment, ...orphan layout segments]
|
|
112
|
+
*/
|
|
113
|
+
export async function resolveSegment<TEnv>(
|
|
114
|
+
entry: EntryData,
|
|
115
|
+
routeKey: string,
|
|
116
|
+
params: Record<string, string>,
|
|
117
|
+
context: HandlerContext<any, TEnv>,
|
|
118
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
119
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
120
|
+
isRouteEntry: boolean = false,
|
|
121
|
+
): Promise<ResolvedSegment[]> {
|
|
122
|
+
const segments: ResolvedSegment[] = [];
|
|
123
|
+
|
|
124
|
+
if (entry.type === "layout" || entry.type === "cache") {
|
|
125
|
+
const loaderSegments = await resolveLoaders(entry, context, false, deps);
|
|
126
|
+
segments.push(...loaderSegments);
|
|
127
|
+
|
|
128
|
+
for (const parallelEntry of entry.parallel) {
|
|
129
|
+
const parallelSegments = await resolveParallelEntry(
|
|
130
|
+
parallelEntry, params, context, false, entry.shortCode, deps,
|
|
131
|
+
);
|
|
132
|
+
segments.push(...parallelSegments);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
(context as InternalHandlerContext)._currentSegmentId = entry.shortCode;
|
|
136
|
+
const component =
|
|
137
|
+
typeof entry.handler === "function"
|
|
138
|
+
? handleHandlerResult(await entry.handler(context))
|
|
139
|
+
: entry.handler;
|
|
140
|
+
|
|
141
|
+
segments.push({
|
|
142
|
+
id: entry.shortCode,
|
|
143
|
+
namespace: entry.id,
|
|
144
|
+
type: "layout",
|
|
145
|
+
index: 0,
|
|
146
|
+
component,
|
|
147
|
+
loading: entry.loading === false ? null : entry.loading,
|
|
148
|
+
params,
|
|
149
|
+
belongsToRoute: false,
|
|
150
|
+
layoutName: entry.id,
|
|
151
|
+
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
for (const orphan of entry.layout) {
|
|
155
|
+
const orphanSegments = await resolveOrphanLayout(
|
|
156
|
+
orphan, params, context, loaderPromises, false, deps,
|
|
157
|
+
);
|
|
158
|
+
segments.push(...orphanSegments);
|
|
159
|
+
}
|
|
160
|
+
} else if (entry.type === "route") {
|
|
161
|
+
const loaderSegments = await resolveLoaders(entry, context, true, deps);
|
|
162
|
+
segments.push(...loaderSegments);
|
|
163
|
+
|
|
164
|
+
for (const orphan of entry.layout) {
|
|
165
|
+
const orphanSegments = await resolveOrphanLayout(
|
|
166
|
+
orphan, params, context, loaderPromises, true, deps,
|
|
167
|
+
);
|
|
168
|
+
segments.push(...orphanSegments);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
for (const parallelEntry of entry.parallel) {
|
|
172
|
+
const parallelSegments = await resolveParallelEntry(
|
|
173
|
+
parallelEntry, params, context, true, entry.shortCode, deps,
|
|
174
|
+
);
|
|
175
|
+
segments.push(...parallelSegments);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
(context as InternalHandlerContext)._currentSegmentId = entry.shortCode;
|
|
179
|
+
let component: ReactNode;
|
|
180
|
+
if (entry.loading) {
|
|
181
|
+
const result = handleHandlerResult(entry.handler(context));
|
|
182
|
+
component = result instanceof Promise ? deps.trackHandler(result) : result;
|
|
183
|
+
} else {
|
|
184
|
+
component = handleHandlerResult(await entry.handler(context));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
segments.push({
|
|
188
|
+
id: entry.shortCode,
|
|
189
|
+
namespace: entry.id,
|
|
190
|
+
type: "route",
|
|
191
|
+
index: 0,
|
|
192
|
+
component,
|
|
193
|
+
loading: entry.loading === false ? null : entry.loading,
|
|
194
|
+
params,
|
|
195
|
+
belongsToRoute: true,
|
|
196
|
+
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
197
|
+
});
|
|
198
|
+
} else {
|
|
199
|
+
throw new Error(`Unknown entry type: ${(entry as any).type}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return segments;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Resolve orphan layout with its middlewares, loaders, and parallels.
|
|
207
|
+
*/
|
|
208
|
+
export async function resolveOrphanLayout<TEnv>(
|
|
209
|
+
orphan: EntryData,
|
|
210
|
+
params: Record<string, string>,
|
|
211
|
+
context: HandlerContext<any, TEnv>,
|
|
212
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
213
|
+
belongsToRoute: boolean,
|
|
214
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
215
|
+
): Promise<ResolvedSegment[]> {
|
|
216
|
+
invariant(
|
|
217
|
+
orphan.type === "layout" || orphan.type === "cache",
|
|
218
|
+
`Expected orphan to be a layout or cache, got: ${orphan.type}`,
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const loaderSegments = await resolveLoaders(orphan, context, belongsToRoute, deps);
|
|
222
|
+
const segments: ResolvedSegment[] = [...loaderSegments];
|
|
223
|
+
|
|
224
|
+
for (const parallelEntry of orphan.parallel) {
|
|
225
|
+
const parallelSegments = await resolveParallelEntry(
|
|
226
|
+
parallelEntry, params, context, belongsToRoute, orphan.shortCode, deps,
|
|
227
|
+
);
|
|
228
|
+
segments.push(...parallelSegments);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const component =
|
|
232
|
+
typeof orphan.handler === "function"
|
|
233
|
+
? handleHandlerResult(await orphan.handler(context))
|
|
234
|
+
: orphan.handler;
|
|
235
|
+
|
|
236
|
+
segments.push({
|
|
237
|
+
id: orphan.shortCode,
|
|
238
|
+
namespace: orphan.id,
|
|
239
|
+
type: "layout",
|
|
240
|
+
index: 0,
|
|
241
|
+
component,
|
|
242
|
+
params,
|
|
243
|
+
belongsToRoute,
|
|
244
|
+
layoutName: orphan.id,
|
|
245
|
+
loading: orphan.loading === false ? null : orphan.loading,
|
|
246
|
+
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
return segments;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Resolve parallel EntryData with its loaders and slot handlers.
|
|
254
|
+
*/
|
|
255
|
+
export async function resolveParallelEntry<TEnv>(
|
|
256
|
+
parallelEntry: EntryData,
|
|
257
|
+
params: Record<string, string>,
|
|
258
|
+
context: HandlerContext<any, TEnv>,
|
|
259
|
+
belongsToRoute: boolean,
|
|
260
|
+
parentShortCode: string,
|
|
261
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
262
|
+
): Promise<ResolvedSegment[]> {
|
|
263
|
+
invariant(
|
|
264
|
+
parallelEntry.type === "parallel",
|
|
265
|
+
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
const segments: ResolvedSegment[] = [];
|
|
269
|
+
|
|
270
|
+
const slots = parallelEntry.handler as Record<
|
|
271
|
+
`@${string}`,
|
|
272
|
+
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
273
|
+
| ReactNode
|
|
274
|
+
>;
|
|
275
|
+
|
|
276
|
+
for (const [slot, handler] of Object.entries(slots)) {
|
|
277
|
+
let component: ReactNode;
|
|
278
|
+
if (parallelEntry.loading) {
|
|
279
|
+
const result =
|
|
280
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
281
|
+
component = result as ReactNode;
|
|
282
|
+
} else {
|
|
283
|
+
component =
|
|
284
|
+
typeof handler === "function" ? await handler(context) : handler;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
segments.push({
|
|
288
|
+
id: `${parentShortCode}.${slot}`,
|
|
289
|
+
namespace: parallelEntry.id,
|
|
290
|
+
type: "parallel",
|
|
291
|
+
index: 0,
|
|
292
|
+
component,
|
|
293
|
+
loading: parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
294
|
+
params,
|
|
295
|
+
slot,
|
|
296
|
+
belongsToRoute,
|
|
297
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
298
|
+
...(parallelEntry.mountPath
|
|
299
|
+
? { mountPath: parallelEntry.mountPath }
|
|
300
|
+
: {}),
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (!parallelEntry.loading) {
|
|
305
|
+
const loaderSegments = await resolveLoaders(
|
|
306
|
+
parallelEntry, context, belongsToRoute, deps, parentShortCode,
|
|
307
|
+
);
|
|
308
|
+
segments.push(...loaderSegments);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return segments;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Wrapper that adds error boundary handling to segment resolution.
|
|
316
|
+
*/
|
|
317
|
+
export async function resolveWithErrorHandling<TEnv>(
|
|
318
|
+
entry: EntryData,
|
|
319
|
+
routeKey: string,
|
|
320
|
+
params: Record<string, string>,
|
|
321
|
+
context: HandlerContext<any, TEnv>,
|
|
322
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
323
|
+
resolveFn: () => Promise<ResolvedSegment[]>,
|
|
324
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
325
|
+
errorContext?: {
|
|
326
|
+
env?: TEnv;
|
|
327
|
+
isPartial?: boolean;
|
|
328
|
+
requestStartTime?: number;
|
|
329
|
+
},
|
|
330
|
+
): Promise<ResolvedSegment[]> {
|
|
331
|
+
try {
|
|
332
|
+
return await resolveFn();
|
|
333
|
+
} catch (error) {
|
|
334
|
+
if (error instanceof Response) {
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (error instanceof DataNotFoundError) {
|
|
339
|
+
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
340
|
+
|
|
341
|
+
if (notFoundFallback) {
|
|
342
|
+
const notFoundInfo = createNotFoundInfo(
|
|
343
|
+
error, entry.shortCode, entry.type, context.pathname,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
deps.callOnError(error, "handler", {
|
|
347
|
+
request: context.request,
|
|
348
|
+
url: context.url,
|
|
349
|
+
routeKey,
|
|
350
|
+
params,
|
|
351
|
+
segmentId: entry.shortCode,
|
|
352
|
+
segmentType: entry.type as any,
|
|
353
|
+
env: errorContext?.env,
|
|
354
|
+
isPartial: errorContext?.isPartial,
|
|
355
|
+
handledByBoundary: true,
|
|
356
|
+
metadata: { notFound: true, message: notFoundInfo.message },
|
|
357
|
+
requestStartTime: errorContext?.requestStartTime,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
console.log(
|
|
361
|
+
`[Router] NotFound caught by notFoundBoundary in ${entry.shortCode}:`,
|
|
362
|
+
notFoundInfo.message,
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
const reqCtx = getRequestContext();
|
|
366
|
+
if (reqCtx) {
|
|
367
|
+
reqCtx.res = new Response(null, {
|
|
368
|
+
status: 404,
|
|
369
|
+
headers: reqCtx.res.headers,
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const notFoundSegment = createNotFoundSegment(
|
|
374
|
+
notFoundInfo, notFoundFallback, entry, params,
|
|
375
|
+
);
|
|
376
|
+
return [notFoundSegment];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const fallback = deps.findNearestErrorBoundary(entry);
|
|
381
|
+
const segmentType: ErrorInfo["segmentType"] = entry.type;
|
|
382
|
+
const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
|
|
383
|
+
const effectiveFallback = fallback ?? DefaultErrorFallback;
|
|
384
|
+
|
|
385
|
+
deps.callOnError(error, "handler", {
|
|
386
|
+
request: context.request,
|
|
387
|
+
url: context.url,
|
|
388
|
+
routeKey,
|
|
389
|
+
params,
|
|
390
|
+
segmentId: entry.shortCode,
|
|
391
|
+
segmentType: entry.type as any,
|
|
392
|
+
env: errorContext?.env,
|
|
393
|
+
isPartial: errorContext?.isPartial,
|
|
394
|
+
handledByBoundary: !!fallback,
|
|
395
|
+
requestStartTime: errorContext?.requestStartTime,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
console.log(
|
|
399
|
+
`[Router] Error caught by ${fallback ? "error boundary" : "default fallback"} in ${entry.shortCode}:`,
|
|
400
|
+
errorInfo.message,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
{
|
|
404
|
+
const reqCtx = getRequestContext();
|
|
405
|
+
if (reqCtx) {
|
|
406
|
+
reqCtx.res = new Response(null, {
|
|
407
|
+
status: 500,
|
|
408
|
+
headers: reqCtx.res.headers,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const errorSegment = createErrorSegment(errorInfo, effectiveFallback, entry, params);
|
|
414
|
+
return [errorSegment];
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Resolve all segments for a route (used for single-cache-per-request pattern).
|
|
420
|
+
*/
|
|
421
|
+
export async function resolveAllSegments<TEnv>(
|
|
422
|
+
entries: EntryData[],
|
|
423
|
+
routeKey: string,
|
|
424
|
+
params: Record<string, string>,
|
|
425
|
+
context: HandlerContext<any, TEnv>,
|
|
426
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
427
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
428
|
+
): Promise<ResolvedSegment[]> {
|
|
429
|
+
const allSegments: ResolvedSegment[] = [];
|
|
430
|
+
|
|
431
|
+
for (const entry of entries) {
|
|
432
|
+
const resolvedSegments = await resolveWithErrorHandling(
|
|
433
|
+
entry, routeKey, params, context, loaderPromises,
|
|
434
|
+
() => resolveSegment(entry, routeKey, params, context, loaderPromises, deps),
|
|
435
|
+
deps,
|
|
436
|
+
);
|
|
437
|
+
allSegments.push(...resolvedSegments);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return allSegments;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Resolve only loader segments for all entries (used when serving cached non-loader segments).
|
|
445
|
+
*/
|
|
446
|
+
export async function resolveLoadersOnly<TEnv>(
|
|
447
|
+
entries: EntryData[],
|
|
448
|
+
context: HandlerContext<any, TEnv>,
|
|
449
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
450
|
+
): Promise<ResolvedSegment[]> {
|
|
451
|
+
const loaderSegments: ResolvedSegment[] = [];
|
|
452
|
+
|
|
453
|
+
for (const entry of entries) {
|
|
454
|
+
const belongsToRoute = entry.type === "route";
|
|
455
|
+
const segments = await resolveLoaders(entry, context, belongsToRoute, deps);
|
|
456
|
+
loaderSegments.push(...segments);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return loaderSegments;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// ---------------------------------------------------------------------------
|
|
463
|
+
// Revalidation path (partial match)
|
|
464
|
+
// ---------------------------------------------------------------------------
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Resolve loaders with revalidation awareness (for partial rendering).
|
|
468
|
+
* Returns both segments to render AND all matched segment IDs.
|
|
469
|
+
*/
|
|
470
|
+
export async function resolveLoadersWithRevalidation<TEnv>(
|
|
471
|
+
entry: EntryData,
|
|
472
|
+
ctx: HandlerContext<any, TEnv>,
|
|
473
|
+
belongsToRoute: boolean,
|
|
474
|
+
clientSegmentIds: Set<string>,
|
|
475
|
+
prevParams: Record<string, string>,
|
|
476
|
+
request: Request,
|
|
477
|
+
prevUrl: URL,
|
|
478
|
+
nextUrl: URL,
|
|
479
|
+
routeKey: string,
|
|
480
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
481
|
+
actionContext?: ActionContext,
|
|
482
|
+
shortCodeOverride?: string,
|
|
483
|
+
stale?: boolean,
|
|
484
|
+
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
485
|
+
const loaderEntries = entry.loader ?? [];
|
|
486
|
+
if (loaderEntries.length === 0) return { segments: [], matchedIds: [] };
|
|
487
|
+
|
|
488
|
+
const shortCode = shortCodeOverride ?? entry.shortCode;
|
|
489
|
+
|
|
490
|
+
const loaderMeta = loaderEntries.map(
|
|
491
|
+
({ loader, revalidate: loaderRevalidateFns }, i) => ({
|
|
492
|
+
loader,
|
|
493
|
+
loaderRevalidateFns,
|
|
494
|
+
segmentId: `${shortCode}D${i}.${loader.$$id}`,
|
|
495
|
+
index: i,
|
|
496
|
+
}),
|
|
497
|
+
);
|
|
498
|
+
|
|
499
|
+
const matchedIds = loaderMeta.map((m) => m.segmentId);
|
|
500
|
+
|
|
501
|
+
const revalidationChecks = await Promise.all(
|
|
502
|
+
loaderMeta.map(
|
|
503
|
+
async ({ loader, loaderRevalidateFns, segmentId, index }) => {
|
|
504
|
+
const shouldRun = await revalidate(
|
|
505
|
+
async () => {
|
|
506
|
+
if (!clientSegmentIds.has(segmentId)) return true;
|
|
507
|
+
|
|
508
|
+
const dummySegment: ResolvedSegment = {
|
|
509
|
+
id: segmentId,
|
|
510
|
+
namespace: entry.id,
|
|
511
|
+
type: "loader",
|
|
512
|
+
index,
|
|
513
|
+
component: null,
|
|
514
|
+
params: ctx.params,
|
|
515
|
+
loaderId: loader.$$id,
|
|
516
|
+
belongsToRoute,
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
return await evaluateRevalidation({
|
|
520
|
+
segment: dummySegment,
|
|
521
|
+
prevParams,
|
|
522
|
+
getPrevSegment: null,
|
|
523
|
+
request,
|
|
524
|
+
prevUrl,
|
|
525
|
+
nextUrl,
|
|
526
|
+
revalidations: loaderRevalidateFns.map((fn, j) => ({
|
|
527
|
+
name: `loader-revalidate${j}`,
|
|
528
|
+
fn,
|
|
529
|
+
})),
|
|
530
|
+
routeKey,
|
|
531
|
+
context: ctx,
|
|
532
|
+
actionContext,
|
|
533
|
+
stale,
|
|
534
|
+
});
|
|
535
|
+
},
|
|
536
|
+
async () => true,
|
|
537
|
+
() => false,
|
|
538
|
+
);
|
|
539
|
+
return { shouldRun, loader, segmentId, index };
|
|
540
|
+
},
|
|
541
|
+
),
|
|
542
|
+
);
|
|
543
|
+
|
|
544
|
+
const loadersToRun = revalidationChecks.filter((c) => c.shouldRun);
|
|
545
|
+
const segments: ResolvedSegment[] = loadersToRun.map(
|
|
546
|
+
({ loader, segmentId, index }) => ({
|
|
547
|
+
id: segmentId,
|
|
548
|
+
namespace: entry.id,
|
|
549
|
+
type: "loader" as const,
|
|
550
|
+
index,
|
|
551
|
+
component: null,
|
|
552
|
+
params: ctx.params,
|
|
553
|
+
loaderId: loader.$$id,
|
|
554
|
+
loaderData: deps.wrapLoaderPromise(
|
|
555
|
+
ctx.use(loader),
|
|
556
|
+
entry,
|
|
557
|
+
segmentId,
|
|
558
|
+
ctx.pathname,
|
|
559
|
+
),
|
|
560
|
+
belongsToRoute,
|
|
561
|
+
}),
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
return { segments, matchedIds };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Resolve only loader segments for all entries with revalidation logic.
|
|
569
|
+
*/
|
|
570
|
+
export async function resolveLoadersOnlyWithRevalidation<TEnv>(
|
|
571
|
+
entries: EntryData[],
|
|
572
|
+
context: HandlerContext<any, TEnv>,
|
|
573
|
+
clientSegmentIds: Set<string>,
|
|
574
|
+
prevParams: Record<string, string>,
|
|
575
|
+
request: Request,
|
|
576
|
+
prevUrl: URL,
|
|
577
|
+
nextUrl: URL,
|
|
578
|
+
routeKey: string,
|
|
579
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
580
|
+
actionContext?: ActionContext,
|
|
581
|
+
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
582
|
+
const allLoaderSegments: ResolvedSegment[] = [];
|
|
583
|
+
const allMatchedIds: string[] = [];
|
|
584
|
+
|
|
585
|
+
for (const entry of entries) {
|
|
586
|
+
const belongsToRoute = entry.type === "route";
|
|
587
|
+
const { segments, matchedIds } = await resolveLoadersWithRevalidation(
|
|
588
|
+
entry, context, belongsToRoute, clientSegmentIds,
|
|
589
|
+
prevParams, request, prevUrl, nextUrl, routeKey, deps, actionContext,
|
|
590
|
+
);
|
|
591
|
+
allLoaderSegments.push(...segments);
|
|
592
|
+
allMatchedIds.push(...matchedIds);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return { segments: allLoaderSegments, matchedIds: allMatchedIds };
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Build a map of segment shortCode -> entry with revalidate functions.
|
|
600
|
+
*/
|
|
601
|
+
export function buildEntryRevalidateMap(
|
|
602
|
+
entries: EntryData[],
|
|
603
|
+
): Map<
|
|
604
|
+
string,
|
|
605
|
+
{ entry: EntryData; revalidate: ShouldRevalidateFn<any, any>[] }
|
|
606
|
+
> {
|
|
607
|
+
const map = new Map<
|
|
608
|
+
string,
|
|
609
|
+
{ entry: EntryData; revalidate: ShouldRevalidateFn<any, any>[] }
|
|
610
|
+
>();
|
|
611
|
+
|
|
612
|
+
function processEntry(entry: EntryData, parentShortCode?: string) {
|
|
613
|
+
map.set(entry.shortCode, { entry, revalidate: entry.revalidate });
|
|
614
|
+
|
|
615
|
+
if (entry.type !== "parallel") {
|
|
616
|
+
for (const parallelEntry of entry.parallel) {
|
|
617
|
+
if (parallelEntry.type === "parallel") {
|
|
618
|
+
const slots = Object.keys(parallelEntry.handler) as `@${string}`[];
|
|
619
|
+
for (const slot of slots) {
|
|
620
|
+
const parallelId = `${parallelEntry.shortCode}.${slot}`;
|
|
621
|
+
map.set(parallelId, {
|
|
622
|
+
entry: parallelEntry,
|
|
623
|
+
revalidate: parallelEntry.revalidate,
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
for (const layoutEntry of entry.layout) {
|
|
631
|
+
processEntry(layoutEntry);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
for (const entry of entries) {
|
|
636
|
+
processEntry(entry);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
return map;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Resolve parallel segments with revalidation.
|
|
644
|
+
*/
|
|
645
|
+
export async function resolveParallelSegmentsWithRevalidation<TEnv>(
|
|
646
|
+
entry: EntryData,
|
|
647
|
+
params: Record<string, string>,
|
|
648
|
+
context: HandlerContext<any, TEnv>,
|
|
649
|
+
belongsToRoute: boolean,
|
|
650
|
+
clientSegmentIds: Set<string>,
|
|
651
|
+
prevParams: Record<string, string>,
|
|
652
|
+
request: Request,
|
|
653
|
+
prevUrl: URL,
|
|
654
|
+
nextUrl: URL,
|
|
655
|
+
routeKey: string,
|
|
656
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
657
|
+
actionContext?: ActionContext,
|
|
658
|
+
stale?: boolean,
|
|
659
|
+
): Promise<SegmentRevalidationResult> {
|
|
660
|
+
const segments: ResolvedSegment[] = [];
|
|
661
|
+
const matchedIds: string[] = [];
|
|
662
|
+
|
|
663
|
+
for (const parallelEntry of entry.parallel) {
|
|
664
|
+
invariant(
|
|
665
|
+
parallelEntry.type === "parallel",
|
|
666
|
+
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
const slots = parallelEntry.handler as Record<
|
|
670
|
+
`@${string}`,
|
|
671
|
+
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
672
|
+
| ReactNode
|
|
673
|
+
>;
|
|
674
|
+
|
|
675
|
+
for (const [slot, handler] of Object.entries(slots)) {
|
|
676
|
+
const parallelId = `${entry.shortCode}.${slot}`;
|
|
677
|
+
|
|
678
|
+
const isFullRefetch = clientSegmentIds.size === 0;
|
|
679
|
+
if (
|
|
680
|
+
isFullRefetch ||
|
|
681
|
+
clientSegmentIds.has(parallelId) ||
|
|
682
|
+
belongsToRoute
|
|
683
|
+
) {
|
|
684
|
+
matchedIds.push(parallelId);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const component = await revalidate(
|
|
688
|
+
async () => {
|
|
689
|
+
if (isFullRefetch) return true;
|
|
690
|
+
if (!clientSegmentIds.has(parallelId)) return belongsToRoute;
|
|
691
|
+
|
|
692
|
+
const dummySegment: ResolvedSegment = {
|
|
693
|
+
id: parallelId,
|
|
694
|
+
namespace: parallelEntry.id,
|
|
695
|
+
type: "parallel",
|
|
696
|
+
index: 0,
|
|
697
|
+
component: null as any,
|
|
698
|
+
params,
|
|
699
|
+
slot,
|
|
700
|
+
belongsToRoute,
|
|
701
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
702
|
+
...(parallelEntry.mountPath
|
|
703
|
+
? { mountPath: parallelEntry.mountPath }
|
|
704
|
+
: {}),
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
return await evaluateRevalidation({
|
|
708
|
+
segment: dummySegment,
|
|
709
|
+
prevParams,
|
|
710
|
+
getPrevSegment: null,
|
|
711
|
+
request,
|
|
712
|
+
prevUrl,
|
|
713
|
+
nextUrl,
|
|
714
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
715
|
+
name: `revalidate${i}`,
|
|
716
|
+
fn,
|
|
717
|
+
})),
|
|
718
|
+
routeKey,
|
|
719
|
+
context,
|
|
720
|
+
actionContext,
|
|
721
|
+
stale,
|
|
722
|
+
});
|
|
723
|
+
},
|
|
724
|
+
async () => {
|
|
725
|
+
if (parallelEntry.loading) {
|
|
726
|
+
const result =
|
|
727
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
728
|
+
return result;
|
|
729
|
+
}
|
|
730
|
+
return typeof handler === "function"
|
|
731
|
+
? await handler(context)
|
|
732
|
+
: handler;
|
|
733
|
+
},
|
|
734
|
+
() => null,
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
segments.push({
|
|
738
|
+
id: parallelId,
|
|
739
|
+
namespace: parallelEntry.id,
|
|
740
|
+
type: "parallel",
|
|
741
|
+
index: 0,
|
|
742
|
+
component,
|
|
743
|
+
loading:
|
|
744
|
+
parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
745
|
+
params,
|
|
746
|
+
slot,
|
|
747
|
+
belongsToRoute,
|
|
748
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
749
|
+
...(parallelEntry.mountPath
|
|
750
|
+
? { mountPath: parallelEntry.mountPath }
|
|
751
|
+
: {}),
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (!parallelEntry.loading) {
|
|
756
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
757
|
+
parallelEntry, context, belongsToRoute, clientSegmentIds,
|
|
758
|
+
prevParams, request, prevUrl, nextUrl, routeKey, deps,
|
|
759
|
+
actionContext, entry.shortCode, stale,
|
|
760
|
+
);
|
|
761
|
+
segments.push(...loaderResult.segments);
|
|
762
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return { segments, matchedIds };
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Resolve entry handler (layout, cache, or route) with revalidation.
|
|
771
|
+
*/
|
|
772
|
+
export async function resolveEntryHandlerWithRevalidation<TEnv>(
|
|
773
|
+
entry: Exclude<EntryData, { type: "parallel" }>,
|
|
774
|
+
params: Record<string, string>,
|
|
775
|
+
context: HandlerContext<any, TEnv>,
|
|
776
|
+
belongsToRoute: boolean,
|
|
777
|
+
clientSegmentIds: Set<string>,
|
|
778
|
+
prevParams: Record<string, string>,
|
|
779
|
+
request: Request,
|
|
780
|
+
prevUrl: URL,
|
|
781
|
+
nextUrl: URL,
|
|
782
|
+
routeKey: string,
|
|
783
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
784
|
+
actionContext?: ActionContext,
|
|
785
|
+
stale?: boolean,
|
|
786
|
+
): Promise<{ segment: ResolvedSegment; matchedId: string }> {
|
|
787
|
+
const matchedId = entry.shortCode;
|
|
788
|
+
|
|
789
|
+
const component = await revalidate(
|
|
790
|
+
async () => {
|
|
791
|
+
const hasSegment = clientSegmentIds.has(entry.shortCode);
|
|
792
|
+
console.log(
|
|
793
|
+
`[Router.resolveEntryHandler] ${entry.shortCode} (${entry.type}): client has=${hasSegment}, belongsToRoute=${belongsToRoute}`,
|
|
794
|
+
);
|
|
795
|
+
if (!hasSegment) return true;
|
|
796
|
+
|
|
797
|
+
const dummySegment: ResolvedSegment = {
|
|
798
|
+
id: entry.shortCode,
|
|
799
|
+
namespace: entry.id,
|
|
800
|
+
type:
|
|
801
|
+
entry.type === "cache"
|
|
802
|
+
? "layout"
|
|
803
|
+
: (entry.type as "layout" | "route"),
|
|
804
|
+
index: 0,
|
|
805
|
+
component: null as any,
|
|
806
|
+
params,
|
|
807
|
+
belongsToRoute,
|
|
808
|
+
...(entry.type === "layout" || entry.type === "cache"
|
|
809
|
+
? { layoutName: entry.id }
|
|
810
|
+
: {}),
|
|
811
|
+
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
const shouldRevalidate = await evaluateRevalidation({
|
|
815
|
+
segment: dummySegment,
|
|
816
|
+
prevParams,
|
|
817
|
+
getPrevSegment: null,
|
|
818
|
+
request,
|
|
819
|
+
prevUrl,
|
|
820
|
+
nextUrl,
|
|
821
|
+
revalidations: entry.revalidate.map((fn, i) => ({
|
|
822
|
+
name: `revalidate${i}`,
|
|
823
|
+
fn,
|
|
824
|
+
})),
|
|
825
|
+
routeKey,
|
|
826
|
+
context,
|
|
827
|
+
actionContext,
|
|
828
|
+
stale,
|
|
829
|
+
});
|
|
830
|
+
console.log(
|
|
831
|
+
`[Router.resolveEntryHandler] ${entry.shortCode}: evaluateRevalidation returned ${shouldRevalidate}`,
|
|
832
|
+
);
|
|
833
|
+
return shouldRevalidate;
|
|
834
|
+
},
|
|
835
|
+
async () => {
|
|
836
|
+
(context as InternalHandlerContext)._currentSegmentId = entry.shortCode;
|
|
837
|
+
if (entry.type === "layout" || entry.type === "cache") {
|
|
838
|
+
return typeof entry.handler === "function"
|
|
839
|
+
? handleHandlerResult(await entry.handler(context))
|
|
840
|
+
: entry.handler;
|
|
841
|
+
}
|
|
842
|
+
const routeEntry = entry as Extract<EntryData, { type: "route" }>;
|
|
843
|
+
if (!routeEntry.loading) {
|
|
844
|
+
return handleHandlerResult(await routeEntry.handler(context));
|
|
845
|
+
}
|
|
846
|
+
if (!actionContext) {
|
|
847
|
+
const result = handleHandlerResult(routeEntry.handler(context));
|
|
848
|
+
return {
|
|
849
|
+
content: result instanceof Promise ? deps.trackHandler(result) : result,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
console.log(
|
|
853
|
+
`[Router] Resolving action route with awaited value: ${entry.id}`,
|
|
854
|
+
);
|
|
855
|
+
return {
|
|
856
|
+
content: Promise.resolve(
|
|
857
|
+
handleHandlerResult(await routeEntry.handler(context)),
|
|
858
|
+
),
|
|
859
|
+
};
|
|
860
|
+
},
|
|
861
|
+
() => null,
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
const resolvedComponent =
|
|
865
|
+
component && typeof component === "object" && "content" in component
|
|
866
|
+
? (component as { content: ReactNode }).content
|
|
867
|
+
: component;
|
|
868
|
+
|
|
869
|
+
const segment: ResolvedSegment = {
|
|
870
|
+
id: entry.shortCode,
|
|
871
|
+
namespace: entry.id,
|
|
872
|
+
type:
|
|
873
|
+
entry.type === "cache" ? "layout" : (entry.type as "layout" | "route"),
|
|
874
|
+
index: 0,
|
|
875
|
+
component: resolvedComponent,
|
|
876
|
+
loading: entry.loading === false ? null : entry.loading,
|
|
877
|
+
params,
|
|
878
|
+
belongsToRoute,
|
|
879
|
+
...(entry.type === "layout" || entry.type === "cache"
|
|
880
|
+
? { layoutName: entry.id }
|
|
881
|
+
: {}),
|
|
882
|
+
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
return { segment, matchedId };
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Resolve segments with revalidation awareness (for partial rendering).
|
|
890
|
+
*/
|
|
891
|
+
export async function resolveSegmentWithRevalidation<TEnv>(
|
|
892
|
+
entry: Exclude<EntryData, { type: "parallel" }>,
|
|
893
|
+
routeKey: string,
|
|
894
|
+
params: Record<string, string>,
|
|
895
|
+
context: HandlerContext<any, TEnv>,
|
|
896
|
+
clientSegmentIds: Set<string>,
|
|
897
|
+
prevParams: Record<string, string>,
|
|
898
|
+
request: Request,
|
|
899
|
+
prevUrl: URL,
|
|
900
|
+
nextUrl: URL,
|
|
901
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
902
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
903
|
+
actionContext?: ActionContext,
|
|
904
|
+
stale?: boolean,
|
|
905
|
+
): Promise<SegmentRevalidationResult> {
|
|
906
|
+
const segments: ResolvedSegment[] = [];
|
|
907
|
+
const matchedIds: string[] = [];
|
|
908
|
+
|
|
909
|
+
const belongsToRoute = entry.type === "route";
|
|
910
|
+
|
|
911
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
912
|
+
entry, context, belongsToRoute, clientSegmentIds,
|
|
913
|
+
prevParams, request, prevUrl, nextUrl, routeKey, deps,
|
|
914
|
+
actionContext, undefined, stale,
|
|
915
|
+
);
|
|
916
|
+
segments.push(...loaderResult.segments);
|
|
917
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
918
|
+
|
|
919
|
+
if (entry.type === "route") {
|
|
920
|
+
for (const orphan of entry.layout) {
|
|
921
|
+
const orphanResult = await resolveOrphanLayoutWithRevalidation(
|
|
922
|
+
orphan, params, context, clientSegmentIds,
|
|
923
|
+
prevParams, request, prevUrl, nextUrl, routeKey,
|
|
924
|
+
loaderPromises, true, deps, actionContext, stale,
|
|
925
|
+
);
|
|
926
|
+
segments.push(...orphanResult.segments);
|
|
927
|
+
matchedIds.push(...orphanResult.matchedIds);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
const parallelResult = await resolveParallelSegmentsWithRevalidation(
|
|
932
|
+
entry, params, context, belongsToRoute, clientSegmentIds,
|
|
933
|
+
prevParams, request, prevUrl, nextUrl, routeKey, deps,
|
|
934
|
+
actionContext, stale,
|
|
935
|
+
);
|
|
936
|
+
segments.push(...parallelResult.segments);
|
|
937
|
+
matchedIds.push(...parallelResult.matchedIds);
|
|
938
|
+
|
|
939
|
+
if (entry.type === "layout" || entry.type === "cache") {
|
|
940
|
+
for (const orphan of entry.layout) {
|
|
941
|
+
const orphanResult = await resolveOrphanLayoutWithRevalidation(
|
|
942
|
+
orphan, params, context, clientSegmentIds,
|
|
943
|
+
prevParams, request, prevUrl, nextUrl, routeKey,
|
|
944
|
+
loaderPromises, false, deps, actionContext, stale,
|
|
945
|
+
);
|
|
946
|
+
segments.push(...orphanResult.segments);
|
|
947
|
+
matchedIds.push(...orphanResult.matchedIds);
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const handlerResult = await resolveEntryHandlerWithRevalidation(
|
|
952
|
+
entry, params, context, belongsToRoute, clientSegmentIds,
|
|
953
|
+
prevParams, request, prevUrl, nextUrl, routeKey, deps,
|
|
954
|
+
actionContext, stale,
|
|
955
|
+
);
|
|
956
|
+
segments.push(handlerResult.segment);
|
|
957
|
+
matchedIds.push(handlerResult.matchedId);
|
|
958
|
+
|
|
959
|
+
return { segments, matchedIds };
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Resolve orphan layout with revalidation.
|
|
964
|
+
*/
|
|
965
|
+
export async function resolveOrphanLayoutWithRevalidation<TEnv>(
|
|
966
|
+
orphan: EntryData,
|
|
967
|
+
params: Record<string, string>,
|
|
968
|
+
context: HandlerContext<any, TEnv>,
|
|
969
|
+
clientSegmentIds: Set<string>,
|
|
970
|
+
prevParams: Record<string, string>,
|
|
971
|
+
request: Request,
|
|
972
|
+
prevUrl: URL,
|
|
973
|
+
nextUrl: URL,
|
|
974
|
+
routeKey: string,
|
|
975
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
976
|
+
belongsToRoute: boolean,
|
|
977
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
978
|
+
actionContext?: ActionContext,
|
|
979
|
+
stale?: boolean,
|
|
980
|
+
): Promise<SegmentRevalidationResult> {
|
|
981
|
+
invariant(
|
|
982
|
+
orphan.type === "layout" || orphan.type === "cache",
|
|
983
|
+
`Expected orphan to be a layout or cache, got: ${orphan.type}`,
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
const segments: ResolvedSegment[] = [];
|
|
987
|
+
const matchedIds: string[] = [];
|
|
988
|
+
|
|
989
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
990
|
+
orphan, context, belongsToRoute, clientSegmentIds,
|
|
991
|
+
prevParams, request, prevUrl, nextUrl, routeKey, deps,
|
|
992
|
+
actionContext, undefined, stale,
|
|
993
|
+
);
|
|
994
|
+
segments.push(...loaderResult.segments);
|
|
995
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
996
|
+
|
|
997
|
+
for (const parallelEntry of orphan.parallel) {
|
|
998
|
+
invariant(
|
|
999
|
+
parallelEntry.type === "parallel",
|
|
1000
|
+
`Expected parallel entry, got: ${parallelEntry.type}`,
|
|
1001
|
+
);
|
|
1002
|
+
|
|
1003
|
+
const loaderResult = await resolveLoadersWithRevalidation(
|
|
1004
|
+
parallelEntry, context, belongsToRoute, clientSegmentIds,
|
|
1005
|
+
prevParams, request, prevUrl, nextUrl, routeKey, deps,
|
|
1006
|
+
actionContext, undefined, stale,
|
|
1007
|
+
);
|
|
1008
|
+
segments.push(...loaderResult.segments);
|
|
1009
|
+
matchedIds.push(...loaderResult.matchedIds);
|
|
1010
|
+
|
|
1011
|
+
const slots = parallelEntry.handler as Record<
|
|
1012
|
+
`@${string}`,
|
|
1013
|
+
| ((ctx: HandlerContext<any, TEnv>) => ReactNode | Promise<ReactNode>)
|
|
1014
|
+
| ReactNode
|
|
1015
|
+
>;
|
|
1016
|
+
|
|
1017
|
+
for (const [slot, handler] of Object.entries(slots)) {
|
|
1018
|
+
const parallelId = `${parallelEntry.shortCode}.${slot}`;
|
|
1019
|
+
matchedIds.push(parallelId);
|
|
1020
|
+
|
|
1021
|
+
const component = await revalidate(
|
|
1022
|
+
async () => {
|
|
1023
|
+
if (!clientSegmentIds.has(parallelId)) return true;
|
|
1024
|
+
|
|
1025
|
+
const dummySegment: ResolvedSegment = {
|
|
1026
|
+
id: parallelId,
|
|
1027
|
+
namespace: parallelEntry.id,
|
|
1028
|
+
type: "parallel",
|
|
1029
|
+
index: 0,
|
|
1030
|
+
component: null as any,
|
|
1031
|
+
params,
|
|
1032
|
+
slot,
|
|
1033
|
+
belongsToRoute,
|
|
1034
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1035
|
+
...(parallelEntry.mountPath
|
|
1036
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1037
|
+
: {}),
|
|
1038
|
+
};
|
|
1039
|
+
|
|
1040
|
+
return await evaluateRevalidation({
|
|
1041
|
+
segment: dummySegment,
|
|
1042
|
+
prevParams,
|
|
1043
|
+
getPrevSegment: null,
|
|
1044
|
+
request,
|
|
1045
|
+
prevUrl,
|
|
1046
|
+
nextUrl,
|
|
1047
|
+
revalidations: parallelEntry.revalidate.map((fn, i) => ({
|
|
1048
|
+
name: `revalidate${i}`,
|
|
1049
|
+
fn,
|
|
1050
|
+
})),
|
|
1051
|
+
routeKey,
|
|
1052
|
+
context,
|
|
1053
|
+
actionContext,
|
|
1054
|
+
stale,
|
|
1055
|
+
});
|
|
1056
|
+
},
|
|
1057
|
+
async () => {
|
|
1058
|
+
if (parallelEntry.loading) {
|
|
1059
|
+
const result =
|
|
1060
|
+
typeof handler === "function" ? handler(context) : handler;
|
|
1061
|
+
return result;
|
|
1062
|
+
}
|
|
1063
|
+
return typeof handler === "function"
|
|
1064
|
+
? await handler(context)
|
|
1065
|
+
: handler;
|
|
1066
|
+
},
|
|
1067
|
+
() => null,
|
|
1068
|
+
);
|
|
1069
|
+
|
|
1070
|
+
segments.push({
|
|
1071
|
+
id: parallelId,
|
|
1072
|
+
namespace: parallelEntry.id,
|
|
1073
|
+
type: "parallel",
|
|
1074
|
+
index: 0,
|
|
1075
|
+
component,
|
|
1076
|
+
loading:
|
|
1077
|
+
parallelEntry.loading === false ? null : parallelEntry.loading,
|
|
1078
|
+
params,
|
|
1079
|
+
slot,
|
|
1080
|
+
belongsToRoute,
|
|
1081
|
+
parallelName: `${parallelEntry.id}.${slot}`,
|
|
1082
|
+
...(parallelEntry.mountPath
|
|
1083
|
+
? { mountPath: parallelEntry.mountPath }
|
|
1084
|
+
: {}),
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
matchedIds.push(orphan.shortCode);
|
|
1090
|
+
|
|
1091
|
+
const component = await revalidate(
|
|
1092
|
+
async () => {
|
|
1093
|
+
if (!clientSegmentIds.has(orphan.shortCode)) return true;
|
|
1094
|
+
|
|
1095
|
+
const dummySegment: ResolvedSegment = {
|
|
1096
|
+
id: orphan.shortCode,
|
|
1097
|
+
namespace: orphan.id,
|
|
1098
|
+
type: "layout",
|
|
1099
|
+
index: 0,
|
|
1100
|
+
component: null as any,
|
|
1101
|
+
params,
|
|
1102
|
+
belongsToRoute,
|
|
1103
|
+
layoutName: orphan.id,
|
|
1104
|
+
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1105
|
+
};
|
|
1106
|
+
|
|
1107
|
+
return await evaluateRevalidation({
|
|
1108
|
+
segment: dummySegment,
|
|
1109
|
+
prevParams,
|
|
1110
|
+
getPrevSegment: null,
|
|
1111
|
+
request,
|
|
1112
|
+
prevUrl,
|
|
1113
|
+
nextUrl,
|
|
1114
|
+
revalidations: orphan.revalidate.map((fn, i) => ({
|
|
1115
|
+
name: `revalidate${i}`,
|
|
1116
|
+
fn,
|
|
1117
|
+
})),
|
|
1118
|
+
routeKey,
|
|
1119
|
+
context,
|
|
1120
|
+
actionContext,
|
|
1121
|
+
stale,
|
|
1122
|
+
});
|
|
1123
|
+
},
|
|
1124
|
+
async () =>
|
|
1125
|
+
typeof orphan.handler === "function"
|
|
1126
|
+
? handleHandlerResult(await orphan.handler(context))
|
|
1127
|
+
: orphan.handler,
|
|
1128
|
+
() => null,
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
segments.push({
|
|
1132
|
+
id: orphan.shortCode,
|
|
1133
|
+
namespace: orphan.id,
|
|
1134
|
+
type: "layout",
|
|
1135
|
+
index: 0,
|
|
1136
|
+
component,
|
|
1137
|
+
params,
|
|
1138
|
+
belongsToRoute,
|
|
1139
|
+
layoutName: orphan.id,
|
|
1140
|
+
loading: orphan.loading === false ? null : orphan.loading,
|
|
1141
|
+
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
1142
|
+
});
|
|
1143
|
+
|
|
1144
|
+
return { segments, matchedIds };
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Wrapper for segment resolution with revalidation that adds error boundary handling.
|
|
1149
|
+
*/
|
|
1150
|
+
export async function resolveWithRevalidationErrorHandling<TEnv>(
|
|
1151
|
+
entry: EntryData,
|
|
1152
|
+
params: Record<string, string>,
|
|
1153
|
+
resolveFn: () => Promise<SegmentRevalidationResult>,
|
|
1154
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
1155
|
+
pathname?: string,
|
|
1156
|
+
errorContext?: {
|
|
1157
|
+
request: Request;
|
|
1158
|
+
url: URL;
|
|
1159
|
+
routeKey?: string;
|
|
1160
|
+
env?: TEnv;
|
|
1161
|
+
isPartial?: boolean;
|
|
1162
|
+
requestStartTime?: number;
|
|
1163
|
+
},
|
|
1164
|
+
): Promise<SegmentRevalidationResult> {
|
|
1165
|
+
try {
|
|
1166
|
+
return await resolveFn();
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
if (error instanceof Response) {
|
|
1169
|
+
throw error;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
if (error instanceof DataNotFoundError) {
|
|
1173
|
+
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
1174
|
+
|
|
1175
|
+
if (notFoundFallback) {
|
|
1176
|
+
const notFoundInfo = createNotFoundInfo(
|
|
1177
|
+
error, entry.shortCode, entry.type, pathname,
|
|
1178
|
+
);
|
|
1179
|
+
|
|
1180
|
+
if (errorContext) {
|
|
1181
|
+
deps.callOnError(error, "handler", {
|
|
1182
|
+
request: errorContext.request,
|
|
1183
|
+
url: errorContext.url,
|
|
1184
|
+
routeKey: errorContext.routeKey,
|
|
1185
|
+
params,
|
|
1186
|
+
segmentId: entry.shortCode,
|
|
1187
|
+
segmentType: entry.type as any,
|
|
1188
|
+
env: errorContext.env,
|
|
1189
|
+
isPartial: errorContext.isPartial,
|
|
1190
|
+
handledByBoundary: true,
|
|
1191
|
+
metadata: { notFound: true, message: notFoundInfo.message },
|
|
1192
|
+
requestStartTime: errorContext.requestStartTime,
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
console.log(
|
|
1197
|
+
`[Router] NotFound caught by notFoundBoundary in ${entry.shortCode}:`,
|
|
1198
|
+
notFoundInfo.message,
|
|
1199
|
+
);
|
|
1200
|
+
|
|
1201
|
+
const reqCtx = getRequestContext();
|
|
1202
|
+
if (reqCtx) {
|
|
1203
|
+
reqCtx.res = new Response(null, {
|
|
1204
|
+
status: 404,
|
|
1205
|
+
headers: reqCtx.res.headers,
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const notFoundSegment = createNotFoundSegment(
|
|
1210
|
+
notFoundInfo, notFoundFallback, entry, params,
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
return {
|
|
1214
|
+
segments: [notFoundSegment],
|
|
1215
|
+
matchedIds: [notFoundSegment.id],
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
const fallback = deps.findNearestErrorBoundary(entry);
|
|
1221
|
+
const segmentType: ErrorInfo["segmentType"] = entry.type;
|
|
1222
|
+
const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
|
|
1223
|
+
const effectiveFallback = fallback ?? DefaultErrorFallback;
|
|
1224
|
+
|
|
1225
|
+
if (errorContext) {
|
|
1226
|
+
deps.callOnError(error, "handler", {
|
|
1227
|
+
request: errorContext.request,
|
|
1228
|
+
url: errorContext.url,
|
|
1229
|
+
routeKey: errorContext.routeKey,
|
|
1230
|
+
params,
|
|
1231
|
+
segmentId: entry.shortCode,
|
|
1232
|
+
segmentType: entry.type as any,
|
|
1233
|
+
env: errorContext.env,
|
|
1234
|
+
isPartial: errorContext.isPartial,
|
|
1235
|
+
handledByBoundary: !!fallback,
|
|
1236
|
+
requestStartTime: errorContext.requestStartTime,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
console.log(
|
|
1241
|
+
`[Router] Error caught by ${fallback ? "error boundary" : "default fallback"} in ${entry.shortCode}:`,
|
|
1242
|
+
errorInfo.message,
|
|
1243
|
+
);
|
|
1244
|
+
|
|
1245
|
+
{
|
|
1246
|
+
const reqCtx = getRequestContext();
|
|
1247
|
+
if (reqCtx) {
|
|
1248
|
+
reqCtx.res = new Response(null, {
|
|
1249
|
+
status: 500,
|
|
1250
|
+
headers: reqCtx.res.headers,
|
|
1251
|
+
});
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const errorSegment = createErrorSegment(errorInfo, effectiveFallback, entry, params);
|
|
1256
|
+
|
|
1257
|
+
return {
|
|
1258
|
+
segments: [errorSegment],
|
|
1259
|
+
matchedIds: [errorSegment.id],
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Resolve all segments for a route with revalidation logic (for matchPartial).
|
|
1266
|
+
*/
|
|
1267
|
+
export async function resolveAllSegmentsWithRevalidation<TEnv>(
|
|
1268
|
+
entries: EntryData[],
|
|
1269
|
+
routeKey: string,
|
|
1270
|
+
params: Record<string, string>,
|
|
1271
|
+
context: HandlerContext<any, TEnv>,
|
|
1272
|
+
clientSegmentSet: Set<string>,
|
|
1273
|
+
prevParams: Record<string, string>,
|
|
1274
|
+
request: Request,
|
|
1275
|
+
prevUrl: URL,
|
|
1276
|
+
nextUrl: URL,
|
|
1277
|
+
loaderPromises: Map<string, Promise<any>>,
|
|
1278
|
+
actionContext: ActionContext | undefined,
|
|
1279
|
+
interceptResult: { intercept: any; entry: EntryData } | null,
|
|
1280
|
+
localRouteName: string,
|
|
1281
|
+
pathname: string,
|
|
1282
|
+
deps: SegmentResolutionDeps<TEnv>,
|
|
1283
|
+
): Promise<{ segments: ResolvedSegment[]; matchedIds: string[] }> {
|
|
1284
|
+
const allSegments: ResolvedSegment[] = [];
|
|
1285
|
+
const matchedIds: string[] = [];
|
|
1286
|
+
|
|
1287
|
+
for (const entry of entries) {
|
|
1288
|
+
if (entry.type === "route" && interceptResult) {
|
|
1289
|
+
console.log(
|
|
1290
|
+
`[Router.matchPartial] Intercepting "${localRouteName}" - skipping route handler`,
|
|
1291
|
+
);
|
|
1292
|
+
matchedIds.push(entry.shortCode);
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const nonParallelEntry = entry as Exclude<EntryData, { type: "parallel" }>;
|
|
1297
|
+
const resolved = await resolveWithRevalidationErrorHandling(
|
|
1298
|
+
nonParallelEntry,
|
|
1299
|
+
params,
|
|
1300
|
+
() =>
|
|
1301
|
+
resolveSegmentWithRevalidation(
|
|
1302
|
+
nonParallelEntry, routeKey, params, context, clientSegmentSet,
|
|
1303
|
+
prevParams, request, prevUrl, nextUrl, loaderPromises, deps,
|
|
1304
|
+
actionContext, false,
|
|
1305
|
+
),
|
|
1306
|
+
deps,
|
|
1307
|
+
pathname,
|
|
1308
|
+
);
|
|
1309
|
+
|
|
1310
|
+
allSegments.push(...resolved.segments);
|
|
1311
|
+
matchedIds.push(...resolved.matchedIds);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
return { segments: allSegments, matchedIds };
|
|
1315
|
+
}
|