@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
|
@@ -6,47 +6,67 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { ReactNode } from "react";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
createErrorInfo,
|
|
12
|
-
createErrorSegment,
|
|
13
|
-
createNotFoundInfo,
|
|
14
|
-
createNotFoundSegment,
|
|
15
|
-
} from "../error-handling.js";
|
|
16
|
-
import { getRequestContext } from "../../server/request-context.js";
|
|
17
|
-
import { DefaultErrorFallback } from "../../default-error-boundary.js";
|
|
9
|
+
import { invariant } from "../../errors";
|
|
18
10
|
import type { EntryData } from "../../server/context";
|
|
19
11
|
import type {
|
|
20
12
|
HandlerContext,
|
|
21
13
|
InternalHandlerContext,
|
|
22
14
|
ResolvedSegment,
|
|
23
|
-
ErrorInfo,
|
|
24
15
|
} from "../../types";
|
|
25
16
|
import type { SegmentResolutionDeps } from "../types.js";
|
|
26
|
-
import { debugLog } from "../logging.js";
|
|
27
|
-
import { tryStaticLookup } from "./static-store.js";
|
|
28
17
|
import { resolveLoaderData } from "./loader-cache.js";
|
|
18
|
+
import {
|
|
19
|
+
handleHandlerResult,
|
|
20
|
+
tryStaticHandler,
|
|
21
|
+
tryStaticSlot,
|
|
22
|
+
resolveLayoutComponent,
|
|
23
|
+
resolveWithErrorBoundary,
|
|
24
|
+
} from "./helpers.js";
|
|
25
|
+
import { getRouterContext } from "../router-context.js";
|
|
26
|
+
import { resolveSink, safeEmit } from "../telemetry.js";
|
|
27
|
+
import { track } from "../../server/context.js";
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Streamed handler telemetry
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
29
32
|
|
|
30
33
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* the
|
|
34
|
+
* Attach a fire-and-forget rejection observer to a streamed handler promise.
|
|
35
|
+
* React catches the actual error via its error boundary; this only emits
|
|
36
|
+
* the handler.error telemetry event.
|
|
34
37
|
*/
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
function observeStreamedHandler(
|
|
39
|
+
promise: Promise<ReactNode>,
|
|
40
|
+
segmentId: string,
|
|
41
|
+
segmentType: string,
|
|
42
|
+
pathname?: string,
|
|
43
|
+
routeKey?: string,
|
|
44
|
+
params?: Record<string, string>,
|
|
45
|
+
): void {
|
|
46
|
+
let routerCtx;
|
|
47
|
+
try {
|
|
48
|
+
routerCtx = getRouterContext();
|
|
49
|
+
} catch {
|
|
50
|
+
return;
|
|
48
51
|
}
|
|
49
|
-
return
|
|
52
|
+
if (!routerCtx?.telemetry) return;
|
|
53
|
+
const sink = resolveSink(routerCtx.telemetry);
|
|
54
|
+
const reqId = routerCtx.requestId;
|
|
55
|
+
promise.catch((err: unknown) => {
|
|
56
|
+
const errorObj = err instanceof Error ? err : new Error(String(err));
|
|
57
|
+
safeEmit(sink, {
|
|
58
|
+
type: "handler.error",
|
|
59
|
+
timestamp: performance.now(),
|
|
60
|
+
requestId: reqId,
|
|
61
|
+
segmentId,
|
|
62
|
+
segmentType,
|
|
63
|
+
error: errorObj,
|
|
64
|
+
handledByBoundary: true,
|
|
65
|
+
pathname,
|
|
66
|
+
routeKey,
|
|
67
|
+
params,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
// ---------------------------------------------------------------------------
|
|
@@ -154,38 +174,14 @@ export async function resolveSegment<TEnv>(
|
|
|
154
174
|
segments.push(...loaderSegments);
|
|
155
175
|
}
|
|
156
176
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
parallelEntry,
|
|
160
|
-
params,
|
|
161
|
-
context,
|
|
162
|
-
false,
|
|
163
|
-
entry.shortCode,
|
|
164
|
-
deps,
|
|
165
|
-
options,
|
|
166
|
-
);
|
|
167
|
-
segments.push(...parallelSegments);
|
|
168
|
-
}
|
|
169
|
-
|
|
177
|
+
// Handler-first: layout handler executes before its parallels and orphan
|
|
178
|
+
// layouts so that ctx.set() values are visible to all children.
|
|
170
179
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
171
180
|
entry.shortCode;
|
|
172
181
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
let component: ReactNode | undefined;
|
|
177
|
-
if (entryAny.isStaticPrerender && entryAny.staticHandlerId) {
|
|
178
|
-
component = await tryStaticLookup(
|
|
179
|
-
entryAny.staticHandlerId,
|
|
180
|
-
entry.shortCode,
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
if (component === undefined) {
|
|
184
|
-
component =
|
|
185
|
-
typeof entry.handler === "function"
|
|
186
|
-
? handleHandlerResult(await entry.handler(context))
|
|
187
|
-
: entry.handler;
|
|
188
|
-
}
|
|
182
|
+
const doneLayoutHandler = track(`handler:${entry.id}`, 2);
|
|
183
|
+
const component = await resolveLayoutComponent(entry, context);
|
|
184
|
+
doneLayoutHandler();
|
|
189
185
|
|
|
190
186
|
segments.push({
|
|
191
187
|
id: entry.shortCode,
|
|
@@ -201,6 +197,20 @@ export async function resolveSegment<TEnv>(
|
|
|
201
197
|
...(entry.mountPath ? { mountPath: entry.mountPath } : {}),
|
|
202
198
|
});
|
|
203
199
|
|
|
200
|
+
for (const parallelEntry of entry.parallel) {
|
|
201
|
+
const parallelSegments = await resolveParallelEntry(
|
|
202
|
+
parallelEntry,
|
|
203
|
+
params,
|
|
204
|
+
context,
|
|
205
|
+
false,
|
|
206
|
+
entry.shortCode,
|
|
207
|
+
deps,
|
|
208
|
+
options,
|
|
209
|
+
routeKey,
|
|
210
|
+
);
|
|
211
|
+
segments.push(...parallelSegments);
|
|
212
|
+
}
|
|
213
|
+
|
|
204
214
|
for (const orphan of entry.layout) {
|
|
205
215
|
const orphanSegments = await resolveOrphanLayout(
|
|
206
216
|
orphan,
|
|
@@ -210,6 +220,7 @@ export async function resolveSegment<TEnv>(
|
|
|
210
220
|
false,
|
|
211
221
|
deps,
|
|
212
222
|
options,
|
|
223
|
+
routeKey,
|
|
213
224
|
);
|
|
214
225
|
segments.push(...orphanSegments);
|
|
215
226
|
}
|
|
@@ -228,22 +239,36 @@ export async function resolveSegment<TEnv>(
|
|
|
228
239
|
// the correct tree composition order (layouts wrap the route content).
|
|
229
240
|
(context as InternalHandlerContext<any, TEnv>)._currentSegmentId =
|
|
230
241
|
entry.shortCode;
|
|
231
|
-
let component: ReactNode | undefined
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
component = await tryStaticLookup(
|
|
236
|
-
(entry as any).staticHandlerId,
|
|
237
|
-
entry.shortCode,
|
|
238
|
-
);
|
|
239
|
-
}
|
|
242
|
+
let component: ReactNode | undefined = await tryStaticHandler(
|
|
243
|
+
entry,
|
|
244
|
+
entry.shortCode,
|
|
245
|
+
);
|
|
240
246
|
if (component === undefined) {
|
|
247
|
+
const doneRouteHandler = track(`handler:${entry.id}`, 2);
|
|
241
248
|
if (entry.loading) {
|
|
242
249
|
const result = handleHandlerResult(entry.handler(context));
|
|
243
|
-
|
|
244
|
-
result
|
|
250
|
+
if (result instanceof Promise) {
|
|
251
|
+
result.finally(doneRouteHandler).catch(() => {});
|
|
252
|
+
const tracked = deps.trackHandler(result, {
|
|
253
|
+
segmentId: entry.shortCode,
|
|
254
|
+
segmentType: entry.type,
|
|
255
|
+
});
|
|
256
|
+
observeStreamedHandler(
|
|
257
|
+
tracked,
|
|
258
|
+
entry.shortCode,
|
|
259
|
+
entry.type,
|
|
260
|
+
context.pathname,
|
|
261
|
+
routeKey,
|
|
262
|
+
params,
|
|
263
|
+
);
|
|
264
|
+
component = tracked;
|
|
265
|
+
} else {
|
|
266
|
+
doneRouteHandler();
|
|
267
|
+
component = result;
|
|
268
|
+
}
|
|
245
269
|
} else {
|
|
246
270
|
component = handleHandlerResult(await entry.handler(context));
|
|
271
|
+
doneRouteHandler();
|
|
247
272
|
}
|
|
248
273
|
}
|
|
249
274
|
|
|
@@ -256,6 +281,7 @@ export async function resolveSegment<TEnv>(
|
|
|
256
281
|
true,
|
|
257
282
|
deps,
|
|
258
283
|
options,
|
|
284
|
+
routeKey,
|
|
259
285
|
);
|
|
260
286
|
segments.push(...orphanSegments);
|
|
261
287
|
}
|
|
@@ -269,6 +295,7 @@ export async function resolveSegment<TEnv>(
|
|
|
269
295
|
entry.shortCode,
|
|
270
296
|
deps,
|
|
271
297
|
options,
|
|
298
|
+
routeKey,
|
|
272
299
|
);
|
|
273
300
|
segments.push(...parallelSegments);
|
|
274
301
|
}
|
|
@@ -303,6 +330,7 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
303
330
|
belongsToRoute: boolean,
|
|
304
331
|
deps: SegmentResolutionDeps<TEnv>,
|
|
305
332
|
options?: ResolveSegmentOptions,
|
|
333
|
+
routeKey?: string,
|
|
306
334
|
): Promise<ResolvedSegment[]> {
|
|
307
335
|
invariant(
|
|
308
336
|
orphan.type === "layout" || orphan.type === "cache",
|
|
@@ -320,34 +348,11 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
320
348
|
segments.push(...loaderSegments);
|
|
321
349
|
}
|
|
322
350
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
belongsToRoute,
|
|
329
|
-
orphan.shortCode,
|
|
330
|
-
deps,
|
|
331
|
-
options,
|
|
332
|
-
);
|
|
333
|
-
segments.push(...parallelSegments);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
// Static handler interception for orphan layouts
|
|
337
|
-
const orphanAny = orphan as any;
|
|
338
|
-
let component: ReactNode | undefined;
|
|
339
|
-
if (orphanAny.isStaticPrerender && orphanAny.staticHandlerId) {
|
|
340
|
-
component = await tryStaticLookup(
|
|
341
|
-
orphanAny.staticHandlerId,
|
|
342
|
-
orphan.shortCode,
|
|
343
|
-
);
|
|
344
|
-
}
|
|
345
|
-
if (component === undefined) {
|
|
346
|
-
component =
|
|
347
|
-
typeof orphan.handler === "function"
|
|
348
|
-
? handleHandlerResult(await orphan.handler(context))
|
|
349
|
-
: orphan.handler;
|
|
350
|
-
}
|
|
351
|
+
// Handler-first: orphan layout handler executes before its parallels
|
|
352
|
+
// so that ctx.set() values are visible to parallel children.
|
|
353
|
+
const doneOrphanHandler = track(`handler:${orphan.id}`, 2);
|
|
354
|
+
const component = await resolveLayoutComponent(orphan, context);
|
|
355
|
+
doneOrphanHandler();
|
|
351
356
|
|
|
352
357
|
segments.push({
|
|
353
358
|
id: orphan.shortCode,
|
|
@@ -363,6 +368,20 @@ export async function resolveOrphanLayout<TEnv>(
|
|
|
363
368
|
...(orphan.mountPath ? { mountPath: orphan.mountPath } : {}),
|
|
364
369
|
});
|
|
365
370
|
|
|
371
|
+
for (const parallelEntry of orphan.parallel) {
|
|
372
|
+
const parallelSegments = await resolveParallelEntry(
|
|
373
|
+
parallelEntry,
|
|
374
|
+
params,
|
|
375
|
+
context,
|
|
376
|
+
belongsToRoute,
|
|
377
|
+
orphan.shortCode,
|
|
378
|
+
deps,
|
|
379
|
+
options,
|
|
380
|
+
routeKey,
|
|
381
|
+
);
|
|
382
|
+
segments.push(...parallelSegments);
|
|
383
|
+
}
|
|
384
|
+
|
|
366
385
|
return segments;
|
|
367
386
|
}
|
|
368
387
|
|
|
@@ -377,6 +396,7 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
377
396
|
parentShortCode: string,
|
|
378
397
|
deps: SegmentResolutionDeps<TEnv>,
|
|
379
398
|
options?: ResolveSegmentOptions,
|
|
399
|
+
routeKey?: string,
|
|
380
400
|
): Promise<ResolvedSegment[]> {
|
|
381
401
|
invariant(
|
|
382
402
|
parallelEntry.type === "parallel",
|
|
@@ -392,27 +412,45 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
392
412
|
>;
|
|
393
413
|
|
|
394
414
|
for (const [slot, handler] of Object.entries(slots)) {
|
|
395
|
-
let component: ReactNode | undefined
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
component = await tryStaticLookup(
|
|
401
|
-
slotStaticId,
|
|
402
|
-
`${parentShortCode}.${slot}`,
|
|
403
|
-
);
|
|
404
|
-
}
|
|
415
|
+
let component: ReactNode | undefined = await tryStaticSlot(
|
|
416
|
+
parallelEntry,
|
|
417
|
+
slot,
|
|
418
|
+
`${parentShortCode}.${slot}`,
|
|
419
|
+
);
|
|
405
420
|
|
|
406
421
|
if (component === undefined) {
|
|
422
|
+
const doneParallelHandler = track(
|
|
423
|
+
`handler:${parallelEntry.id}.${slot}`,
|
|
424
|
+
2,
|
|
425
|
+
);
|
|
407
426
|
const hasLoadingFallback =
|
|
408
427
|
parallelEntry.loading !== undefined && parallelEntry.loading !== false;
|
|
409
428
|
if (hasLoadingFallback) {
|
|
410
429
|
const result =
|
|
411
430
|
typeof handler === "function" ? handler(context) : handler;
|
|
412
|
-
|
|
431
|
+
if (result instanceof Promise) {
|
|
432
|
+
result.finally(doneParallelHandler).catch(() => {});
|
|
433
|
+
const tracked = deps.trackHandler(result, {
|
|
434
|
+
segmentId: `${parentShortCode}.${slot}`,
|
|
435
|
+
segmentType: "parallel",
|
|
436
|
+
});
|
|
437
|
+
observeStreamedHandler(
|
|
438
|
+
tracked,
|
|
439
|
+
`${parentShortCode}.${slot}`,
|
|
440
|
+
"parallel",
|
|
441
|
+
context.pathname,
|
|
442
|
+
routeKey,
|
|
443
|
+
params,
|
|
444
|
+
);
|
|
445
|
+
component = tracked as ReactNode;
|
|
446
|
+
} else {
|
|
447
|
+
doneParallelHandler();
|
|
448
|
+
component = result as ReactNode;
|
|
449
|
+
}
|
|
413
450
|
} else {
|
|
414
451
|
component =
|
|
415
452
|
typeof handler === "function" ? await handler(context) : handler;
|
|
453
|
+
doneParallelHandler();
|
|
416
454
|
}
|
|
417
455
|
}
|
|
418
456
|
|
|
@@ -448,136 +486,6 @@ export async function resolveParallelEntry<TEnv>(
|
|
|
448
486
|
return segments;
|
|
449
487
|
}
|
|
450
488
|
|
|
451
|
-
/**
|
|
452
|
-
* Wrapper that adds error boundary handling to segment resolution.
|
|
453
|
-
*/
|
|
454
|
-
export async function resolveWithErrorHandling<TEnv>(
|
|
455
|
-
entry: EntryData,
|
|
456
|
-
routeKey: string,
|
|
457
|
-
params: Record<string, string>,
|
|
458
|
-
context: HandlerContext<any, TEnv>,
|
|
459
|
-
loaderPromises: Map<string, Promise<any>>,
|
|
460
|
-
resolveFn: () => Promise<ResolvedSegment[]>,
|
|
461
|
-
deps: SegmentResolutionDeps<TEnv>,
|
|
462
|
-
errorContext?: {
|
|
463
|
-
env?: TEnv;
|
|
464
|
-
isPartial?: boolean;
|
|
465
|
-
requestStartTime?: number;
|
|
466
|
-
},
|
|
467
|
-
): Promise<ResolvedSegment[]> {
|
|
468
|
-
try {
|
|
469
|
-
return await resolveFn();
|
|
470
|
-
} catch (error) {
|
|
471
|
-
if (error instanceof Response) {
|
|
472
|
-
throw error;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (error instanceof DataNotFoundError) {
|
|
476
|
-
const notFoundFallback = deps.findNearestNotFoundBoundary(entry);
|
|
477
|
-
|
|
478
|
-
if (notFoundFallback) {
|
|
479
|
-
const notFoundInfo = createNotFoundInfo(
|
|
480
|
-
error,
|
|
481
|
-
entry.shortCode,
|
|
482
|
-
entry.type,
|
|
483
|
-
context.pathname,
|
|
484
|
-
);
|
|
485
|
-
|
|
486
|
-
// Safe request access: during build-time prerendering, context.request
|
|
487
|
-
// is a throwing getter. Use undefined when unavailable.
|
|
488
|
-
let safeRequest: Request | undefined;
|
|
489
|
-
try {
|
|
490
|
-
safeRequest = context.request;
|
|
491
|
-
} catch {}
|
|
492
|
-
|
|
493
|
-
deps.callOnError(error, "handler", {
|
|
494
|
-
request: safeRequest as Request,
|
|
495
|
-
url: context.url,
|
|
496
|
-
routeKey,
|
|
497
|
-
params,
|
|
498
|
-
segmentId: entry.shortCode,
|
|
499
|
-
segmentType: entry.type as any,
|
|
500
|
-
env: errorContext?.env,
|
|
501
|
-
isPartial: errorContext?.isPartial,
|
|
502
|
-
handledByBoundary: true,
|
|
503
|
-
metadata: { notFound: true, message: notFoundInfo.message },
|
|
504
|
-
requestStartTime: errorContext?.requestStartTime,
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
debugLog("segment", "notFound boundary handled error", {
|
|
508
|
-
segmentId: entry.shortCode,
|
|
509
|
-
message: notFoundInfo.message,
|
|
510
|
-
});
|
|
511
|
-
|
|
512
|
-
const reqCtx = getRequestContext();
|
|
513
|
-
if (reqCtx) {
|
|
514
|
-
reqCtx.res = new Response(null, {
|
|
515
|
-
status: 404,
|
|
516
|
-
headers: reqCtx.res.headers,
|
|
517
|
-
});
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
const notFoundSegment = createNotFoundSegment(
|
|
521
|
-
notFoundInfo,
|
|
522
|
-
notFoundFallback,
|
|
523
|
-
entry,
|
|
524
|
-
params,
|
|
525
|
-
);
|
|
526
|
-
return [notFoundSegment];
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
const fallback = deps.findNearestErrorBoundary(entry);
|
|
531
|
-
const segmentType: ErrorInfo["segmentType"] = entry.type;
|
|
532
|
-
const errorInfo = createErrorInfo(error, entry.shortCode, segmentType);
|
|
533
|
-
const effectiveFallback = fallback ?? DefaultErrorFallback;
|
|
534
|
-
|
|
535
|
-
// Safe request access: during build-time prerendering, context.request
|
|
536
|
-
// is a throwing getter. Use undefined when unavailable.
|
|
537
|
-
let safeReq: Request | undefined;
|
|
538
|
-
try {
|
|
539
|
-
safeReq = context.request;
|
|
540
|
-
} catch {}
|
|
541
|
-
|
|
542
|
-
deps.callOnError(error, "handler", {
|
|
543
|
-
request: safeReq as Request,
|
|
544
|
-
url: context.url,
|
|
545
|
-
routeKey,
|
|
546
|
-
params,
|
|
547
|
-
segmentId: entry.shortCode,
|
|
548
|
-
segmentType: entry.type as any,
|
|
549
|
-
env: errorContext?.env,
|
|
550
|
-
isPartial: errorContext?.isPartial,
|
|
551
|
-
handledByBoundary: !!fallback,
|
|
552
|
-
requestStartTime: errorContext?.requestStartTime,
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
debugLog("segment", "error boundary handled error", {
|
|
556
|
-
segmentId: entry.shortCode,
|
|
557
|
-
boundary: fallback ? "custom" : "default",
|
|
558
|
-
message: errorInfo.message,
|
|
559
|
-
});
|
|
560
|
-
|
|
561
|
-
{
|
|
562
|
-
const reqCtx = getRequestContext();
|
|
563
|
-
if (reqCtx) {
|
|
564
|
-
reqCtx.res = new Response(null, {
|
|
565
|
-
status: 500,
|
|
566
|
-
headers: reqCtx.res.headers,
|
|
567
|
-
});
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
const errorSegment = createErrorSegment(
|
|
572
|
-
errorInfo,
|
|
573
|
-
effectiveFallback,
|
|
574
|
-
entry,
|
|
575
|
-
params,
|
|
576
|
-
);
|
|
577
|
-
return [errorSegment];
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
489
|
/**
|
|
582
490
|
* Resolve all segments for a route (used for single-cache-per-request pattern).
|
|
583
491
|
*/
|
|
@@ -593,13 +501,24 @@ export async function resolveAllSegments<TEnv>(
|
|
|
593
501
|
const allSegments: ResolvedSegment[] = [];
|
|
594
502
|
const seenIds = new Set<string>();
|
|
595
503
|
|
|
504
|
+
// Safe request access: during build-time prerendering, context.request
|
|
505
|
+
// is a throwing getter. Use undefined when unavailable.
|
|
506
|
+
let safeRequest: Request | undefined;
|
|
507
|
+
try {
|
|
508
|
+
safeRequest = context.request;
|
|
509
|
+
} catch {}
|
|
510
|
+
|
|
511
|
+
// Get telemetry sink from RouterContext (may not exist during prerendering)
|
|
512
|
+
let telemetry;
|
|
513
|
+
try {
|
|
514
|
+
telemetry = getRouterContext()?.telemetry;
|
|
515
|
+
} catch {}
|
|
516
|
+
|
|
596
517
|
for (const entry of entries) {
|
|
597
|
-
const
|
|
518
|
+
const doneEntry = track(`segment:${entry.id}`, 1);
|
|
519
|
+
const resolvedSegments = await resolveWithErrorBoundary(
|
|
598
520
|
entry,
|
|
599
|
-
routeKey,
|
|
600
521
|
params,
|
|
601
|
-
context,
|
|
602
|
-
loaderPromises,
|
|
603
522
|
() =>
|
|
604
523
|
resolveSegment(
|
|
605
524
|
entry,
|
|
@@ -611,8 +530,12 @@ export async function resolveAllSegments<TEnv>(
|
|
|
611
530
|
false,
|
|
612
531
|
options,
|
|
613
532
|
),
|
|
533
|
+
(seg) => [seg],
|
|
614
534
|
deps,
|
|
535
|
+
{ request: safeRequest, url: context.url, routeKey, telemetry },
|
|
536
|
+
context.pathname,
|
|
615
537
|
);
|
|
538
|
+
doneEntry();
|
|
616
539
|
// Deduplicate by segment ID. include() scopes can produce entries that
|
|
617
540
|
// resolve the same shared layout/loader segment. Duplicates in the segment
|
|
618
541
|
// array propagate to the client's matched[] and change the React tree depth.
|