@timber-js/app 0.2.0-alpha.204 → 0.2.0-alpha.206
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/{actions-HUdJADAD.js → actions-jNrXdzgs.js} +4 -3
- package/dist/_chunks/{actions-HUdJADAD.js.map → actions-jNrXdzgs.js.map} +1 -1
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
- package/dist/_chunks/{build-manifest-DWppEdLB.js → build-manifest-DTmSGLRz.js} +51 -2
- package/dist/_chunks/build-manifest-DTmSGLRz.js.map +1 -0
- package/dist/_chunks/{cache-api-CAPbZTga.js → cache-api-CUD9Ezaq.js} +3 -2
- package/dist/_chunks/{cache-api-CAPbZTga.js.map → cache-api-CUD9Ezaq.js.map} +1 -1
- package/dist/_chunks/{chains-C9Y7Oyno.js → chains-B4bgOl2g.js} +2 -2
- package/dist/_chunks/{chains-C9Y7Oyno.js.map → chains-B4bgOl2g.js.map} +1 -1
- package/dist/_chunks/{cli-check-BzMGuIH6.js → cli-check-BqOylm-3.js} +3 -3
- package/dist/_chunks/{cli-check-BzMGuIH6.js.map → cli-check-BqOylm-3.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-DnXqcIIj.js → cli-schema-sync--LZKs8Zn.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-DnXqcIIj.js.map → cli-schema-sync--LZKs8Zn.js.map} +1 -1
- package/dist/_chunks/{cloudflare-DxX1SU0g.js → cloudflare-BFb__LYG.js} +3 -3
- package/dist/_chunks/{cloudflare-DxX1SU0g.js.map → cloudflare-BFb__LYG.js.map} +1 -1
- package/dist/_chunks/{convention-lint-DHOFvX5s.js → convention-lint-BnO5TyHD.js} +2 -2
- package/dist/_chunks/{convention-lint-DHOFvX5s.js.map → convention-lint-BnO5TyHD.js.map} +1 -1
- package/dist/_chunks/{dev-server-BV01iyUs.js → dev-server-BzBxgYAV.js} +9 -196
- package/dist/_chunks/dev-server-BzBxgYAV.js.map +1 -0
- package/dist/_chunks/{error-boundary-BQKxl6EX.js → error-boundary-BndF-3Td.js} +2 -2
- package/dist/_chunks/{error-boundary-BQKxl6EX.js.map → error-boundary-BndF-3Td.js.map} +1 -1
- package/dist/_chunks/{graph-cache-Cv3njEH8.js → graph-cache-CP4GEmf9.js} +2 -2
- package/dist/_chunks/{graph-cache-Cv3njEH8.js.map → graph-cache-CP4GEmf9.js.map} +1 -1
- package/dist/_chunks/{live-graph-DCLQzV6m.js → live-graph-Bm5WTvcr.js} +3 -3
- package/dist/_chunks/{live-graph-DCLQzV6m.js.map → live-graph-Bm5WTvcr.js.map} +1 -1
- package/dist/_chunks/logger-k2DQ4EUf.js +506 -0
- package/dist/_chunks/logger-k2DQ4EUf.js.map +1 -0
- package/dist/_chunks/{poison-scan-CX_NaNDi.js → poison-scan-DG5eI2_z.js} +2 -2
- package/dist/_chunks/{poison-scan-CX_NaNDi.js.map → poison-scan-DG5eI2_z.js.map} +1 -1
- package/dist/_chunks/{logger-DqJ2VoAY.js → primitives-DLAnvsrk.js} +17 -518
- package/dist/_chunks/primitives-DLAnvsrk.js.map +1 -0
- package/dist/_chunks/{scanner-B_tnqFcF.js → scanner-BQ5sPIJ7.js} +4 -4
- package/dist/_chunks/{scanner-B_tnqFcF.js.map → scanner-BQ5sPIJ7.js.map} +1 -1
- package/dist/_chunks/{tree-match-CdbvYTBz.js → tree-match-6yihorhk.js} +49 -2
- package/dist/_chunks/tree-match-6yihorhk.js.map +1 -0
- package/dist/_chunks/{walkers-Cm3PC5JT.js → walkers-CN2N_bA-.js} +2 -2
- package/dist/_chunks/{walkers-Cm3PC5JT.js.map → walkers-CN2N_bA-.js.map} +1 -1
- package/dist/adapters/cloudflare-dev.js +1 -1
- package/dist/adapters/cloudflare-kv-cache.js +1 -1
- package/dist/adapters/cloudflare.js +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +3 -3
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +3 -3
- package/dist/client/browser-entry/rsc-stream.d.ts.map +1 -1
- package/dist/client/error-boundary.js +1 -1
- package/dist/client/internal.js +1 -1
- package/dist/dev-tools/index.d.ts +1 -1
- package/dist/dev-tools/index.d.ts.map +1 -1
- package/dist/dev-tools/overlay.d.ts +1 -21
- package/dist/dev-tools/overlay.d.ts.map +1 -1
- package/dist/index.js +6 -7
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/server/als-registry.d.ts +0 -7
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts +2 -6
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/index.js +3 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.js +180 -7
- package/dist/server/internal.js.map +1 -1
- package/dist/server/pipeline.d.ts +1 -13
- package/dist/server/pipeline.d.ts.map +1 -1
- package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/helpers.d.ts +0 -38
- package/dist/server/rsc-entry/helpers.d.ts.map +1 -1
- package/dist/server/rsc-entry/index.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts +0 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +1 -7
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts +0 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/slot-subtree-contain.d.ts +1 -1
- package/dist/server/slot-subtree-contain.d.ts.map +1 -1
- package/dist/server/tree-match.d.ts +10 -0
- package/dist/server/tree-match.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client/browser-entry/rsc-stream.ts +1 -82
- package/src/dev-tools/index.ts +0 -2
- package/src/dev-tools/overlay.ts +1 -65
- package/src/plugins/dev-server.ts +4 -45
- package/src/server/als-registry.ts +0 -8
- package/src/server/deny-renderer.ts +0 -7
- package/src/server/pipeline.ts +1 -14
- package/src/server/route-matcher.ts +73 -20
- package/src/server/rsc-entry/deny-fallback.ts +1 -10
- package/src/server/rsc-entry/error-renderer.ts +1 -11
- package/src/server/rsc-entry/helpers.ts +0 -92
- package/src/server/rsc-entry/index.ts +1 -14
- package/src/server/rsc-entry/render-route.ts +2 -23
- package/src/server/rsc-entry/rsc-payload.ts +1 -2
- package/src/server/rsc-entry/rsc-stream.ts +3 -29
- package/src/server/rsc-entry/ssr-renderer.ts +2 -15
- package/src/server/slot-resolver.ts +7 -29
- package/src/server/slot-subtree-contain.ts +22 -14
- package/src/server/tree-match.ts +250 -18
- package/dist/_chunks/build-manifest-DWppEdLB.js.map +0 -1
- package/dist/_chunks/csp-nonce-hOGniaG4.js +0 -227
- package/dist/_chunks/csp-nonce-hOGniaG4.js.map +0 -1
- package/dist/_chunks/dev-server-BV01iyUs.js.map +0 -1
- package/dist/_chunks/logger-DqJ2VoAY.js.map +0 -1
- package/dist/_chunks/tree-match-CdbvYTBz.js.map +0 -1
- package/dist/dev-tools/debug-channel.d.ts +0 -55
- package/dist/dev-tools/debug-channel.d.ts.map +0 -1
- package/src/dev-tools/debug-channel.ts +0 -151
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
resolveServePathForFile,
|
|
19
19
|
type MetadataRouteType,
|
|
20
20
|
} from './metadata-routes.ts';
|
|
21
|
-
import { matchUrlParts } from './tree-match.ts';
|
|
21
|
+
import { matchUrlParts, matchUrlPartsAll } from './tree-match.ts';
|
|
22
22
|
|
|
23
23
|
// ─── Manifest Types ───────────────────────────────────────────────────────
|
|
24
24
|
//
|
|
@@ -190,30 +190,44 @@ function matchMetadataPathname(
|
|
|
190
190
|
|
|
191
191
|
let segment: ManifestSegmentNode;
|
|
192
192
|
let params: Record<string, string | string[]>;
|
|
193
|
+
let file: ManifestFile | null = null;
|
|
193
194
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
195
|
+
// Find the metadata file owner. For prefix matches, iterate all matching
|
|
196
|
+
// segments in priority order — when competing bare-dynamic groups match
|
|
197
|
+
// the same prefix, the first may not own the file. For root-level matches,
|
|
198
|
+
// search all group children since findMetadataRouteInSegment may return
|
|
199
|
+
// a file with the wrong extension when multiple groups own the same
|
|
200
|
+
// convention with different static types.
|
|
201
|
+
segment = root;
|
|
202
|
+
params = {};
|
|
203
|
+
const candidates = [...metadataOwnerCandidates(root, prefixParts)];
|
|
203
204
|
|
|
204
|
-
//
|
|
205
|
-
|
|
205
|
+
// Two passes: direct owners first (node itself + groups), then
|
|
206
|
+
// zero-segment optional-catch-all descent. A direct owner across any
|
|
207
|
+
// candidate must beat a zero-segment catch-all on an earlier candidate.
|
|
208
|
+
for (const allowCatchAll of [false, true]) {
|
|
209
|
+
for (const candidate of candidates) {
|
|
210
|
+
if (!convention.nestable && candidate.segment !== root) continue;
|
|
211
|
+
const found = findMetadataRouteInSegment(
|
|
212
|
+
candidate.segment,
|
|
213
|
+
baseName,
|
|
214
|
+
allowCatchAll && convention.nestable
|
|
215
|
+
);
|
|
216
|
+
if (!found) continue;
|
|
217
|
+
if (convention.serveExtension) {
|
|
218
|
+
const resolvedServePath = resolveServePathForFile(baseName, found.filePath);
|
|
219
|
+
if (lastPart !== resolvedServePath) continue;
|
|
220
|
+
}
|
|
221
|
+
segment = candidate.segment;
|
|
222
|
+
params = candidate.params;
|
|
223
|
+
file = found;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
if (file) break;
|
|
227
|
+
}
|
|
206
228
|
|
|
207
|
-
const file = findMetadataRouteInSegment(segment, baseName, convention.nestable);
|
|
208
229
|
if (!file) return null;
|
|
209
230
|
|
|
210
|
-
// Validate the requested URL extension matches the file's resolved serve path.
|
|
211
|
-
// Prevents /icon.svg matching when only icon.png exists.
|
|
212
|
-
if (convention.serveExtension) {
|
|
213
|
-
const resolvedServePath = resolveServePathForFile(baseName, file.filePath);
|
|
214
|
-
if (lastPart !== resolvedServePath) return null;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
231
|
const ext = file.filePath.slice(file.filePath.lastIndexOf('.') + 1);
|
|
218
232
|
const isStatic = isStaticMetadataExtension(baseName, ext);
|
|
219
233
|
const contentType = isStatic
|
|
@@ -265,3 +279,42 @@ function findMetadataRouteInSegment(
|
|
|
265
279
|
}
|
|
266
280
|
return null;
|
|
267
281
|
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Yield candidate owner segments for a metadata route.
|
|
285
|
+
*
|
|
286
|
+
* For root-level metadata (empty prefix), yields the root and then each
|
|
287
|
+
* transparent group child — so extension validation can skip a group that
|
|
288
|
+
* owns the wrong static type and find the right one. For prefix matches,
|
|
289
|
+
* delegates to `matchUrlPartsAll` which handles all segment kinds.
|
|
290
|
+
*/
|
|
291
|
+
function* metadataOwnerCandidates(
|
|
292
|
+
root: ManifestSegmentNode,
|
|
293
|
+
prefixParts: string[]
|
|
294
|
+
): Generator<{ segment: ManifestSegmentNode; params: Record<string, string | string[]> }> {
|
|
295
|
+
if (prefixParts.length === 0) {
|
|
296
|
+
yield { segment: root, params: {} };
|
|
297
|
+
// Also yield each group child individually so the caller can validate
|
|
298
|
+
// extensions per-group rather than taking the first file found.
|
|
299
|
+
for (const child of root.children) {
|
|
300
|
+
if (child.segmentType === 'group') {
|
|
301
|
+
yield* groupDescendants(child);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
for (const result of matchUrlPartsAll(root, prefixParts, { requireTerminal: false })) {
|
|
306
|
+
yield { segment: result.chain[result.chain.length - 1], params: result.params };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function* groupDescendants(
|
|
312
|
+
node: ManifestSegmentNode
|
|
313
|
+
): Generator<{ segment: ManifestSegmentNode; params: Record<string, string | string[]> }> {
|
|
314
|
+
yield { segment: node, params: {} };
|
|
315
|
+
for (const child of node.children) {
|
|
316
|
+
if (child.segmentType === 'group') {
|
|
317
|
+
yield* groupDescendants(child);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
@@ -17,7 +17,6 @@ import { logRenderError } from '../logger.ts';
|
|
|
17
17
|
import type { PipelineConfig, RouteMatch } from '../pipeline.ts';
|
|
18
18
|
import type { ManifestSegmentNode } from '../route-matcher.ts';
|
|
19
19
|
import { loadModule } from '../safe-load.ts';
|
|
20
|
-
import { createDebugChannelSink } from './helpers.ts';
|
|
21
20
|
import { isRscRequest } from '../../shared/rsc-media-type.ts';
|
|
22
21
|
import { callSsr } from './ssr-bridge.ts';
|
|
23
22
|
|
|
@@ -69,14 +68,7 @@ export function createDenyFallbackRenderer(options: {
|
|
|
69
68
|
// request is an RSC payload request — mirroring the normal render path.
|
|
70
69
|
// See design/19-client-navigation.md §"RSC Payload Handling" and TIM-823.
|
|
71
70
|
if (isRscRequest(req)) {
|
|
72
|
-
return renderDenyPageAsRsc(
|
|
73
|
-
deny,
|
|
74
|
-
chain,
|
|
75
|
-
layoutComponents,
|
|
76
|
-
req,
|
|
77
|
-
responseHeaders,
|
|
78
|
-
createDebugChannelSink
|
|
79
|
-
);
|
|
71
|
+
return renderDenyPageAsRsc(deny, chain, layoutComponents, req, responseHeaders);
|
|
80
72
|
}
|
|
81
73
|
return renderDenyPage(
|
|
82
74
|
deny,
|
|
@@ -86,7 +78,6 @@ export function createDenyFallbackRenderer(options: {
|
|
|
86
78
|
match,
|
|
87
79
|
responseHeaders,
|
|
88
80
|
clientBootstrap,
|
|
89
|
-
createDebugChannelSink,
|
|
90
81
|
callSsr
|
|
91
82
|
);
|
|
92
83
|
};
|
|
@@ -23,7 +23,6 @@ import { getCspNonce } from '../csp-nonce.ts';
|
|
|
23
23
|
import { renderDenyPage, renderDenyPageAsRsc } from '../deny-renderer.ts';
|
|
24
24
|
import type { DefaultPageRenderer, LayoutEntry } from '../deny-renderer.ts';
|
|
25
25
|
import type { NavContext } from '../ssr-bridge-types.ts';
|
|
26
|
-
import { createDebugChannelSink } from './helpers.ts';
|
|
27
26
|
import { isRscRequest } from '../../shared/rsc-media-type.ts';
|
|
28
27
|
import { withPublishedParams } from '../publish-params.ts';
|
|
29
28
|
import { getCookiesForSsr } from '../cookie-context.ts';
|
|
@@ -291,7 +290,6 @@ async function renderErrorPageViaRsc(
|
|
|
291
290
|
onError(err: unknown) {
|
|
292
291
|
logRenderError({ method: req.method, path: url.pathname, error: err });
|
|
293
292
|
},
|
|
294
|
-
debugChannel: createDebugChannelSink(),
|
|
295
293
|
});
|
|
296
294
|
|
|
297
295
|
const [ssrStream, inlineStream] = teeWithErrorPropagation(rscStream);
|
|
@@ -402,14 +400,7 @@ export async function renderNoMatchPage(
|
|
|
402
400
|
const match: RouteMatch = { segments, segmentParams: {}, middlewareChain: [] };
|
|
403
401
|
|
|
404
402
|
if (isRscRequest(req)) {
|
|
405
|
-
return renderDenyPageAsRsc(
|
|
406
|
-
deny,
|
|
407
|
-
segments,
|
|
408
|
-
layoutComponents,
|
|
409
|
-
req,
|
|
410
|
-
responseHeaders,
|
|
411
|
-
createDebugChannelSink
|
|
412
|
-
);
|
|
403
|
+
return renderDenyPageAsRsc(deny, segments, layoutComponents, req, responseHeaders);
|
|
413
404
|
}
|
|
414
405
|
|
|
415
406
|
return renderDenyPage(
|
|
@@ -420,7 +411,6 @@ export async function renderNoMatchPage(
|
|
|
420
411
|
match,
|
|
421
412
|
responseHeaders,
|
|
422
413
|
clientBootstrap,
|
|
423
|
-
createDebugChannelSink,
|
|
424
414
|
callSsr,
|
|
425
415
|
defaultPage
|
|
426
416
|
);
|
|
@@ -5,104 +5,12 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { ManifestSegmentNode } from '../route-matcher.ts';
|
|
8
|
-
import { swallow } from '../logger.ts';
|
|
9
8
|
import { computeSegmentKeys, computeSegmentTreePaths } from '../../routing/segment-keys.ts';
|
|
10
9
|
import type { SegmentInfo } from '../../shared/segment-info.ts';
|
|
11
10
|
import type { SlotSkipEntry } from '../slot-resolver.ts';
|
|
12
11
|
import { isClientReference } from '../utils/element-type.ts';
|
|
13
12
|
import { isStaticRequestDependent } from '../request-dep.ts';
|
|
14
13
|
|
|
15
|
-
/**
|
|
16
|
-
* No-op — returns undefined so no debugChannel is passed to Flight.
|
|
17
|
-
* Debug rows ride the main stream inline (React's default behavior).
|
|
18
|
-
*
|
|
19
|
-
* Production Flight emits no debug rows. In dev, inline debug rows
|
|
20
|
-
* are handled by the Flight client for owner stack resolution.
|
|
21
|
-
*
|
|
22
|
-
* A writable-only debugChannel triggers a react-server-dom-webpack
|
|
23
|
-
* >=19.3.0 Flight client regression (owner/stack refs parsed after
|
|
24
|
-
* stream close produce "Connection closed" errors).
|
|
25
|
-
*/
|
|
26
|
-
export function createDebugChannelSink(): undefined {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// ─── Debug Channel Collector (dev mode only) ────────────────────────────
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Parsed component debug info extracted from the Flight debug channel.
|
|
34
|
-
*
|
|
35
|
-
* Contains only component names, environment labels, and stack frames —
|
|
36
|
-
* never source code or props. See design/13-security.md §"Server source
|
|
37
|
-
* never reaches the client".
|
|
38
|
-
*/
|
|
39
|
-
export interface DebugComponentEntry {
|
|
40
|
-
name: string;
|
|
41
|
-
env: string | null;
|
|
42
|
-
key: string | null;
|
|
43
|
-
stack: unknown[] | null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Parse React Flight debug rows into component entries.
|
|
48
|
-
*
|
|
49
|
-
* The Flight debug channel writes rows in `hexId:json\n` format. Each row
|
|
50
|
-
* with a JSON object containing a `name` field is a component debug info
|
|
51
|
-
* entry. Rows without `name` (timing rows, reference rows like `D"$id"`)
|
|
52
|
-
* are skipped.
|
|
53
|
-
*
|
|
54
|
-
* Security: `props` are explicitly stripped from parsed entries — they may
|
|
55
|
-
* contain rendered output or user data. Only `name`, `env`, `key`, and
|
|
56
|
-
* `stack` are retained.
|
|
57
|
-
*/
|
|
58
|
-
export function parseDebugRows(text: string): DebugComponentEntry[] {
|
|
59
|
-
if (!text) return [];
|
|
60
|
-
|
|
61
|
-
const entries: DebugComponentEntry[] = [];
|
|
62
|
-
const lines = text.split('\n');
|
|
63
|
-
|
|
64
|
-
for (const line of lines) {
|
|
65
|
-
if (!line) continue;
|
|
66
|
-
|
|
67
|
-
// Flight row format: hexId:payload
|
|
68
|
-
const colonIdx = line.indexOf(':');
|
|
69
|
-
if (colonIdx === -1) continue;
|
|
70
|
-
|
|
71
|
-
let payload = line.slice(colonIdx + 1);
|
|
72
|
-
|
|
73
|
-
// React Flight debug rows may have a type-tag prefix before the JSON
|
|
74
|
-
// object (e.g., "D{...}" for debug info rows). Strip the single-char
|
|
75
|
-
// tag so the JSON parser sees the object. Non-JSON payloads like
|
|
76
|
-
// D"$a" reference rows are caught by the JSON.parse try/catch below.
|
|
77
|
-
// See TIM-556.
|
|
78
|
-
if (payload.length > 0 && payload[0] !== '{' && payload[0] !== '[' && payload[0] !== '"') {
|
|
79
|
-
payload = payload.slice(1);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Skip payloads that still aren't JSON objects after tag stripping
|
|
83
|
-
if (!payload.startsWith('{')) continue;
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const parsed = JSON.parse(payload);
|
|
87
|
-
if (typeof parsed !== 'object' || parsed === null) continue;
|
|
88
|
-
if (typeof parsed.name !== 'string') continue;
|
|
89
|
-
|
|
90
|
-
// Strip props — may contain source-derived data or user data.
|
|
91
|
-
// Only retain: name, env, key, stack.
|
|
92
|
-
entries.push({
|
|
93
|
-
name: parsed.name,
|
|
94
|
-
env: parsed.env ?? null,
|
|
95
|
-
key: parsed.key ?? null,
|
|
96
|
-
stack: Array.isArray(parsed.stack) ? parsed.stack : null,
|
|
97
|
-
});
|
|
98
|
-
} catch (e) {
|
|
99
|
-
swallow(e, 'malformed JSON in RSC client reference row');
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return entries;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
14
|
/**
|
|
107
15
|
* Build segment metadata for the X-Timber-Segments response header.
|
|
108
16
|
* Describes the rendered segment chain with request-dependency status,
|
|
@@ -60,7 +60,6 @@ import { renderNoMatchPage } from './error-renderer.ts';
|
|
|
60
60
|
import { renderRoute } from './render-route.ts';
|
|
61
61
|
import { isDebug, isDevMode, setDebugFromConfig } from '../debug.ts';
|
|
62
62
|
import { setSourceMapCallback } from '../dev-source-map.ts';
|
|
63
|
-
import { requestContextAls } from '../als-registry.ts';
|
|
64
63
|
import { capturePrebuiltPayloads } from '../prebuilt-builder.ts';
|
|
65
64
|
import { setPrebuiltPayloadSource } from '../prebuilt/payload-source.ts';
|
|
66
65
|
import { setRscFunctions, setComponentRefreshTimeout } from '../prebuilt-runtime.ts';
|
|
@@ -97,10 +96,6 @@ export function setDevSourceMapHandler(handler: (error: Error) => void): void {
|
|
|
97
96
|
setSourceMapCallback(handler);
|
|
98
97
|
}
|
|
99
98
|
|
|
100
|
-
// Dev-only: debug components getter is stored per-request in ALS
|
|
101
|
-
// (RequestContextStore.debugComponentsGetter) to avoid cross-request
|
|
102
|
-
// race conditions. See TIM-557.
|
|
103
|
-
|
|
104
99
|
/**
|
|
105
100
|
* Create the RSC request handler from the route manifest.
|
|
106
101
|
*
|
|
@@ -299,7 +294,6 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
299
294
|
rootSegment: manifest.root,
|
|
300
295
|
buildManifest: typedBuildManifest,
|
|
301
296
|
globalError: manifest.globalError,
|
|
302
|
-
devDebugRegistry: pipelineConfig.devDebugRegistry,
|
|
303
297
|
},
|
|
304
298
|
interception
|
|
305
299
|
);
|
|
@@ -351,14 +345,7 @@ async function createRequestHandler(manifest: typeof routeManifest, runtimeConfi
|
|
|
351
345
|
onPipelineError: isDev
|
|
352
346
|
? (error: Error, phase: string) => {
|
|
353
347
|
if (pipelineConfig.devPipelineErrorHandler) {
|
|
354
|
-
|
|
355
|
-
// from the Flight debug channel (if available from the current request).
|
|
356
|
-
const store = requestContextAls.getStore();
|
|
357
|
-
const debugComponents =
|
|
358
|
-
phase === 'render' && store?.debugComponentsGetter
|
|
359
|
-
? store.debugComponentsGetter()
|
|
360
|
-
: undefined;
|
|
361
|
-
pipelineConfig.devPipelineErrorHandler(error, phase, debugComponents);
|
|
348
|
+
pipelineConfig.devPipelineErrorHandler(error, phase);
|
|
362
349
|
}
|
|
363
350
|
}
|
|
364
351
|
: undefined,
|
|
@@ -35,13 +35,12 @@ import { buildRouteElement, NoPageComponentError } from '../route-element-builde
|
|
|
35
35
|
import { ParamCoercionError } from '../param-coercion.ts';
|
|
36
36
|
import type { ManifestSegmentNode } from '../route-matcher.ts';
|
|
37
37
|
import { recordTiming } from '../server-timing.ts';
|
|
38
|
-
import { requestContextAls } from '../als-registry.ts';
|
|
39
38
|
import { parseClientStateTree } from '../state-tree-diff.ts';
|
|
40
39
|
|
|
41
40
|
import { getFirstDeniedSegmentIndex } from '../deny-boundary.ts';
|
|
42
41
|
import { handleApiRoute } from './api-handler.ts';
|
|
43
42
|
import { renderErrorPage, renderNoMatchPage } from './error-renderer.ts';
|
|
44
|
-
import { buildRedirectResponse
|
|
43
|
+
import { buildRedirectResponse } from './helpers.ts';
|
|
45
44
|
import { isRscRequest } from '../../shared/rsc-media-type.ts';
|
|
46
45
|
import { buildRscPayloadResponse } from './rsc-payload.ts';
|
|
47
46
|
import { renderRscStream } from './rsc-stream.ts';
|
|
@@ -60,7 +59,6 @@ export interface RenderRouteDeps {
|
|
|
60
59
|
rootSegment: ManifestSegmentNode;
|
|
61
60
|
buildManifest: BuildManifest;
|
|
62
61
|
globalError?: { load: () => Promise<unknown>; filePath: string };
|
|
63
|
-
devDebugRegistry?: import('../../dev-tools/debug-channel.ts').DebugChannelRegistry;
|
|
64
62
|
}
|
|
65
63
|
|
|
66
64
|
/**
|
|
@@ -199,24 +197,8 @@ export async function renderRoute(
|
|
|
199
197
|
|
|
200
198
|
// Render to RSC Flight stream with signal tracking.
|
|
201
199
|
const _rscStart = performance.now();
|
|
202
|
-
|
|
203
|
-
// requests (client navigations) don't serve HTML with __timber_debug_id,
|
|
204
|
-
// so the browser can't subscribe — skip to avoid orphaned sessions.
|
|
205
|
-
const debugRegistry = isRscRequest(req) || clientJsDisabled ? undefined : deps.devDebugRegistry;
|
|
206
|
-
const { rscStream, signals, getDebugComponents, debugRequestId } = renderRscStream(
|
|
207
|
-
payload,
|
|
208
|
-
req,
|
|
209
|
-
leafDenyChain,
|
|
210
|
-
debugRegistry
|
|
211
|
-
);
|
|
200
|
+
const { rscStream, signals } = renderRscStream(payload, req, leafDenyChain);
|
|
212
201
|
|
|
213
|
-
// Store the debug components getter in ALS so onPipelineError can
|
|
214
|
-
// include component tree context for render-phase errors (dev mode only).
|
|
215
|
-
// Per-request via ALS — no cross-request race. See TIM-557.
|
|
216
|
-
const alsStore = requestContextAls.getStore();
|
|
217
|
-
if (alsStore) {
|
|
218
|
-
alsStore.debugComponentsGetter = getDebugComponents;
|
|
219
|
-
}
|
|
220
202
|
recordTiming({
|
|
221
203
|
name: 'rsc-init',
|
|
222
204
|
dur: Math.round(performance.now() - _rscStart),
|
|
@@ -239,7 +221,6 @@ export async function renderRoute(
|
|
|
239
221
|
layoutComponents as LayoutEntry[],
|
|
240
222
|
req,
|
|
241
223
|
responseHeaders,
|
|
242
|
-
createDebugChannelSink,
|
|
243
224
|
getFirstDeniedSegmentIndex()
|
|
244
225
|
);
|
|
245
226
|
}
|
|
@@ -251,7 +232,6 @@ export async function renderRoute(
|
|
|
251
232
|
match,
|
|
252
233
|
responseHeaders,
|
|
253
234
|
clientBootstrap,
|
|
254
|
-
createDebugChannelSink,
|
|
255
235
|
callSsr,
|
|
256
236
|
undefined,
|
|
257
237
|
getFirstDeniedSegmentIndex()
|
|
@@ -310,6 +290,5 @@ export async function renderRoute(
|
|
|
310
290
|
deferSuspenseFor,
|
|
311
291
|
globalError,
|
|
312
292
|
slotSkipInfo,
|
|
313
|
-
debugRequestId,
|
|
314
293
|
});
|
|
315
294
|
}
|
|
@@ -26,7 +26,7 @@ import type { LayoutComponentEntry } from '../route-element-builder.ts';
|
|
|
26
26
|
import type { ManifestSegmentNode } from '../route-matcher.ts';
|
|
27
27
|
import type { SlotSkipEntry } from '../slot-resolver.ts';
|
|
28
28
|
|
|
29
|
-
import { buildRedirectResponse, buildSegmentInfo
|
|
29
|
+
import { buildRedirectResponse, buildSegmentInfo } from './helpers.ts';
|
|
30
30
|
import { RSC_CONTENT_TYPE } from '../../shared/rsc-media-type.ts';
|
|
31
31
|
|
|
32
32
|
import { rscErrorEnvelope } from '../rsc-error-envelope.ts';
|
|
@@ -178,7 +178,6 @@ export async function buildRscPayloadResponse(
|
|
|
178
178
|
layoutComponents as LayoutEntry[],
|
|
179
179
|
req,
|
|
180
180
|
responseHeaders,
|
|
181
|
-
createDebugChannelSink,
|
|
182
181
|
getDeniedIdx()
|
|
183
182
|
);
|
|
184
183
|
}
|
|
@@ -27,8 +27,7 @@ import {
|
|
|
27
27
|
} from '../primitives.ts';
|
|
28
28
|
import { checkAndWarnRscPropError } from '../rsc-prop-warnings.ts';
|
|
29
29
|
|
|
30
|
-
import { isAbortError
|
|
31
|
-
import type { DebugChannelRegistry } from '../../dev-tools/debug-channel.ts';
|
|
30
|
+
import { isAbortError } from './helpers.ts';
|
|
32
31
|
import { isDebug } from '../debug.ts';
|
|
33
32
|
import { isDevMode } from '../debug.ts';
|
|
34
33
|
|
|
@@ -78,10 +77,6 @@ export interface RenderSignals {
|
|
|
78
77
|
export interface RscStreamResult {
|
|
79
78
|
rscStream: ReadableStream<Uint8Array> | undefined;
|
|
80
79
|
signals: RenderSignals;
|
|
81
|
-
/** Dev-only: server component debug info from the Flight debug channel. */
|
|
82
|
-
getDebugComponents?: () => DebugComponentEntry[];
|
|
83
|
-
/** Dev-only: request ID for the debug channel HMR transport. */
|
|
84
|
-
debugRequestId?: string;
|
|
85
80
|
}
|
|
86
81
|
|
|
87
82
|
/**
|
|
@@ -98,8 +93,7 @@ export interface RscStreamResult {
|
|
|
98
93
|
export function renderRscStream(
|
|
99
94
|
payload: RscPayloadRoot,
|
|
100
95
|
req: Request,
|
|
101
|
-
leafDenyChain?: DenyPageEntry[]
|
|
102
|
-
devDebugRegistry?: DebugChannelRegistry
|
|
96
|
+
leafDenyChain?: DenyPageEntry[]
|
|
103
97
|
): RscStreamResult {
|
|
104
98
|
const signals: RenderSignals = {
|
|
105
99
|
denySignal: null,
|
|
@@ -111,14 +105,6 @@ export function renderRscStream(
|
|
|
111
105
|
|
|
112
106
|
let rscStream: ReadableStream<Uint8Array> | undefined;
|
|
113
107
|
|
|
114
|
-
// No debugChannel — debug rows ride the main RSC stream inline.
|
|
115
|
-
// Production Flight emits none. In dev, the browser Flight client
|
|
116
|
-
// handles inline debug rows for owner stack resolution (React's
|
|
117
|
-
// default behavior). A writable-only debugChannel triggers a
|
|
118
|
-
// react-server-dom-webpack >=19.3.0 Flight client regression.
|
|
119
|
-
const debugChannel = undefined;
|
|
120
|
-
const debugRequestId = undefined;
|
|
121
|
-
|
|
122
108
|
try {
|
|
123
109
|
rscStream = renderToReadableStream(
|
|
124
110
|
payload,
|
|
@@ -243,7 +229,6 @@ export function renderRscStream(
|
|
|
243
229
|
errorId,
|
|
244
230
|
});
|
|
245
231
|
},
|
|
246
|
-
debugChannel,
|
|
247
232
|
},
|
|
248
233
|
{
|
|
249
234
|
onClientReference(info: { id: string; name: string; deps: unknown }) {
|
|
@@ -272,16 +257,5 @@ export function renderRscStream(
|
|
|
272
257
|
}
|
|
273
258
|
}
|
|
274
259
|
|
|
275
|
-
return {
|
|
276
|
-
rscStream,
|
|
277
|
-
signals,
|
|
278
|
-
// In production, the debug channel collector's getComponents is available.
|
|
279
|
-
// In dev mode, debug rows ride the main stream (no separate channel), so
|
|
280
|
-
// this is undefined — the error overlay uses stack traces directly.
|
|
281
|
-
getDebugComponents:
|
|
282
|
-
debugChannel && 'getComponents' in debugChannel
|
|
283
|
-
? (debugChannel as { getComponents: () => DebugComponentEntry[] }).getComponents
|
|
284
|
-
: undefined,
|
|
285
|
-
debugRequestId,
|
|
286
|
-
};
|
|
260
|
+
return { rscStream, signals };
|
|
287
261
|
}
|
|
@@ -27,12 +27,7 @@ import type { SlotSkipEntry } from '../slot-resolver.ts';
|
|
|
27
27
|
import type { NavContext } from '../ssr-bridge-types.ts';
|
|
28
28
|
|
|
29
29
|
import { htmlEscapeJsonString } from '../flight-scripts.ts';
|
|
30
|
-
import {
|
|
31
|
-
buildRedirectResponse,
|
|
32
|
-
buildSegmentInfo,
|
|
33
|
-
createDebugChannelSink,
|
|
34
|
-
isAbortError,
|
|
35
|
-
} from './helpers.ts';
|
|
30
|
+
import { buildRedirectResponse, buildSegmentInfo, isAbortError } from './helpers.ts';
|
|
36
31
|
import { getCookiesForSsr } from '../cookie-context.ts';
|
|
37
32
|
import { getDenyStatus, getFirstDeniedSegmentIndex } from '../deny-boundary.ts';
|
|
38
33
|
import { renderErrorPage, type GlobalErrorFile } from './error-renderer.ts';
|
|
@@ -105,7 +100,6 @@ interface SsrRenderOptions {
|
|
|
105
100
|
/** Tier 2 global-error.tsx file, if present in app/. */
|
|
106
101
|
globalError?: GlobalErrorFile;
|
|
107
102
|
slotSkipInfo?: SlotSkipEntry[];
|
|
108
|
-
debugRequestId?: string;
|
|
109
103
|
}
|
|
110
104
|
|
|
111
105
|
/**
|
|
@@ -198,12 +192,7 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
198
192
|
clientBootstrap.preloadLinks +
|
|
199
193
|
// Initialize __timber_f in <head> so it exists before any streaming
|
|
200
194
|
// chunk scripts arrive in <body>. See flight-scripts.ts, LOCAL-415.
|
|
201
|
-
(clientJsDisabled ? '' : flightInitScript(nonce))
|
|
202
|
-
// Embed the debug channel request ID so the browser can subscribe
|
|
203
|
-
// to the correct HMR debug stream for owner stack resolution.
|
|
204
|
-
(opts.debugRequestId && !clientJsDisabled
|
|
205
|
-
? `<script${nonceAttr(nonce)}>self.__timber_debug_id=${htmlEscapeJsonString(JSON.stringify(opts.debugRequestId))}</script>`
|
|
206
|
-
: ''),
|
|
195
|
+
(clientJsDisabled ? '' : flightInitScript(nonce)),
|
|
207
196
|
getHeadSegmentScript: clientJsDisabled
|
|
208
197
|
? undefined
|
|
209
198
|
: () =>
|
|
@@ -250,7 +239,6 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
250
239
|
match,
|
|
251
240
|
responseHeaders,
|
|
252
241
|
clientBootstrap,
|
|
253
|
-
createDebugChannelSink,
|
|
254
242
|
callSsr,
|
|
255
243
|
undefined,
|
|
256
244
|
resolvedDeniedIndex()
|
|
@@ -437,7 +425,6 @@ export async function renderSsrResponse(opts: SsrRenderOptions): Promise<Respons
|
|
|
437
425
|
match,
|
|
438
426
|
responseHeaders,
|
|
439
427
|
clientBootstrap,
|
|
440
|
-
createDebugChannelSink,
|
|
441
428
|
callSsr,
|
|
442
429
|
undefined,
|
|
443
430
|
resolvedDeniedIndex()
|
|
@@ -156,12 +156,7 @@ export async function resolveSlotElement(
|
|
|
156
156
|
const rendered = await scope(() =>
|
|
157
157
|
(SlotPage as (props: Record<string, unknown>) => unknown)(props)
|
|
158
158
|
);
|
|
159
|
-
return containSlotSubtree(
|
|
160
|
-
rendered as React.ReactNode,
|
|
161
|
-
denyFallback,
|
|
162
|
-
undefined,
|
|
163
|
-
slotNode.segmentName
|
|
164
|
-
);
|
|
159
|
+
return containSlotSubtree(rendered as React.ReactNode, denyFallback);
|
|
165
160
|
} catch (error) {
|
|
166
161
|
// RedirectSignal must propagate — the pipeline handles redirects
|
|
167
162
|
// at the top level. Swallowing it here would silently return
|
|
@@ -194,20 +189,12 @@ export async function resolveSlotElement(
|
|
|
194
189
|
element,
|
|
195
190
|
h,
|
|
196
191
|
scope,
|
|
197
|
-
denyFallback
|
|
198
|
-
slotNode.segmentName
|
|
192
|
+
denyFallback
|
|
199
193
|
);
|
|
200
194
|
|
|
201
195
|
// Wrap with slot root's layout — INSIDE the access gate, so the layout
|
|
202
196
|
// server component never executes when access.ts denies. See TIM-1074.
|
|
203
|
-
element = await wrapWithLayout(
|
|
204
|
-
slotNode,
|
|
205
|
-
element,
|
|
206
|
-
h,
|
|
207
|
-
scope,
|
|
208
|
-
denyFallback,
|
|
209
|
-
slotNode.segmentName
|
|
210
|
-
);
|
|
197
|
+
element = await wrapWithLayout(slotNode, element, h, scope, denyFallback);
|
|
211
198
|
|
|
212
199
|
// Wrap in SlotAccessGate if slot root has access.ts. The gate is
|
|
213
200
|
// OUTSIDE the layout, matching the segment model in
|
|
@@ -303,8 +290,7 @@ async function wrapWithIntermediateSegments(
|
|
|
303
290
|
element: React.ReactElement,
|
|
304
291
|
h: CreateElementFn,
|
|
305
292
|
scope?: SlotParamScope,
|
|
306
|
-
denyFallback?: React.ReactNode
|
|
307
|
-
slotName?: string
|
|
293
|
+
denyFallback?: React.ReactNode
|
|
308
294
|
): Promise<React.ReactElement> {
|
|
309
295
|
for (let i = chain.length - 1; i > 0; i--) {
|
|
310
296
|
const seg = chain[i];
|
|
@@ -314,7 +300,7 @@ async function wrapWithIntermediateSegments(
|
|
|
314
300
|
errorBoundaryComponent: TimberErrorBoundary,
|
|
315
301
|
isSlotBoundary: true,
|
|
316
302
|
});
|
|
317
|
-
element = await wrapWithLayout(seg, element, h, scope, denyFallback
|
|
303
|
+
element = await wrapWithLayout(seg, element, h, scope, denyFallback);
|
|
318
304
|
element = await wrapWithAccessGate(seg, element, h, scope);
|
|
319
305
|
}
|
|
320
306
|
return element;
|
|
@@ -355,16 +341,10 @@ function ScopedSlotLayout(props: {
|
|
|
355
341
|
layout: (props: Record<string, unknown>) => unknown;
|
|
356
342
|
scope: SlotParamScope;
|
|
357
343
|
denyFallback: React.ReactNode;
|
|
358
|
-
slotName: string | undefined;
|
|
359
344
|
children: React.ReactElement;
|
|
360
345
|
}): unknown {
|
|
361
346
|
const rendered = props.scope(() => props.layout({ children: props.children }));
|
|
362
|
-
return containSlotSubtree(
|
|
363
|
-
rendered as React.ReactNode,
|
|
364
|
-
props.denyFallback,
|
|
365
|
-
props.children,
|
|
366
|
-
props.slotName
|
|
367
|
-
);
|
|
347
|
+
return containSlotSubtree(rendered as React.ReactNode, props.denyFallback, props.children);
|
|
368
348
|
}
|
|
369
349
|
|
|
370
350
|
/**
|
|
@@ -382,8 +362,7 @@ async function wrapWithLayout(
|
|
|
382
362
|
element: React.ReactElement,
|
|
383
363
|
h: CreateElementFn,
|
|
384
364
|
scope?: SlotParamScope,
|
|
385
|
-
denyFallback: React.ReactNode = null
|
|
386
|
-
slotName?: string
|
|
365
|
+
denyFallback: React.ReactNode = null
|
|
387
366
|
): Promise<React.ReactElement> {
|
|
388
367
|
if (!node.layout) return element;
|
|
389
368
|
const Layout = await loadComponent(node.layout);
|
|
@@ -394,7 +373,6 @@ async function wrapWithLayout(
|
|
|
394
373
|
layout: Layout as (props: Record<string, unknown>) => unknown,
|
|
395
374
|
scope,
|
|
396
375
|
denyFallback,
|
|
397
|
-
slotName,
|
|
398
376
|
children: element,
|
|
399
377
|
});
|
|
400
378
|
}
|