@timber-js/app 0.2.0-alpha.57 → 0.2.0-alpha.59
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/als-registry-Ba7URUIn.js.map +1 -1
- package/dist/_chunks/define-D5STJpIr.js +121 -0
- package/dist/_chunks/define-D5STJpIr.js.map +1 -0
- package/dist/_chunks/define-TK8C1M3x.js.map +1 -1
- package/dist/_chunks/{define-cookie-k9btcEfI.js → define-cookie-DtAavax4.js} +4 -4
- package/dist/_chunks/define-cookie-DtAavax4.js.map +1 -0
- package/dist/_chunks/{error-boundary-B9vT_YK_.js → error-boundary-DpZJBCqh.js} +1 -1
- package/dist/_chunks/{error-boundary-B9vT_YK_.js.map → error-boundary-DpZJBCqh.js.map} +1 -1
- package/dist/_chunks/interception-Cey5DCGr.js.map +1 -1
- package/dist/_chunks/{request-context-0h-6Voad.js → request-context-0wfZsnhh.js} +3 -1
- package/dist/_chunks/request-context-0wfZsnhh.js.map +1 -0
- package/dist/_chunks/{segment-context-Bmugn-ao.js → segment-context-CyaM1mrD.js} +1 -1
- package/dist/_chunks/{segment-context-Bmugn-ao.js.map → segment-context-CyaM1mrD.js.map} +1 -1
- package/dist/_chunks/{stale-reload-Db4wqE46.js → stale-reload-DKN3aXxR.js} +1 -1
- package/dist/_chunks/{stale-reload-Db4wqE46.js.map → stale-reload-DKN3aXxR.js.map} +1 -1
- package/dist/_chunks/{tracing-JI4cYUdz.js → tracing-VYETCQsg.js} +1 -1
- package/dist/_chunks/{tracing-JI4cYUdz.js.map → tracing-VYETCQsg.js.map} +1 -1
- package/dist/_chunks/use-query-states-wEXY2JQB.js.map +1 -1
- package/dist/_chunks/{wrappers-C9XPg7-U.js → wrappers-BaG1bnM3.js} +1 -1
- package/dist/_chunks/{wrappers-C9XPg7-U.js.map → wrappers-BaG1bnM3.js.map} +1 -1
- package/dist/cache/index.js +1 -1
- package/dist/cache/index.js.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/error-reconstituter.d.ts +54 -0
- package/dist/client/error-reconstituter.d.ts.map +1 -0
- package/dist/client/form.d.ts +2 -2
- package/dist/client/form.d.ts.map +1 -1
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +4 -4
- package/dist/client/index.js.map +1 -1
- package/dist/client/link.d.ts +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/segment-outlet.d.ts +63 -0
- package/dist/client/segment-outlet.d.ts.map +1 -0
- package/dist/client/use-params.d.ts +1 -1
- package/dist/client/use-params.d.ts.map +1 -1
- package/dist/client/use-query-states.d.ts +1 -1
- package/dist/client/use-query-states.d.ts.map +1 -1
- package/dist/cookies/define-cookie.d.ts +3 -3
- package/dist/cookies/define-cookie.d.ts.map +1 -1
- package/dist/cookies/index.js +1 -1
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +173 -6
- package/dist/index.js.map +1 -1
- package/dist/params/define.d.ts +25 -1
- package/dist/params/define.d.ts.map +1 -1
- package/dist/params/index.d.ts +5 -5
- package/dist/params/index.d.ts.map +1 -1
- package/dist/params/index.js +2 -103
- package/dist/plugins/adapter-build.d.ts +1 -1
- package/dist/plugins/adapter-build.d.ts.map +1 -1
- package/dist/plugins/build-manifest.d.ts +2 -2
- package/dist/plugins/build-manifest.d.ts.map +1 -1
- package/dist/plugins/build-report.d.ts +3 -3
- package/dist/plugins/build-report.d.ts.map +1 -1
- package/dist/plugins/content.d.ts +1 -1
- package/dist/plugins/content.d.ts.map +1 -1
- package/dist/plugins/dev-browser-logs.d.ts +84 -0
- package/dist/plugins/dev-browser-logs.d.ts.map +1 -0
- package/dist/plugins/dev-logs.d.ts +1 -1
- package/dist/plugins/dev-logs.d.ts.map +1 -1
- package/dist/plugins/dev-server.d.ts +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/plugins/entries.d.ts +1 -1
- package/dist/plugins/entries.d.ts.map +1 -1
- package/dist/plugins/fonts.d.ts +2 -2
- package/dist/plugins/fonts.d.ts.map +1 -1
- package/dist/plugins/mdx.d.ts +1 -1
- package/dist/plugins/mdx.d.ts.map +1 -1
- package/dist/plugins/routing.d.ts +1 -1
- package/dist/plugins/routing.d.ts.map +1 -1
- package/dist/plugins/shims.d.ts +8 -5
- package/dist/plugins/shims.d.ts.map +1 -1
- package/dist/plugins/static-build.d.ts +1 -1
- package/dist/plugins/static-build.d.ts.map +1 -1
- package/dist/search-params/define.d.ts +1 -1
- package/dist/search-params/define.d.ts.map +1 -1
- package/dist/search-params/index.d.ts +1 -1
- package/dist/search-params/index.d.ts.map +1 -1
- package/dist/search-params/index.js +1 -1
- package/dist/server/actions.d.ts +1 -1
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +7 -0
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/fallback-error.d.ts +3 -3
- package/dist/server/fallback-error.d.ts.map +1 -1
- package/dist/server/index.js +4 -4
- package/dist/server/index.js.map +1 -1
- package/dist/server/pipeline-interception.d.ts +1 -1
- package/dist/server/pipeline-interception.d.ts.map +1 -1
- package/dist/server/pipeline.d.ts +2 -2
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/rsc-entry/api-handler.d.ts +2 -2
- package/dist/server/rsc-entry/api-handler.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts +13 -13
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +2 -2
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts +2 -2
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +3 -3
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts +1 -1
- package/dist/server/rsc-entry/ssr-bridge.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +4 -4
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/status-code-resolver.d.ts +1 -1
- package/dist/server/status-code-resolver.d.ts.map +1 -1
- package/dist/server/stream-utils.d.ts +36 -0
- package/dist/server/stream-utils.d.ts.map +1 -0
- package/dist/server/tree-builder.d.ts +1 -1
- package/dist/server/tree-builder.d.ts.map +1 -1
- package/dist/shims/font-google.d.ts +1 -1
- package/dist/shims/font-google.d.ts.map +1 -1
- package/package.json +1 -4
- package/src/cache/timber-cache.ts +1 -1
- package/src/client/browser-entry.ts +7 -8
- package/src/client/error-reconstituter.tsx +65 -0
- package/src/client/form.tsx +2 -2
- package/src/client/index.ts +2 -2
- package/src/client/link.tsx +2 -2
- package/src/client/segment-outlet.tsx +86 -0
- package/src/client/use-params.ts +1 -1
- package/src/client/use-query-states.ts +2 -2
- package/src/cookies/define-cookie.ts +9 -9
- package/src/index.ts +17 -0
- package/src/params/define.ts +61 -1
- package/src/params/index.ts +5 -5
- package/src/plugins/adapter-build.ts +2 -2
- package/src/plugins/build-manifest.ts +2 -2
- package/src/plugins/build-report.ts +3 -3
- package/src/plugins/cache-transform.ts +1 -1
- package/src/plugins/content.ts +1 -1
- package/src/plugins/dev-browser-logs.ts +274 -0
- package/src/plugins/dev-logs.ts +1 -1
- package/src/plugins/dev-server.ts +3 -3
- package/src/plugins/entries.ts +1 -1
- package/src/plugins/fonts.ts +9 -9
- package/src/plugins/mdx.ts +1 -1
- package/src/plugins/routing.ts +6 -6
- package/src/plugins/server-action-exports.ts +1 -1
- package/src/plugins/shims.ts +19 -39
- package/src/plugins/static-build.ts +2 -2
- package/src/routing/scanner.ts +1 -1
- package/src/routing/status-file-lint.ts +1 -1
- package/src/search-params/define.ts +2 -2
- package/src/search-params/index.ts +1 -1
- package/src/server/action-client.ts +1 -1
- package/src/server/action-handler.ts +1 -1
- package/src/server/actions.ts +1 -1
- package/src/server/als-registry.ts +7 -0
- package/src/server/deny-renderer.ts +3 -2
- package/src/server/error-boundary-wrapper.ts +1 -1
- package/src/server/fallback-error.ts +6 -6
- package/src/server/pipeline-interception.ts +1 -1
- package/src/server/pipeline.ts +2 -2
- package/src/server/primitives.ts +1 -1
- package/src/server/request-context.ts +7 -1
- package/src/server/route-element-builder.ts +1 -1
- package/src/server/rsc-entry/api-handler.ts +8 -8
- package/src/server/rsc-entry/error-renderer.ts +120 -185
- package/src/server/rsc-entry/helpers.ts +2 -2
- package/src/server/rsc-entry/index.ts +42 -38
- package/src/server/rsc-entry/rsc-payload.ts +6 -6
- package/src/server/rsc-entry/rsc-stream.ts +6 -6
- package/src/server/rsc-entry/ssr-bridge.ts +2 -2
- package/src/server/rsc-entry/ssr-renderer.ts +16 -12
- package/src/server/slot-resolver.ts +2 -2
- package/src/server/ssr-entry.ts +3 -3
- package/src/server/status-code-resolver.ts +1 -1
- package/src/server/stream-utils.ts +209 -0
- package/src/server/tree-builder.ts +1 -1
- package/src/shims/font-google.ts +1 -1
- package/dist/_chunks/define-cookie-k9btcEfI.js.map +0 -1
- package/dist/_chunks/request-context-0h-6Voad.js.map +0 -1
- package/dist/params/index.js.map +0 -1
- package/dist/server/rsc-entry/ssr-error-bridge.d.ts +0 -12
- package/dist/server/rsc-entry/ssr-error-bridge.d.ts.map +0 -1
- package/dist/server/ssr-error-entry.d.ts +0 -65
- package/dist/server/ssr-error-entry.d.ts.map +0 -1
- package/src/server/rsc-entry/ssr-error-bridge.ts +0 -20
- package/src/server/ssr-error-entry.ts +0 -237
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RSC Error & No-Match Renderers — handles error pages and 404s.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* - TSX
|
|
6
|
-
*
|
|
4
|
+
* All error pages (TSX and MDX) render through the RSC → SSR pipeline:
|
|
5
|
+
* - TSX ('use client'): Error is converted to SerializableError, wrapped
|
|
6
|
+
* with ErrorReconstituter to reconstitute a real Error on the client.
|
|
7
|
+
* - MDX (server component): Receives plain props ({ status }), no wrapper needed.
|
|
7
8
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* when Error instances are passed as props to 'use client' components.
|
|
9
|
+
* This unified approach replaces the former SSR-only bypass for TSX error
|
|
10
|
+
* pages. See design/spike-TIM-565-unify-error-pages.md for the full analysis.
|
|
11
11
|
*
|
|
12
12
|
* Design docs: 10-error-handling.md §"Three-Tier Error Page Rendering"
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { createElement } from 'react';
|
|
16
|
-
import { renderToReadableStream } from '
|
|
17
|
-
|
|
18
|
-
import type { RouteMatch } from '
|
|
19
|
-
import { logRenderError } from '
|
|
20
|
-
import type { ManifestSegmentNode } from '
|
|
21
|
-
import { DenySignal, RenderError } from '
|
|
22
|
-
import type { ClientBootstrapConfig } from '
|
|
23
|
-
import { flightInitScript } from '
|
|
24
|
-
import { renderDenyPage } from '
|
|
25
|
-
import type { LayoutEntry } from '
|
|
26
|
-
import type { NavContext } from '
|
|
16
|
+
import { renderToReadableStream } from '../../rsc-runtime/rsc.js';
|
|
17
|
+
|
|
18
|
+
import type { RouteMatch } from '../pipeline.js';
|
|
19
|
+
import { logRenderError } from '../logger.js';
|
|
20
|
+
import type { ManifestSegmentNode } from '../route-matcher.js';
|
|
21
|
+
import { DenySignal, RenderError } from '../primitives.js';
|
|
22
|
+
import type { ClientBootstrapConfig } from '../html-injectors.js';
|
|
23
|
+
import { flightInitScript } from '../flight-scripts.js';
|
|
24
|
+
import { renderDenyPage } from '../deny-renderer.js';
|
|
25
|
+
import type { LayoutEntry } from '../deny-renderer.js';
|
|
26
|
+
import type { NavContext } from '../ssr-entry.js';
|
|
27
27
|
import { createDebugChannelSink } from './helpers.js';
|
|
28
|
-
import { getCookiesForSsr } from '
|
|
28
|
+
import { getCookiesForSsr } from '../request-context.js';
|
|
29
29
|
import { callSsr } from './ssr-bridge.js';
|
|
30
|
-
import {
|
|
30
|
+
import { teeWithErrorPropagation } from '../stream-utils.js';
|
|
31
|
+
import { isDevMode } from '../debug.js';
|
|
32
|
+
import { ErrorReconstituter } from '../../client/error-reconstituter.js';
|
|
33
|
+
import type { SerializableError } from '../../client/error-reconstituter.js';
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* A manifest file reference with lazy import and path.
|
|
@@ -55,14 +58,12 @@ function isMdxFile(filePath: string): boolean {
|
|
|
55
58
|
* Result of walking the segment chain for an error file.
|
|
56
59
|
*/
|
|
57
60
|
interface ErrorFileResolution {
|
|
58
|
-
/** The loaded component
|
|
61
|
+
/** The loaded component */
|
|
59
62
|
component: (...args: unknown[]) => unknown;
|
|
60
63
|
/** Index of the segment where the file was found */
|
|
61
64
|
segmentIndex: number;
|
|
62
65
|
/** Whether the file is MDX (server component) */
|
|
63
66
|
isMdx: boolean;
|
|
64
|
-
/** File path for SSR-only import */
|
|
65
|
-
filePath: string;
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
/**
|
|
@@ -91,7 +92,6 @@ async function resolveErrorFile(
|
|
|
91
92
|
component: mod.default as (...args: unknown[]) => unknown,
|
|
92
93
|
segmentIndex: i,
|
|
93
94
|
isMdx: isMdxFile(specificFile.filePath),
|
|
94
|
-
filePath: specificFile.filePath,
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
}
|
|
@@ -105,7 +105,6 @@ async function resolveErrorFile(
|
|
|
105
105
|
component: mod.default as (...args: unknown[]) => unknown,
|
|
106
106
|
segmentIndex: i,
|
|
107
107
|
isMdx: isMdxFile(categoryFile.filePath),
|
|
108
|
-
filePath: categoryFile.filePath,
|
|
109
108
|
};
|
|
110
109
|
}
|
|
111
110
|
}
|
|
@@ -119,7 +118,6 @@ async function resolveErrorFile(
|
|
|
119
118
|
component: mod.default as (...args: unknown[]) => unknown,
|
|
120
119
|
segmentIndex: i,
|
|
121
120
|
isMdx: isMdxFile(segment.error.filePath),
|
|
122
|
-
filePath: segment.error.filePath,
|
|
123
121
|
};
|
|
124
122
|
}
|
|
125
123
|
}
|
|
@@ -140,12 +138,28 @@ function getLayoutsForErrorFile(
|
|
|
140
138
|
return layoutComponents.filter((lc) => resolvedSegments.has(lc.segment));
|
|
141
139
|
}
|
|
142
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Convert an error to a SerializableError for RSC Flight serialization.
|
|
143
|
+
* Stack traces are stripped in production (gated by isDevMode).
|
|
144
|
+
*
|
|
145
|
+
* See design/13-security.md §"Debug Flag Security Boundary" — uses isDevMode(),
|
|
146
|
+
* not isDebug(), because this data crosses the RSC → client boundary.
|
|
147
|
+
*/
|
|
148
|
+
function toSerializableError(error: unknown): SerializableError {
|
|
149
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
150
|
+
return {
|
|
151
|
+
message: err.message,
|
|
152
|
+
name: err.name,
|
|
153
|
+
...(isDevMode() && err.stack ? { stack: err.stack } : {}),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
143
157
|
/**
|
|
144
158
|
* Render an error page for unhandled throws or RenderError outside Suspense.
|
|
145
159
|
*
|
|
146
|
-
*
|
|
147
|
-
* - TSX
|
|
148
|
-
* - MDX:
|
|
160
|
+
* All error pages (TSX and MDX) go through RSC → SSR:
|
|
161
|
+
* - TSX: ErrorReconstituter wrapper converts SerializableError → real Error
|
|
162
|
+
* - MDX: Plain props ({ status }), no wrapper needed
|
|
149
163
|
*/
|
|
150
164
|
export async function renderErrorPage(
|
|
151
165
|
error: unknown,
|
|
@@ -158,8 +172,6 @@ export async function renderErrorPage(
|
|
|
158
172
|
clientBootstrap: ClientBootstrapConfig,
|
|
159
173
|
globalError?: GlobalErrorFile
|
|
160
174
|
): Promise<Response> {
|
|
161
|
-
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
162
|
-
|
|
163
175
|
// Walk segments from leaf to root to find the error component
|
|
164
176
|
const resolution = await resolveErrorFile(status, segments);
|
|
165
177
|
|
|
@@ -181,28 +193,14 @@ export async function renderErrorPage(
|
|
|
181
193
|
return new Response(null, { status, headers: responseHeaders });
|
|
182
194
|
}
|
|
183
195
|
|
|
184
|
-
const { component: errorComponent, segmentIndex, isMdx
|
|
196
|
+
const { component: errorComponent, segmentIndex, isMdx } = resolution;
|
|
185
197
|
const layoutsToWrap = getLayoutsForErrorFile(segments, layoutComponents, segmentIndex);
|
|
186
198
|
|
|
187
|
-
|
|
188
|
-
if (isMdx) {
|
|
189
|
-
return renderErrorPageViaMdx(
|
|
190
|
-
errorComponent,
|
|
191
|
-
status,
|
|
192
|
-
layoutsToWrap,
|
|
193
|
-
req,
|
|
194
|
-
match,
|
|
195
|
-
responseHeaders,
|
|
196
|
-
clientBootstrap,
|
|
197
|
-
h
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// TSX error pages: SSR-only render (bypass RSC Flight)
|
|
202
|
-
return renderErrorPageViaSsrOnly(
|
|
199
|
+
return renderErrorPageViaRsc(
|
|
203
200
|
error,
|
|
201
|
+
errorComponent,
|
|
202
|
+
isMdx,
|
|
204
203
|
status,
|
|
205
|
-
filePath,
|
|
206
204
|
layoutsToWrap,
|
|
207
205
|
req,
|
|
208
206
|
match,
|
|
@@ -212,77 +210,47 @@ export async function renderErrorPage(
|
|
|
212
210
|
}
|
|
213
211
|
|
|
214
212
|
/**
|
|
215
|
-
*
|
|
213
|
+
* Render an error page through the RSC → SSR pipeline.
|
|
216
214
|
*
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* serialization constraint on props.
|
|
215
|
+
* Handles both TSX and MDX error pages:
|
|
216
|
+
* - TSX: Wrapped with ErrorReconstituter to reconstitute Error on client
|
|
217
|
+
* - MDX: Receives plain props ({ status }), rendered as server component
|
|
221
218
|
*/
|
|
222
|
-
async function
|
|
219
|
+
async function renderErrorPageViaRsc(
|
|
223
220
|
error: unknown,
|
|
221
|
+
errorComponent: (...args: unknown[]) => unknown,
|
|
222
|
+
isMdx: boolean,
|
|
224
223
|
status: number,
|
|
225
|
-
errorFilePath: string,
|
|
226
224
|
layoutsToWrap: LayoutEntry[],
|
|
227
225
|
req: Request,
|
|
228
226
|
match: RouteMatch,
|
|
229
227
|
responseHeaders: Headers,
|
|
230
228
|
clientBootstrap: ClientBootstrapConfig
|
|
231
229
|
): Promise<Response> {
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
error instanceof RenderError ? { code: error.code, data: error.digest.data } : null;
|
|
235
|
-
|
|
236
|
-
// Collect layout file paths (root → leaf order) for SSR-env import.
|
|
237
|
-
// Layouts have filePaths on their segment's layout ManifestFile.
|
|
238
|
-
const layoutPaths: string[] = [];
|
|
239
|
-
for (const lc of layoutsToWrap) {
|
|
240
|
-
if (lc.segment.layout) {
|
|
241
|
-
layoutPaths.push(lc.segment.layout.filePath);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
230
|
+
const h = createElement as (...args: unknown[]) => React.ReactElement;
|
|
231
|
+
const url = new URL(req.url);
|
|
244
232
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
233
|
+
let element: React.ReactElement;
|
|
234
|
+
|
|
235
|
+
if (isMdx) {
|
|
236
|
+
// MDX error pages receive plain props — no Error serialization issue
|
|
237
|
+
element = h(errorComponent, { status });
|
|
238
|
+
} else {
|
|
239
|
+
// TSX error pages: wrap with ErrorReconstituter for Error reconstitution.
|
|
240
|
+
// The ErrorReconstituter is a 'use client' component that receives
|
|
241
|
+
// SerializableError (plain object) and reconstitutes a real Error
|
|
242
|
+
// before passing to the user's error component.
|
|
243
|
+
const serializedError = toSerializableError(error);
|
|
244
|
+
const digest =
|
|
245
|
+
error instanceof RenderError ? { code: error.code, data: error.digest.data } : null;
|
|
246
|
+
|
|
247
|
+
element = h(ErrorReconstituter, {
|
|
248
|
+
error: serializedError,
|
|
262
249
|
digest,
|
|
263
250
|
reset: undefined,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* MDX error page: RSC → SSR render. MDX is a server component and needs
|
|
272
|
-
* the Flight pipeline, but it receives plain props (no Error object).
|
|
273
|
-
*/
|
|
274
|
-
async function renderErrorPageViaMdx(
|
|
275
|
-
errorComponent: (...args: unknown[]) => unknown,
|
|
276
|
-
status: number,
|
|
277
|
-
layoutsToWrap: LayoutEntry[],
|
|
278
|
-
req: Request,
|
|
279
|
-
match: RouteMatch,
|
|
280
|
-
responseHeaders: Headers,
|
|
281
|
-
clientBootstrap: ClientBootstrapConfig,
|
|
282
|
-
h: (...args: unknown[]) => React.ReactElement
|
|
283
|
-
): Promise<Response> {
|
|
284
|
-
// MDX error pages receive {} or { status } — no Error object
|
|
285
|
-
let element = h(errorComponent, { status });
|
|
251
|
+
component: errorComponent,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
286
254
|
|
|
287
255
|
// Wrap in layouts
|
|
288
256
|
for (let i = layoutsToWrap.length - 1; i >= 0; i--) {
|
|
@@ -290,39 +258,43 @@ async function renderErrorPageViaMdx(
|
|
|
290
258
|
element = h(component, null, element);
|
|
291
259
|
}
|
|
292
260
|
|
|
293
|
-
// Render through RSC → SSR (same as deny pages)
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const navContext: NavContext = {
|
|
304
|
-
pathname: new URL(req.url).pathname,
|
|
305
|
-
params: match.params,
|
|
306
|
-
searchParams: Object.fromEntries(new URL(req.url).searchParams),
|
|
307
|
-
statusCode: status,
|
|
308
|
-
responseHeaders,
|
|
309
|
-
headHtml: flightInitScript(),
|
|
310
|
-
bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
|
|
311
|
-
rscStream: inlineStream,
|
|
312
|
-
cookies: getCookiesForSsr(),
|
|
313
|
-
};
|
|
261
|
+
// Render through RSC → SSR (same path as deny pages)
|
|
262
|
+
try {
|
|
263
|
+
const rscStream = renderToReadableStream(element, {
|
|
264
|
+
onError(err: unknown) {
|
|
265
|
+
logRenderError({ method: req.method, path: url.pathname, error: err });
|
|
266
|
+
},
|
|
267
|
+
debugChannel: createDebugChannelSink(),
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const [ssrStream, inlineStream] = teeWithErrorPropagation(rscStream);
|
|
314
271
|
|
|
315
|
-
|
|
272
|
+
const navContext: NavContext = {
|
|
273
|
+
pathname: url.pathname,
|
|
274
|
+
params: match.params,
|
|
275
|
+
searchParams: Object.fromEntries(url.searchParams),
|
|
276
|
+
statusCode: status,
|
|
277
|
+
responseHeaders,
|
|
278
|
+
headHtml: flightInitScript(),
|
|
279
|
+
bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
|
|
280
|
+
rscStream: inlineStream,
|
|
281
|
+
cookies: getCookiesForSsr(),
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
return callSsr(ssrStream, navContext);
|
|
285
|
+
} catch (renderError) {
|
|
286
|
+
// Error page itself failed to render — fall through to bare response.
|
|
287
|
+
// This guards against recursive errors in user error page code.
|
|
288
|
+
logRenderError({ method: req.method, path: url.pathname, error: renderError });
|
|
289
|
+
return new Response(null, { status, headers: responseHeaders });
|
|
290
|
+
}
|
|
316
291
|
}
|
|
317
292
|
|
|
318
293
|
/**
|
|
319
294
|
* Tier 2 — Render global-error.tsx as a standalone full-page replacement.
|
|
320
295
|
*
|
|
321
296
|
* No layout wrapping. The component must provide its own <html> and <body>.
|
|
322
|
-
*
|
|
323
|
-
* a 'use client' component receiving { error, digest, reset }.
|
|
324
|
-
*
|
|
325
|
-
* MDX global-error files go through RSC → SSR with plain props.
|
|
297
|
+
* All formats (TSX and MDX) go through RSC → SSR.
|
|
326
298
|
*
|
|
327
299
|
* See design/10-error-handling.md §"Tier 2 — Global Error Page"
|
|
328
300
|
*/
|
|
@@ -335,63 +307,26 @@ async function renderGlobalErrorPage(
|
|
|
335
307
|
responseHeaders: Headers,
|
|
336
308
|
clientBootstrap: ClientBootstrapConfig
|
|
337
309
|
): Promise<Response> {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
// MDX global-error: RSC → SSR with plain props (no Error object)
|
|
342
|
-
const mod = (await globalError.load()) as Record<string, unknown>;
|
|
343
|
-
if (!mod.default) {
|
|
344
|
-
return new Response(null, { status, headers: responseHeaders });
|
|
345
|
-
}
|
|
346
|
-
const component = mod.default as (...args: unknown[]) => unknown;
|
|
347
|
-
const element = h(component, { status });
|
|
348
|
-
|
|
349
|
-
const rscStream = renderToReadableStream(element, {
|
|
350
|
-
onError(err: unknown) {
|
|
351
|
-
logRenderError({ method: req.method, path: new URL(req.url).pathname, error: err });
|
|
352
|
-
},
|
|
353
|
-
debugChannel: createDebugChannelSink(),
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
const [ssrStream, inlineStream] = rscStream.tee();
|
|
357
|
-
|
|
358
|
-
const navContext: NavContext = {
|
|
359
|
-
pathname: new URL(req.url).pathname,
|
|
360
|
-
params: match.params,
|
|
361
|
-
searchParams: Object.fromEntries(new URL(req.url).searchParams),
|
|
362
|
-
statusCode: status,
|
|
363
|
-
responseHeaders,
|
|
364
|
-
headHtml: flightInitScript(),
|
|
365
|
-
bootstrapScriptContent: clientBootstrap.bootstrapScriptContent,
|
|
366
|
-
rscStream: inlineStream,
|
|
367
|
-
cookies: getCookiesForSsr(),
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
return callSsr(ssrStream, navContext);
|
|
310
|
+
const mod = (await globalError.load()) as Record<string, unknown>;
|
|
311
|
+
if (!mod.default) {
|
|
312
|
+
return new Response(null, { status, headers: responseHeaders });
|
|
371
313
|
}
|
|
372
314
|
|
|
373
|
-
|
|
374
|
-
const
|
|
375
|
-
error instanceof RenderError ? { code: error.code, data: error.digest.data } : null;
|
|
315
|
+
const component = mod.default as (...args: unknown[]) => unknown;
|
|
316
|
+
const isMdx = isMdxFile(globalError.filePath);
|
|
376
317
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
318
|
+
// Reuse the unified RSC path with no layouts
|
|
319
|
+
return renderErrorPageViaRsc(
|
|
320
|
+
error,
|
|
321
|
+
component,
|
|
322
|
+
isMdx,
|
|
323
|
+
status,
|
|
324
|
+
[], // No layouts — global-error is standalone
|
|
325
|
+
req,
|
|
326
|
+
match,
|
|
382
327
|
responseHeaders,
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
signal: req.signal,
|
|
386
|
-
cookies: getCookiesForSsr(),
|
|
387
|
-
errorProps: {
|
|
388
|
-
error: error instanceof Error ? error : new Error(String(error)),
|
|
389
|
-
digest,
|
|
390
|
-
reset: undefined,
|
|
391
|
-
},
|
|
392
|
-
errorComponentPath: globalError.filePath,
|
|
393
|
-
layoutPaths: [], // No layouts — global-error is standalone
|
|
394
|
-
});
|
|
328
|
+
clientBootstrap
|
|
329
|
+
);
|
|
395
330
|
}
|
|
396
331
|
|
|
397
332
|
/**
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Small, stateless functions used across the RSC entry modules.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ManifestSegmentNode } from '
|
|
8
|
-
import { RedirectSignal } from '
|
|
7
|
+
import type { ManifestSegmentNode } from '../route-matcher.js';
|
|
8
|
+
import { RedirectSignal } from '../primitives.js';
|
|
9
9
|
|
|
10
10
|
/** RSC content type for client navigation payload requests. */
|
|
11
11
|
export const RSC_CONTENT_TYPE = 'text/x-component';
|
|
@@ -24,38 +24,38 @@ import buildManifest from 'virtual:timber-build-manifest';
|
|
|
24
24
|
// @ts-expect-error — virtual module provided by timber-entries plugin
|
|
25
25
|
import loadUserInstrumentation from 'virtual:timber-instrumentation';
|
|
26
26
|
|
|
27
|
-
import type { FormRerender } from '
|
|
28
|
-
import { handleActionRequest, isActionRequest } from '
|
|
29
|
-
import type { BodyLimitsConfig } from '
|
|
30
|
-
import type { BuildManifest } from '
|
|
27
|
+
import type { FormRerender } from '../action-handler.js';
|
|
28
|
+
import { handleActionRequest, isActionRequest } from '../action-handler.js';
|
|
29
|
+
import type { BodyLimitsConfig } from '../body-limits.js';
|
|
30
|
+
import type { BuildManifest } from '../build-manifest.js';
|
|
31
31
|
import {
|
|
32
32
|
buildFontPreloadTags,
|
|
33
33
|
buildModulepreloadTags,
|
|
34
34
|
collectRouteFonts,
|
|
35
35
|
collectRouteModulepreloads,
|
|
36
|
-
} from '
|
|
37
|
-
import type { LayoutEntry } from '
|
|
38
|
-
import { renderDenyPage, renderDenyPageAsRsc } from '
|
|
39
|
-
import { resolveLogMode } from '
|
|
40
|
-
import { sendEarlyHints103 } from '
|
|
41
|
-
import { collectEarlyHintHeaders } from '
|
|
42
|
-
import { runWithFormFlash } from '
|
|
43
|
-
import type { ClientBootstrapConfig } from '
|
|
44
|
-
import { buildClientScripts } from '
|
|
45
|
-
import type { InterceptionContext, PipelineConfig, RouteMatch } from '
|
|
46
|
-
import { createPipeline, coerceSegmentParams } from '
|
|
47
|
-
import { DenySignal, RedirectSignal } from '
|
|
36
|
+
} from '../build-manifest.js';
|
|
37
|
+
import type { LayoutEntry } from '../deny-renderer.js';
|
|
38
|
+
import { renderDenyPage, renderDenyPageAsRsc } from '../deny-renderer.js';
|
|
39
|
+
import { resolveLogMode } from '../dev-logger.js';
|
|
40
|
+
import { sendEarlyHints103 } from '../early-hints-sender.js';
|
|
41
|
+
import { collectEarlyHintHeaders } from '../early-hints.js';
|
|
42
|
+
import { runWithFormFlash } from '../form-flash.js';
|
|
43
|
+
import type { ClientBootstrapConfig } from '../html-injectors.js';
|
|
44
|
+
import { buildClientScripts } from '../html-injectors.js';
|
|
45
|
+
import type { InterceptionContext, PipelineConfig, RouteMatch } from '../pipeline.js';
|
|
46
|
+
import { createPipeline, coerceSegmentParams } from '../pipeline.js';
|
|
47
|
+
import { DenySignal, RedirectSignal } from '../primitives.js';
|
|
48
48
|
import {
|
|
49
49
|
buildRouteElement,
|
|
50
50
|
RouteSignalWithContext,
|
|
51
51
|
ParamCoercionError,
|
|
52
|
-
} from '
|
|
53
|
-
import type { ManifestSegmentNode } from '
|
|
54
|
-
import { createMetadataRouteMatcher, createRouteMatcher } from '
|
|
55
|
-
import { initDevTracing } from '
|
|
52
|
+
} from '../route-element-builder.js';
|
|
53
|
+
import type { ManifestSegmentNode } from '../route-matcher.js';
|
|
54
|
+
import { createMetadataRouteMatcher, createRouteMatcher } from '../route-matcher.js';
|
|
55
|
+
import { initDevTracing } from '../tracing.js';
|
|
56
56
|
|
|
57
|
-
import { renderFallbackError as renderFallback } from '
|
|
58
|
-
import { loadInstrumentation } from '
|
|
57
|
+
import { renderFallbackError as renderFallback } from '../fallback-error.js';
|
|
58
|
+
import { loadInstrumentation } from '../instrumentation.js';
|
|
59
59
|
import { handleApiRoute } from './api-handler.js';
|
|
60
60
|
import { renderErrorPage, renderNoMatchPage } from './error-renderer.js';
|
|
61
61
|
import {
|
|
@@ -65,13 +65,14 @@ import {
|
|
|
65
65
|
isRscPayloadRequest,
|
|
66
66
|
type DebugComponentEntry,
|
|
67
67
|
} from './helpers.js';
|
|
68
|
-
import { parseClientStateTree } from '
|
|
68
|
+
import { parseClientStateTree } from '../state-tree-diff.js';
|
|
69
69
|
import { buildRscPayloadResponse } from './rsc-payload.js';
|
|
70
70
|
import { renderRscStream } from './rsc-stream.js';
|
|
71
71
|
import { renderSsrResponse } from './ssr-renderer.js';
|
|
72
72
|
import { callSsr } from './ssr-bridge.js';
|
|
73
|
-
import { isDebug, isDevMode, setDebugFromConfig } from '
|
|
74
|
-
import { recordTiming } from '
|
|
73
|
+
import { isDebug, isDevMode, setDebugFromConfig } from '../debug.js';
|
|
74
|
+
import { recordTiming } from '../server-timing.js';
|
|
75
|
+
import { requestContextAls } from '../als-registry.js';
|
|
75
76
|
|
|
76
77
|
/**
|
|
77
78
|
* Resolve the Server-Timing mode from timber.config.ts.
|
|
@@ -113,11 +114,9 @@ export function setDevPipelineErrorHandler(
|
|
|
113
114
|
_devPipelineErrorHandler = handler;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
|
-
// Dev-only:
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
// scoped by convention — each renderRoute call sets it before returning.
|
|
120
|
-
let _lastDebugComponentsGetter: (() => DebugComponentEntry[]) | undefined;
|
|
117
|
+
// Dev-only: debug components getter is stored per-request in ALS
|
|
118
|
+
// (RequestContextStore.debugComponentsGetter) to avoid cross-request
|
|
119
|
+
// race conditions. See TIM-557.
|
|
121
120
|
|
|
122
121
|
/**
|
|
123
122
|
* Create the RSC request handler from the route manifest.
|
|
@@ -139,7 +138,7 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
139
138
|
| string
|
|
140
139
|
| undefined;
|
|
141
140
|
if (deploymentId) {
|
|
142
|
-
const { setDeploymentId } = await import('
|
|
141
|
+
const { setDeploymentId } = await import('../version-skew.js');
|
|
143
142
|
setDeploymentId(deploymentId);
|
|
144
143
|
}
|
|
145
144
|
|
|
@@ -251,9 +250,10 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
251
250
|
if (_devPipelineErrorHandler) {
|
|
252
251
|
// For render-phase errors, include RSC debug component data
|
|
253
252
|
// from the Flight debug channel (if available from the current request).
|
|
253
|
+
const store = requestContextAls.getStore();
|
|
254
254
|
const debugComponents =
|
|
255
|
-
phase === 'render' &&
|
|
256
|
-
?
|
|
255
|
+
phase === 'render' && store?.debugComponentsGetter
|
|
256
|
+
? store.debugComponentsGetter()
|
|
257
257
|
: undefined;
|
|
258
258
|
_devPipelineErrorHandler(error, phase, debugComponents);
|
|
259
259
|
}
|
|
@@ -487,9 +487,13 @@ async function renderRoute(
|
|
|
487
487
|
const _rscStart = performance.now();
|
|
488
488
|
const { rscStream, signals, getDebugComponents } = renderRscStream(element, _req);
|
|
489
489
|
|
|
490
|
-
// Store the debug components getter so onPipelineError can
|
|
491
|
-
// component tree context for render-phase errors (dev mode only).
|
|
492
|
-
|
|
490
|
+
// Store the debug components getter in ALS so onPipelineError can
|
|
491
|
+
// include component tree context for render-phase errors (dev mode only).
|
|
492
|
+
// Per-request via ALS — no cross-request race. See TIM-557.
|
|
493
|
+
const alsStore = requestContextAls.getStore();
|
|
494
|
+
if (alsStore) {
|
|
495
|
+
alsStore.debugComponentsGetter = getDebugComponents;
|
|
496
|
+
}
|
|
493
497
|
recordTiming({
|
|
494
498
|
name: 'rsc-init',
|
|
495
499
|
dur: Math.round(performance.now() - _rscStart),
|
|
@@ -580,10 +584,10 @@ async function renderRoute(
|
|
|
580
584
|
|
|
581
585
|
// Re-export for generated entry points (e.g., Nitro node-server/bun) to wrap
|
|
582
586
|
// the handler with per-request 103 Early Hints sender via ALS.
|
|
583
|
-
export { runWithEarlyHintsSender } from '
|
|
587
|
+
export { runWithEarlyHintsSender } from '../early-hints-sender.js';
|
|
584
588
|
|
|
585
589
|
// Re-export for generated entry points to wrap the handler with per-request
|
|
586
590
|
// waitUntil support via ALS. See design/11-platform.md §"waitUntil()".
|
|
587
|
-
export { runWithWaitUntil } from '
|
|
591
|
+
export { runWithWaitUntil } from '../waituntil-bridge.js';
|
|
588
592
|
|
|
589
593
|
export default await createRequestHandler(routeManifest, config);
|
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* 16-metadata.md §"Head Elements"
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import type { LayoutEntry } from '
|
|
13
|
-
import { renderDenyPageAsRsc } from '
|
|
14
|
-
import type { RouteMatch } from '
|
|
15
|
-
import type { RedirectSignal } from '
|
|
16
|
-
import type { HeadElement, LayoutComponentEntry } from '
|
|
17
|
-
import type { ManifestSegmentNode } from '
|
|
12
|
+
import type { LayoutEntry } from '../deny-renderer.js';
|
|
13
|
+
import { renderDenyPageAsRsc } from '../deny-renderer.js';
|
|
14
|
+
import type { RouteMatch } from '../pipeline.js';
|
|
15
|
+
import type { RedirectSignal } from '../primitives.js';
|
|
16
|
+
import type { HeadElement, LayoutComponentEntry } from '../route-element-builder.js';
|
|
17
|
+
import type { ManifestSegmentNode } from '../route-matcher.js';
|
|
18
18
|
|
|
19
19
|
import {
|
|
20
20
|
buildRedirectResponse,
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
* 13-security.md §"Errors don't leak"
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { renderToReadableStream } from '
|
|
13
|
+
import { renderToReadableStream } from '../../rsc-runtime/rsc.js';
|
|
14
14
|
|
|
15
15
|
import { randomUUID } from 'node:crypto';
|
|
16
16
|
|
|
17
|
-
import { logRenderError } from '
|
|
18
|
-
import { DenySignal, RedirectSignal, RenderError } from '
|
|
19
|
-
import { checkAndWarnRscPropError } from '
|
|
17
|
+
import { logRenderError } from '../logger.js';
|
|
18
|
+
import { DenySignal, RedirectSignal, RenderError } from '../primitives.js';
|
|
19
|
+
import { checkAndWarnRscPropError } from '../rsc-prop-warnings.js';
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
22
|
createDebugChannelSink,
|
|
@@ -24,8 +24,8 @@ import {
|
|
|
24
24
|
isAbortError,
|
|
25
25
|
type DebugComponentEntry,
|
|
26
26
|
} from './helpers.js';
|
|
27
|
-
import { isDebug } from '
|
|
28
|
-
import { isDevMode } from '
|
|
27
|
+
import { isDebug } from '../debug.js';
|
|
28
|
+
import { isDevMode } from '../debug.js';
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Mutable signal state captured during RSC rendering.
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
/// <reference types="@vitejs/plugin-rsc/types" />
|
|
6
6
|
|
|
7
|
-
import type { NavContext } from '
|
|
7
|
+
import type { NavContext } from '../ssr-entry.js';
|
|
8
8
|
|
|
9
9
|
export async function callSsr(
|
|
10
10
|
rscStream: ReadableStream<Uint8Array>,
|
|
11
11
|
navContext: NavContext
|
|
12
12
|
): Promise<Response> {
|
|
13
|
-
const ssrEntry = await import.meta.viteRsc.import<typeof import('
|
|
13
|
+
const ssrEntry = await import.meta.viteRsc.import<typeof import('../ssr-entry.js')>(
|
|
14
14
|
'../ssr-entry.js',
|
|
15
15
|
{ environment: 'ssr' }
|
|
16
16
|
);
|