@rangojs/router 0.0.0-experimental.18 → 0.0.0-experimental.19
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 +46 -8
- package/dist/bin/rango.js +105 -18
- package/dist/vite/index.js +227 -93
- package/package.json +15 -14
- package/skills/hooks/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +79 -0
- package/skills/layout/SKILL.md +62 -2
- package/skills/loader/SKILL.md +94 -1
- package/skills/middleware/SKILL.md +81 -0
- package/skills/parallel/SKILL.md +57 -2
- package/skills/prerender/SKILL.md +187 -17
- package/skills/route/SKILL.md +42 -1
- package/skills/router-setup/SKILL.md +77 -0
- package/src/__internal.ts +1 -1
- package/src/bin/rango.ts +38 -19
- package/src/browser/action-coordinator.ts +97 -0
- package/src/browser/event-controller.ts +25 -27
- package/src/browser/history-state.ts +80 -0
- package/src/browser/intercept-utils.ts +1 -1
- package/src/browser/link-interceptor.ts +0 -3
- package/src/browser/merge-segment-loaders.ts +9 -2
- package/src/browser/navigation-bridge.ts +46 -13
- package/src/browser/navigation-client.ts +32 -61
- package/src/browser/navigation-store.ts +1 -31
- package/src/browser/navigation-transaction.ts +46 -207
- package/src/browser/partial-update.ts +102 -150
- package/src/browser/{prefetch-cache.ts → prefetch/cache.ts} +23 -4
- package/src/browser/{prefetch-fetch.ts → prefetch/fetch.ts} +36 -8
- package/src/browser/prefetch/policy.ts +42 -0
- package/src/browser/{prefetch-queue.ts → prefetch/queue.ts} +10 -3
- package/src/browser/react/Link.tsx +28 -23
- package/src/browser/react/NavigationProvider.tsx +9 -1
- package/src/browser/react/index.ts +2 -6
- package/src/browser/react/location-state-shared.ts +1 -1
- package/src/browser/react/location-state.ts +2 -0
- package/src/browser/react/nonce-context.ts +23 -0
- package/src/browser/react/use-action.ts +9 -1
- package/src/browser/react/use-handle.ts +3 -25
- package/src/browser/react/use-params.ts +2 -4
- package/src/browser/react/use-pathname.ts +2 -3
- package/src/browser/react/use-router.ts +1 -1
- package/src/browser/react/use-search-params.ts +2 -1
- package/src/browser/react/use-segments.ts +7 -60
- package/src/browser/response-adapter.ts +73 -0
- package/src/browser/rsc-router.tsx +29 -23
- package/src/browser/scroll-restoration.ts +10 -7
- package/src/browser/server-action-bridge.ts +115 -96
- package/src/browser/types.ts +1 -31
- package/src/browser/validate-redirect-origin.ts +29 -0
- package/src/build/generate-manifest.ts +5 -0
- package/src/build/generate-route-types.ts +2 -0
- 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 +45 -3
- 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 +132 -96
- package/src/cache/cache-scope.ts +71 -73
- package/src/cache/cf/cf-cache-store.ts +9 -4
- package/src/cache/document-cache.ts +72 -47
- package/src/cache/handle-capture.ts +81 -0
- package/src/cache/memory-segment-store.ts +18 -7
- package/src/cache/profile-registry.ts +43 -8
- package/src/cache/read-through-swr.ts +134 -0
- package/src/cache/segment-codec.ts +101 -112
- package/src/cache/taint.ts +26 -0
- package/src/client.tsx +53 -30
- 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/router.ts +14 -1
- package/src/href-client.ts +3 -1
- package/src/index.rsc.ts +33 -1
- package/src/index.ts +27 -0
- package/src/loader.rsc.ts +12 -4
- package/src/loader.ts +8 -0
- package/src/prerender/store.ts +4 -3
- 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/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 +94 -15
- package/src/router/intercept-resolution.ts +6 -4
- package/src/router/lazy-includes.ts +4 -0
- package/src/router/loader-resolution.ts +1 -0
- package/src/router/logging.ts +100 -3
- package/src/router/manifest.ts +32 -3
- package/src/router/match-api.ts +61 -7
- 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 +69 -4
- package/src/router/match-middleware/cache-store.ts +2 -0
- package/src/router/match-pipelines.ts +8 -43
- package/src/router/middleware-types.ts +7 -0
- package/src/router/middleware.ts +93 -8
- package/src/router/pattern-matching.ts +41 -5
- package/src/router/prerender-match.ts +34 -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 +34 -0
- package/src/router/router-options.ts +200 -0
- package/src/router/segment-resolution/fresh.ts +123 -30
- package/src/router/segment-resolution/helpers.ts +19 -0
- package/src/router/segment-resolution/loader-cache.ts +37 -146
- package/src/router/segment-resolution/revalidation.ts +358 -94
- 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/types.ts +7 -1
- package/src/router.ts +155 -11
- package/src/rsc/handler-context.ts +11 -0
- package/src/rsc/handler.ts +380 -88
- package/src/rsc/helpers.ts +25 -16
- package/src/rsc/loader-fetch.ts +84 -42
- package/src/rsc/origin-guard.ts +141 -0
- package/src/rsc/progressive-enhancement.ts +232 -19
- package/src/rsc/response-route-handler.ts +37 -26
- package/src/rsc/rsc-rendering.ts +12 -5
- package/src/rsc/runtime-warnings.ts +42 -0
- package/src/rsc/server-action.ts +134 -58
- package/src/rsc/types.ts +8 -0
- package/src/search-params.ts +22 -10
- package/src/server/context.ts +53 -5
- package/src/server/fetchable-loader-store.ts +11 -6
- package/src/server/handle-store.ts +66 -9
- package/src/server/loader-registry.ts +11 -46
- package/src/server/request-context.ts +90 -9
- package/src/ssr/index.tsx +63 -27
- package/src/static-handler.ts +7 -0
- package/src/theme/ThemeProvider.tsx +6 -1
- package/src/theme/index.ts +1 -6
- package/src/theme/theme-context.ts +1 -28
- package/src/theme/theme-script.ts +2 -1
- package/src/types/cache-types.ts +5 -0
- package/src/types/error-types.ts +3 -0
- package/src/types/global-namespace.ts +9 -0
- package/src/types/handler-context.ts +35 -13
- package/src/types/loader-types.ts +7 -0
- package/src/types/route-entry.ts +28 -0
- 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 +27 -2
- package/src/urls/type-extraction.ts +23 -15
- package/src/use-loader.tsx +12 -4
- package/src/vite/discovery/bundle-postprocess.ts +12 -7
- package/src/vite/discovery/discover-routers.ts +30 -18
- package/src/vite/discovery/prerender-collection.ts +24 -27
- package/src/vite/discovery/route-types-writer.ts +7 -7
- package/src/vite/discovery/virtual-module-codegen.ts +5 -2
- package/src/vite/plugins/client-ref-hashing.ts +3 -3
- package/src/vite/plugins/use-cache-transform.ts +91 -3
- package/src/vite/rango.ts +3 -3
- package/src/vite/router-discovery.ts +99 -36
- package/src/vite/utils/prerender-utils.ts +21 -0
- package/src/vite/utils/shared-utils.ts +3 -1
- package/src/browser/request-controller.ts +0 -164
- package/src/href-context.ts +0 -33
- package/src/router.gen.ts +0 -6
- package/src/static-handler.gen.ts +0 -5
- package/src/urls.gen.ts +0 -8
- /package/src/browser/{prefetch-observer.ts → prefetch/observer.ts} +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router Timeout
|
|
3
|
+
*
|
|
4
|
+
* Types, resolution logic, and helpers for request-level timeouts.
|
|
5
|
+
* Timeouts wrap action execution and render-start phases with
|
|
6
|
+
* a Promise.race mechanism, returning 504 on expiry.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Public types
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export interface RouterTimeouts {
|
|
14
|
+
/** Timeout for server action execution (ms). */
|
|
15
|
+
actionMs?: number;
|
|
16
|
+
/** Timeout for initial render/response production (ms). */
|
|
17
|
+
renderStartMs?: number;
|
|
18
|
+
/** Timeout for idle streaming after render starts (ms). Reserved for PR 2. */
|
|
19
|
+
streamIdleMs?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type TimeoutPhase = "action" | "render-start" | "stream-idle";
|
|
23
|
+
|
|
24
|
+
export interface TimeoutContext<TEnv = any> {
|
|
25
|
+
phase: TimeoutPhase;
|
|
26
|
+
request: Request;
|
|
27
|
+
url: URL;
|
|
28
|
+
env: TEnv;
|
|
29
|
+
routeKey?: string;
|
|
30
|
+
actionId?: string;
|
|
31
|
+
durationMs: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type OnTimeoutCallback<TEnv = any> = (
|
|
35
|
+
ctx: TimeoutContext<TEnv>,
|
|
36
|
+
) => Response | Promise<Response>;
|
|
37
|
+
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Internal resolved form
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
export interface ResolvedTimeouts {
|
|
43
|
+
actionMs: number | undefined;
|
|
44
|
+
renderStartMs: number | undefined;
|
|
45
|
+
streamIdleMs: number | undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Merge the `timeout` shorthand with the structured `timeouts` object.
|
|
50
|
+
*
|
|
51
|
+
* - `timeout` applies to `actionMs` and `renderStartMs` (NOT `streamIdleMs`).
|
|
52
|
+
* - Explicit `timeouts.*` values override the shorthand.
|
|
53
|
+
* - Returns `undefined` for any phase that has no configured value.
|
|
54
|
+
*/
|
|
55
|
+
export function resolveTimeouts(
|
|
56
|
+
timeout?: number,
|
|
57
|
+
timeouts?: RouterTimeouts,
|
|
58
|
+
): ResolvedTimeouts {
|
|
59
|
+
return {
|
|
60
|
+
actionMs: timeouts?.actionMs ?? timeout ?? undefined,
|
|
61
|
+
renderStartMs: timeouts?.renderStartMs ?? timeout ?? undefined,
|
|
62
|
+
streamIdleMs: timeouts?.streamIdleMs ?? undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Error class
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
export class RouterTimeoutError extends Error {
|
|
71
|
+
override name = "RouterTimeoutError" as const;
|
|
72
|
+
phase: TimeoutPhase;
|
|
73
|
+
durationMs: number;
|
|
74
|
+
|
|
75
|
+
constructor(phase: TimeoutPhase, durationMs: number) {
|
|
76
|
+
super(
|
|
77
|
+
`Request timed out during ${phase} after ${Math.round(durationMs)}ms`,
|
|
78
|
+
);
|
|
79
|
+
this.phase = phase;
|
|
80
|
+
this.durationMs = durationMs;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// Race helper
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
type TimeoutResult<T> =
|
|
89
|
+
| { result: T; timedOut: false }
|
|
90
|
+
| { timedOut: true; durationMs: number };
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Race an operation against a deadline.
|
|
94
|
+
*
|
|
95
|
+
* Returns a discriminated union so callers handle the timeout case
|
|
96
|
+
* without try/catch. Non-timeout errors from the operation re-throw.
|
|
97
|
+
*
|
|
98
|
+
* When `timeoutMs` is `undefined` or `<= 0`, the operation runs
|
|
99
|
+
* without any deadline (pass-through).
|
|
100
|
+
*/
|
|
101
|
+
export async function withTimeout<T>(
|
|
102
|
+
operation: Promise<T>,
|
|
103
|
+
timeoutMs: number | undefined,
|
|
104
|
+
phase: TimeoutPhase,
|
|
105
|
+
): Promise<TimeoutResult<T>> {
|
|
106
|
+
if (timeoutMs == null || timeoutMs <= 0) {
|
|
107
|
+
return { result: await operation, timedOut: false };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const start = performance.now();
|
|
111
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
112
|
+
|
|
113
|
+
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
114
|
+
timer = setTimeout(() => {
|
|
115
|
+
reject(new RouterTimeoutError(phase, performance.now() - start));
|
|
116
|
+
}, timeoutMs);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const result = await Promise.race([operation, timeoutPromise]);
|
|
121
|
+
clearTimeout(timer!);
|
|
122
|
+
return { result, timedOut: false };
|
|
123
|
+
} catch (error) {
|
|
124
|
+
clearTimeout(timer!);
|
|
125
|
+
if (error instanceof RouterTimeoutError) {
|
|
126
|
+
return { timedOut: true, durationMs: error.durationMs };
|
|
127
|
+
}
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Default response
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create the default 504 response for a timed-out request.
|
|
138
|
+
* Includes `X-Rango-Timeout-Phase` header for observability.
|
|
139
|
+
*/
|
|
140
|
+
export function createDefaultTimeoutResponse(phase: TimeoutPhase): Response {
|
|
141
|
+
return new Response("Request timed out", {
|
|
142
|
+
status: 504,
|
|
143
|
+
headers: {
|
|
144
|
+
"Content-Type": "text/plain;charset=utf-8",
|
|
145
|
+
"X-Rango-Timeout-Phase": phase,
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
}
|
package/src/router/types.ts
CHANGED
|
@@ -83,7 +83,13 @@ export interface SegmentResolutionDeps<TEnv = any> {
|
|
|
83
83
|
requestStartTime?: number;
|
|
84
84
|
},
|
|
85
85
|
) => Promise<LoaderDataResult<T>>;
|
|
86
|
-
trackHandler: <T>(
|
|
86
|
+
trackHandler: <T>(
|
|
87
|
+
promise: Promise<T>,
|
|
88
|
+
errorContext?: {
|
|
89
|
+
segmentId?: string;
|
|
90
|
+
segmentType?: string;
|
|
91
|
+
},
|
|
92
|
+
) => Promise<T>;
|
|
87
93
|
findNearestErrorBoundary: (
|
|
88
94
|
entry: EntryData | null,
|
|
89
95
|
) => ReactNode | ErrorBoundaryHandler | null;
|
package/src/router.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { createCacheScope } from "./cache/cache-scope.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
setCacheProfiles,
|
|
5
|
+
resolveCacheProfiles,
|
|
6
|
+
} from "./cache/profile-registry.js";
|
|
4
7
|
import { isCachedFunction } from "./cache/taint.js";
|
|
5
8
|
import { assertClientComponent } from "./component-utils.js";
|
|
6
9
|
import { DefaultDocument } from "./components/DefaultDocument.js";
|
|
@@ -68,12 +71,14 @@ import {
|
|
|
68
71
|
extractStaticPrefix,
|
|
69
72
|
traverseBack,
|
|
70
73
|
} from "./router/pattern-matching.js";
|
|
74
|
+
import { resolveSink, safeEmit, getRequestId } from "./router/telemetry.js";
|
|
71
75
|
import { evaluateRevalidation } from "./router/revalidation.js";
|
|
72
76
|
import {
|
|
73
77
|
type RouterContext,
|
|
74
78
|
runWithRouterContext,
|
|
75
79
|
} from "./router/router-context.js";
|
|
76
80
|
import { resolveThemeConfig } from "./theme/constants.js";
|
|
81
|
+
import { resolveTimeouts } from "./router/timeout.js";
|
|
77
82
|
|
|
78
83
|
// Extracted content negotiation utilities
|
|
79
84
|
import { flattenNamedRoutes } from "./router/content-negotiation.js";
|
|
@@ -111,6 +116,9 @@ export { RSC_ROUTER_BRAND, RouterRegistry } from "./router/router-registry.js";
|
|
|
111
116
|
export type {
|
|
112
117
|
RSCRouterOptions,
|
|
113
118
|
RootLayoutProps,
|
|
119
|
+
SSRStreamMode,
|
|
120
|
+
SSROptions,
|
|
121
|
+
ResolveStreamingContext,
|
|
114
122
|
} from "./router/router-options.js";
|
|
115
123
|
export type {
|
|
116
124
|
RSCRouter,
|
|
@@ -142,12 +150,22 @@ export function createRouter<TEnv = any>(
|
|
|
142
150
|
prefetchCacheControl: prefetchCacheControlOption,
|
|
143
151
|
warmup: warmupOption,
|
|
144
152
|
allowDebugManifest: allowDebugManifestOption = false,
|
|
153
|
+
telemetry: telemetrySink,
|
|
154
|
+
ssr: ssrOption,
|
|
155
|
+
timeout: timeoutShorthand,
|
|
156
|
+
timeouts: timeoutsOption,
|
|
157
|
+
onTimeout,
|
|
158
|
+
originCheck: originCheckOption,
|
|
145
159
|
} = options;
|
|
146
160
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
// Resolve telemetry sink (no-op when not configured)
|
|
162
|
+
const telemetry = resolveSink(telemetrySink);
|
|
163
|
+
|
|
164
|
+
// Resolve cache profiles: merge user config with guaranteed default profile.
|
|
165
|
+
// This resolved map is both stored on the router (for per-request context)
|
|
166
|
+
// and written to the global registry (for DSL-time cache("profileName")).
|
|
167
|
+
const resolvedCacheProfiles = resolveCacheProfiles(cacheProfilesOption);
|
|
168
|
+
setCacheProfiles(resolvedCacheProfiles);
|
|
151
169
|
|
|
152
170
|
// Source file: prefer Vite-injected path (zero cost), fall back to
|
|
153
171
|
// stack trace parsing for non-Vite environments (e.g. tests).
|
|
@@ -196,15 +214,29 @@ export function createRouter<TEnv = any>(
|
|
|
196
214
|
? resolveThemeConfig(themeOption)
|
|
197
215
|
: null;
|
|
198
216
|
|
|
217
|
+
// Resolve timeout config (merge shorthand + structured)
|
|
218
|
+
const resolvedTimeouts = resolveTimeouts(timeoutShorthand, timeoutsOption);
|
|
219
|
+
|
|
199
220
|
/**
|
|
200
221
|
* Wrapper for invokeOnError that binds the router's onError callback.
|
|
201
222
|
* Uses the shared utility from router/error-handling.ts for consistent behavior.
|
|
223
|
+
*
|
|
224
|
+
* Deduplicates via per-request WeakSet stored on the ALS request context.
|
|
225
|
+
* A closure-level WeakSet would silently swallow errors if the same object
|
|
226
|
+
* instance is thrown across separate requests (e.g. a singleton error).
|
|
202
227
|
*/
|
|
203
228
|
function callOnError(
|
|
204
229
|
error: unknown,
|
|
205
230
|
phase: ErrorPhase,
|
|
206
231
|
context: Parameters<typeof invokeOnError<TEnv>>[3],
|
|
207
232
|
): void {
|
|
233
|
+
if (error != null && typeof error === "object") {
|
|
234
|
+
const reportedErrors = _getRequestContext()?._reportedErrors;
|
|
235
|
+
if (reportedErrors) {
|
|
236
|
+
if (reportedErrors.has(error)) return;
|
|
237
|
+
reportedErrors.add(error);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
208
240
|
invokeOnError(onError, error, phase, context, "Router");
|
|
209
241
|
}
|
|
210
242
|
|
|
@@ -340,14 +372,43 @@ export function createRouter<TEnv = any>(
|
|
|
340
372
|
return _getRequestContext()?._handleStore;
|
|
341
373
|
};
|
|
342
374
|
|
|
343
|
-
// Track a pending handler promise (non-blocking)
|
|
344
|
-
|
|
375
|
+
// Track a pending handler promise (non-blocking).
|
|
376
|
+
// Attaches a side-effect .catch() to report streaming handler errors to onError
|
|
377
|
+
// without altering the rejection chain (React's streaming error boundary still handles it).
|
|
378
|
+
const trackHandler = <T>(
|
|
379
|
+
promise: Promise<T>,
|
|
380
|
+
errorContext?: {
|
|
381
|
+
segmentId?: string;
|
|
382
|
+
segmentType?: string;
|
|
383
|
+
},
|
|
384
|
+
): Promise<T> => {
|
|
345
385
|
const store = getHandleStore();
|
|
346
|
-
|
|
386
|
+
const tracked = store ? store.track(promise) : promise;
|
|
387
|
+
|
|
388
|
+
// Report streaming handler errors to onError as a side-effect.
|
|
389
|
+
// The rejection still propagates to the RSC stream for client error boundaries.
|
|
390
|
+
// Captures request context eagerly (closure) so the catch handler has full context.
|
|
391
|
+
const reqCtx = _getRequestContext();
|
|
392
|
+
if (reqCtx && onError) {
|
|
393
|
+
tracked.catch((error) => {
|
|
394
|
+
callOnError(error, "handler", {
|
|
395
|
+
request: reqCtx.request,
|
|
396
|
+
url: reqCtx.url,
|
|
397
|
+
routeKey: reqCtx._routeName,
|
|
398
|
+
params: reqCtx.params as Record<string, string>,
|
|
399
|
+
env: reqCtx.env as TEnv,
|
|
400
|
+
segmentId: errorContext?.segmentId,
|
|
401
|
+
segmentType: errorContext?.segmentType as any,
|
|
402
|
+
handledByBoundary: true,
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return tracked;
|
|
347
408
|
};
|
|
348
409
|
|
|
349
410
|
// Wrapper for wrapLoaderWithErrorHandling that uses router's error boundary finder
|
|
350
|
-
// Includes onError callback for loader error notification
|
|
411
|
+
// Includes onError callback for loader error notification and telemetry emission.
|
|
351
412
|
function wrapLoaderPromise<T>(
|
|
352
413
|
promise: Promise<T>,
|
|
353
414
|
entry: EntryData,
|
|
@@ -363,7 +424,25 @@ export function createRouter<TEnv = any>(
|
|
|
363
424
|
requestStartTime?: number;
|
|
364
425
|
},
|
|
365
426
|
): Promise<LoaderDataResult<T>> {
|
|
366
|
-
|
|
427
|
+
const loaderStart = telemetrySink ? performance.now() : 0;
|
|
428
|
+
const loaderRequestId = telemetrySink
|
|
429
|
+
? errorContext?.request
|
|
430
|
+
? getRequestId(errorContext.request)
|
|
431
|
+
: undefined
|
|
432
|
+
: undefined;
|
|
433
|
+
if (telemetrySink) {
|
|
434
|
+
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
435
|
+
safeEmit(telemetry, {
|
|
436
|
+
type: "loader.start",
|
|
437
|
+
timestamp: loaderStart,
|
|
438
|
+
requestId: loaderRequestId,
|
|
439
|
+
segmentId,
|
|
440
|
+
loaderName,
|
|
441
|
+
pathname,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const result = wrapLoaderWithErrorHandling(
|
|
367
446
|
promise,
|
|
368
447
|
entry,
|
|
369
448
|
segmentId,
|
|
@@ -386,9 +465,42 @@ export function createRouter<TEnv = any>(
|
|
|
386
465
|
handledByBoundary: ctx.handledByBoundary,
|
|
387
466
|
requestStartTime: errorContext.requestStartTime,
|
|
388
467
|
});
|
|
468
|
+
if (telemetrySink) {
|
|
469
|
+
const errorObj =
|
|
470
|
+
error instanceof Error ? error : new Error(String(error));
|
|
471
|
+
safeEmit(telemetry, {
|
|
472
|
+
type: "loader.error",
|
|
473
|
+
timestamp: performance.now(),
|
|
474
|
+
requestId: loaderRequestId,
|
|
475
|
+
segmentId: ctx.segmentId,
|
|
476
|
+
loaderName: ctx.loaderName,
|
|
477
|
+
pathname,
|
|
478
|
+
error: errorObj,
|
|
479
|
+
handledByBoundary: ctx.handledByBoundary,
|
|
480
|
+
});
|
|
481
|
+
}
|
|
389
482
|
}
|
|
390
483
|
: undefined,
|
|
391
484
|
);
|
|
485
|
+
|
|
486
|
+
// Emit loader.end after the promise settles (fire-and-forget)
|
|
487
|
+
if (telemetrySink) {
|
|
488
|
+
const loaderName = segmentId.split(".").pop() || "unknown";
|
|
489
|
+
result.then((r) => {
|
|
490
|
+
safeEmit(telemetry, {
|
|
491
|
+
type: "loader.end",
|
|
492
|
+
timestamp: performance.now(),
|
|
493
|
+
requestId: loaderRequestId,
|
|
494
|
+
segmentId,
|
|
495
|
+
loaderName,
|
|
496
|
+
pathname,
|
|
497
|
+
durationMs: performance.now() - loaderStart,
|
|
498
|
+
ok: r.ok,
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return result;
|
|
392
504
|
}
|
|
393
505
|
|
|
394
506
|
// Dependencies object for extracted segment resolution functions.
|
|
@@ -468,6 +580,7 @@ export function createRouter<TEnv = any>(
|
|
|
468
580
|
resolveLoadersOnlyWithRevalidation,
|
|
469
581
|
resolveInterceptLoadersOnly,
|
|
470
582
|
resolveLoadersOnly,
|
|
583
|
+
telemetry: telemetrySink,
|
|
471
584
|
};
|
|
472
585
|
}
|
|
473
586
|
|
|
@@ -483,8 +596,15 @@ export function createRouter<TEnv = any>(
|
|
|
483
596
|
pathname: string,
|
|
484
597
|
params: Record<string, string>,
|
|
485
598
|
buildVars?: Record<string, any>,
|
|
599
|
+
isPassthroughRoute?: boolean,
|
|
486
600
|
) {
|
|
487
|
-
return _matchForPrerender(
|
|
601
|
+
return _matchForPrerender(
|
|
602
|
+
pathname,
|
|
603
|
+
params,
|
|
604
|
+
prerenderDeps,
|
|
605
|
+
buildVars,
|
|
606
|
+
isPassthroughRoute,
|
|
607
|
+
);
|
|
488
608
|
}
|
|
489
609
|
|
|
490
610
|
async function renderStaticSegment(
|
|
@@ -508,6 +628,7 @@ export function createRouter<TEnv = any>(
|
|
|
508
628
|
defaultErrorBoundary,
|
|
509
629
|
findMatch,
|
|
510
630
|
findInterceptForRoute,
|
|
631
|
+
telemetry: telemetrySink,
|
|
511
632
|
});
|
|
512
633
|
|
|
513
634
|
const { match, matchPartial, matchError, previewMatch } = matchHandlers;
|
|
@@ -568,6 +689,7 @@ export function createRouter<TEnv = any>(
|
|
|
568
689
|
parent: syntheticMapRoot,
|
|
569
690
|
counters: {},
|
|
570
691
|
mountIndex: currentMountIndex,
|
|
692
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
571
693
|
},
|
|
572
694
|
() => {
|
|
573
695
|
handlerResult = urlPatterns.handler() as AllUseItems[];
|
|
@@ -582,10 +704,15 @@ export function createRouter<TEnv = any>(
|
|
|
582
704
|
|
|
583
705
|
// Collect route keys that have prerender handlers (for non-trie match path)
|
|
584
706
|
let prerenderRouteKeys: Set<string> | undefined;
|
|
707
|
+
let passthroughRouteKeys: Set<string> | undefined;
|
|
585
708
|
for (const [name, entry] of manifest.entries()) {
|
|
586
709
|
if (entry.type === "route" && entry.isPrerender) {
|
|
587
710
|
if (!prerenderRouteKeys) prerenderRouteKeys = new Set();
|
|
588
711
|
prerenderRouteKeys.add(name);
|
|
712
|
+
if (entry.prerenderDef?.options?.passthrough === true) {
|
|
713
|
+
if (!passthroughRouteKeys) passthroughRouteKeys = new Set();
|
|
714
|
+
passthroughRouteKeys.add(name);
|
|
715
|
+
}
|
|
589
716
|
}
|
|
590
717
|
}
|
|
591
718
|
|
|
@@ -608,7 +735,9 @@ export function createRouter<TEnv = any>(
|
|
|
608
735
|
trailingSlash: trailingSlashConfig,
|
|
609
736
|
handler: urlPatterns.handler,
|
|
610
737
|
mountIndex: currentMountIndex,
|
|
738
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
611
739
|
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
740
|
+
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
612
741
|
});
|
|
613
742
|
}
|
|
614
743
|
} else {
|
|
@@ -625,7 +754,9 @@ export function createRouter<TEnv = any>(
|
|
|
625
754
|
trailingSlash: trailingSlashConfig,
|
|
626
755
|
handler: urlPatterns.handler,
|
|
627
756
|
mountIndex: currentMountIndex,
|
|
757
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
628
758
|
...(prerenderRouteKeys ? { prerenderRouteKeys } : {}),
|
|
759
|
+
...(passthroughRouteKeys ? { passthroughRouteKeys } : {}),
|
|
629
760
|
});
|
|
630
761
|
}
|
|
631
762
|
|
|
@@ -735,6 +866,9 @@ export function createRouter<TEnv = any>(
|
|
|
735
866
|
// Expose resolved theme configuration for NavigationProvider and MetaTags
|
|
736
867
|
themeConfig: resolvedThemeConfig,
|
|
737
868
|
|
|
869
|
+
// Expose resolved cache profiles for per-request resolution
|
|
870
|
+
cacheProfiles: resolvedCacheProfiles,
|
|
871
|
+
|
|
738
872
|
// Expose prefetch cache control for RSC handler
|
|
739
873
|
prefetchCacheControl,
|
|
740
874
|
|
|
@@ -744,6 +878,16 @@ export function createRouter<TEnv = any>(
|
|
|
744
878
|
// Expose debug manifest flag for handler
|
|
745
879
|
allowDebugManifest: allowDebugManifestOption,
|
|
746
880
|
|
|
881
|
+
// Expose origin check configuration for handler (default: enabled)
|
|
882
|
+
originCheck: originCheckOption ?? true,
|
|
883
|
+
|
|
884
|
+
// Expose SSR configuration for handler
|
|
885
|
+
ssr: ssrOption,
|
|
886
|
+
|
|
887
|
+
// Expose resolved timeouts for RSC handler
|
|
888
|
+
timeouts: resolvedTimeouts,
|
|
889
|
+
onTimeout,
|
|
890
|
+
|
|
747
891
|
// Expose global middleware for RSC handler
|
|
748
892
|
middleware: globalMiddleware,
|
|
749
893
|
|
|
@@ -10,6 +10,7 @@ import type { RSCRouterInternal } from "../router/router-interfaces.js";
|
|
|
10
10
|
import type { ErrorPhase } from "../types.js";
|
|
11
11
|
import type { InvokeOnErrorContext } from "../router/error-handling.js";
|
|
12
12
|
import type { RSCDependencies, LoadSSRModule } from "./types.js";
|
|
13
|
+
import type { SSRStreamMode } from "../router/router-options.js";
|
|
13
14
|
|
|
14
15
|
export interface HandlerContext<TEnv = unknown> {
|
|
15
16
|
router: RSCRouterInternal<TEnv, any>;
|
|
@@ -31,4 +32,14 @@ export interface HandlerContext<TEnv = unknown> {
|
|
|
31
32
|
redirectUrl: string,
|
|
32
33
|
locationState?: Record<string, unknown>,
|
|
33
34
|
) => Response;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the SSR stream mode for a given request.
|
|
38
|
+
* Returns "stream" when no resolveStreaming callback is configured.
|
|
39
|
+
*/
|
|
40
|
+
resolveStreamMode: (
|
|
41
|
+
request: Request,
|
|
42
|
+
env: TEnv,
|
|
43
|
+
url: URL,
|
|
44
|
+
) => Promise<SSRStreamMode>;
|
|
34
45
|
}
|