@timber-js/app 0.2.0-alpha.197 → 0.2.0-alpha.199
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-BS-m5SLv.js → actions-d1hCqnU3.js} +35 -8
- package/dist/_chunks/actions-d1hCqnU3.js.map +1 -0
- package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
- package/dist/_chunks/{cache-api-DqzgTEqk.js → cache-api-ByagcC-J.js} +2 -2
- package/dist/_chunks/{cache-api-DqzgTEqk.js.map → cache-api-ByagcC-J.js.map} +1 -1
- package/dist/_chunks/canonicalize-CgHoscYO.js +66 -0
- package/dist/_chunks/canonicalize-CgHoscYO.js.map +1 -0
- package/dist/_chunks/{chains-CZG7E5zg.js → chains-Bpb0W4ax.js} +3 -3
- package/dist/_chunks/{chains-CZG7E5zg.js.map → chains-Bpb0W4ax.js.map} +1 -1
- package/dist/_chunks/{cli-check-bE3H5Bjr.js → cli-check-D6VolrDV.js} +3 -3
- package/dist/_chunks/{cli-check-bE3H5Bjr.js.map → cli-check-D6VolrDV.js.map} +1 -1
- package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js → cli-schema-sync-D6rO-VcS.js} +2 -2
- package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js.map → cli-schema-sync-D6rO-VcS.js.map} +1 -1
- package/dist/_chunks/{convention-lint-n3RJLgww.js → convention-lint-fRkwVwEH.js} +27 -4
- package/dist/_chunks/convention-lint-fRkwVwEH.js.map +1 -0
- package/dist/_chunks/error-boundary-BfPHZjm0.js +1050 -0
- package/dist/_chunks/error-boundary-BfPHZjm0.js.map +1 -0
- package/dist/_chunks/{live-graph-BXDsdzBv.js → live-graph-D_2D32Ad.js} +3 -3
- package/dist/_chunks/{live-graph-BXDsdzBv.js.map → live-graph-D_2D32Ad.js.map} +1 -1
- package/dist/_chunks/{logger-DDirEsn7.js → logger-uLBuGKDI.js} +471 -440
- package/dist/_chunks/logger-uLBuGKDI.js.map +1 -0
- package/dist/_chunks/{navigation-root-B00jjGd5.js → navigation-context-D0TU0Jog.js} +3 -101
- package/dist/_chunks/navigation-context-D0TU0Jog.js.map +1 -0
- package/dist/_chunks/navigation-root-mHSK9psY.js +126 -0
- package/dist/_chunks/{navigation-root-B00jjGd5.js.map → navigation-root-mHSK9psY.js.map} +1 -1
- package/dist/_chunks/{poison-scan-BoDLgbix.js → poison-scan-Bm9Yyqk9.js} +2 -2
- package/dist/_chunks/{poison-scan-BoDLgbix.js.map → poison-scan-Bm9Yyqk9.js.map} +1 -1
- package/dist/_chunks/{scanner-tdFPvDYi.js → scanner-AiazgH_f.js} +6 -5
- package/dist/_chunks/scanner-AiazgH_f.js.map +1 -0
- package/dist/_chunks/{segment-keys-BhqoHiLc.js → segment-keys-lqtdookO.js} +2 -65
- package/dist/_chunks/segment-keys-lqtdookO.js.map +1 -0
- package/dist/_chunks/{ssr-data-BQGhTPAK.js → ssr-data-D6T6Y3ef.js} +4 -26
- package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +1 -0
- package/dist/_chunks/state-FippDgxN.js +52 -0
- package/dist/_chunks/state-FippDgxN.js.map +1 -0
- package/dist/_chunks/status-page-marker-DwQBrLBz.js +496 -0
- package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +1 -0
- package/dist/_chunks/{walkers-DNX05dC0.js → walkers-B6XUtmqK.js} +2 -2
- package/dist/_chunks/{walkers-DNX05dC0.js.map → walkers-B6XUtmqK.js.map} +1 -1
- package/dist/analyze/crawl-entry.js +2 -2
- package/dist/analyze/graph-command.js +2 -2
- package/dist/cache/index.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/client/browser-entry/action-dispatch.d.ts +6 -4
- package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
- package/dist/client/browser-entry/action-queue.d.ts +44 -0
- package/dist/client/browser-entry/action-queue.d.ts.map +1 -0
- package/dist/client/browser-entry/router-init.d.ts.map +1 -1
- package/dist/client/deny-last-resort.d.ts +29 -0
- package/dist/client/deny-last-resort.d.ts.map +1 -0
- package/dist/client/error-boundary.d.ts +48 -2
- package/dist/client/error-boundary.d.ts.map +1 -1
- package/dist/client/error-boundary.js +2 -2
- package/dist/client/history.d.ts +21 -2
- package/dist/client/history.d.ts.map +1 -1
- package/dist/client/index.js +34 -14
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal.d.ts +1 -0
- package/dist/client/internal.d.ts.map +1 -1
- package/dist/client/internal.js +272 -1225
- package/dist/client/internal.js.map +1 -1
- package/dist/client/link.d.ts.map +1 -1
- package/dist/client/navigation-commit.d.ts +12 -19
- package/dist/client/navigation-commit.d.ts.map +1 -1
- package/dist/client/navigation-transition.d.ts +62 -11
- package/dist/client/navigation-transition.d.ts.map +1 -1
- package/dist/client/router-effects.d.ts +9 -8
- package/dist/client/router-effects.d.ts.map +1 -1
- package/dist/client/router-lifecycle.d.ts +60 -17
- package/dist/client/router-lifecycle.d.ts.map +1 -1
- package/dist/client/router-pipeline.d.ts +7 -4
- package/dist/client/router-pipeline.d.ts.map +1 -1
- package/dist/client/router-types.d.ts +63 -8
- package/dist/client/router-types.d.ts.map +1 -1
- package/dist/client/router.d.ts.map +1 -1
- package/dist/client/rsc-fetch.d.ts +0 -9
- package/dist/client/rsc-fetch.d.ts.map +1 -1
- package/dist/client/segment-cache.d.ts +23 -8
- package/dist/client/segment-cache.d.ts.map +1 -1
- package/dist/client/state.d.ts +16 -0
- package/dist/client/state.d.ts.map +1 -1
- package/dist/client/status-page-marker.d.ts +25 -0
- package/dist/client/status-page-marker.d.ts.map +1 -0
- package/dist/cookies/index.js +1 -1
- package/dist/dev-tools/holding-server.d.ts +4 -17
- package/dist/dev-tools/holding-server.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -175
- package/dist/index.js.map +1 -1
- package/dist/plugins/dev-server.d.ts.map +1 -1
- package/dist/routing/codegen-write.d.ts.map +1 -1
- package/dist/routing/index.js +2 -2
- package/dist/routing/interception.d.ts +2 -2
- package/dist/routing/slot-placement.d.ts +2 -2
- package/dist/server/access-gate.d.ts +73 -1
- package/dist/server/access-gate.d.ts.map +1 -1
- package/dist/server/action-handler.d.ts.map +1 -1
- package/dist/server/actions.d.ts +16 -1
- package/dist/server/actions.d.ts.map +1 -1
- package/dist/server/als-registry.d.ts +3 -9
- package/dist/server/als-registry.d.ts.map +1 -1
- package/dist/server/children-interception.d.ts +1 -1
- package/dist/server/default-status-page.d.ts +2 -2
- package/dist/server/default-status-page.d.ts.map +1 -1
- package/dist/server/deny-boundary.d.ts +15 -9
- package/dist/server/deny-boundary.d.ts.map +1 -1
- package/dist/server/deny-renderer.d.ts.map +1 -1
- package/dist/server/error-boundary-wrapper.d.ts +21 -4
- package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
- package/dist/server/error-response-headers.d.ts +3 -0
- package/dist/server/error-response-headers.d.ts.map +1 -0
- package/dist/server/index.js +3 -3
- package/dist/server/index.js.map +1 -1
- package/dist/server/internal.d.ts +1 -2
- package/dist/server/internal.d.ts.map +1 -1
- package/dist/server/internal.js +2339 -2506
- package/dist/server/internal.js.map +1 -1
- package/dist/server/metadata-collector.d.ts +2 -5
- package/dist/server/metadata-collector.d.ts.map +1 -1
- package/dist/server/param-coercion.d.ts +10 -3
- package/dist/server/param-coercion.d.ts.map +1 -1
- package/dist/server/pipeline-outcome.d.ts.map +1 -1
- package/dist/server/pipeline-phases.d.ts +11 -0
- package/dist/server/pipeline-phases.d.ts.map +1 -1
- package/dist/server/port-resolution.d.ts +3 -89
- package/dist/server/port-resolution.d.ts.map +1 -1
- package/dist/server/primitives.d.ts +38 -10
- package/dist/server/primitives.d.ts.map +1 -1
- package/dist/server/response-cache-policy.d.ts +3 -0
- package/dist/server/response-cache-policy.d.ts.map +1 -0
- package/dist/server/route-element-builder.d.ts +11 -41
- package/dist/server/route-element-builder.d.ts.map +1 -1
- package/dist/server/route-element-helpers.d.ts +12 -0
- package/dist/server/route-element-helpers.d.ts.map +1 -0
- package/dist/server/route-module-loader.d.ts +37 -0
- package/dist/server/route-module-loader.d.ts.map +1 -0
- package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
- package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
- package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
- package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts +22 -1
- package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
- package/dist/server/rsc-entry/rsc-stream.d.ts +4 -11
- package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
- package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
- package/dist/server/rsc-error-envelope.d.ts +11 -0
- package/dist/server/rsc-error-envelope.d.ts.map +1 -0
- package/dist/server/skippable-prefix.d.ts +18 -15
- package/dist/server/skippable-prefix.d.ts.map +1 -1
- package/dist/server/slot-resolver.d.ts.map +1 -1
- package/dist/server/slot-subtree-contain.d.ts +54 -0
- package/dist/server/slot-subtree-contain.d.ts.map +1 -0
- package/dist/server/stream-utils.d.ts.map +1 -1
- package/dist/server/utils/element-type.d.ts +10 -0
- package/dist/server/utils/element-type.d.ts.map +1 -1
- package/dist/shared/rsc-error-envelope.d.ts +0 -9
- package/dist/shared/rsc-error-envelope.d.ts.map +1 -1
- package/dist/shared/status-reason-phrase.d.ts +26 -0
- package/dist/shared/status-reason-phrase.d.ts.map +1 -0
- package/docs/api/30-api-server.mdx +4 -2
- package/docs/api/31-api-client.mdx +5 -1
- package/docs/api/36-cli.mdx +5 -3
- package/docs/learn/12-error-handling.mdx +5 -1
- package/package.json +10 -10
- package/src/client/browser-entry/action-dispatch.ts +166 -99
- package/src/client/browser-entry/action-queue.ts +90 -0
- package/src/client/browser-entry/router-init.ts +60 -35
- package/src/client/deny-last-resort.tsx +54 -0
- package/src/client/error-boundary.tsx +144 -42
- package/src/client/history.ts +52 -3
- package/src/client/internal.ts +1 -0
- package/src/client/link.tsx +70 -35
- package/src/client/navigation-commit.ts +79 -27
- package/src/client/navigation-transition.ts +176 -127
- package/src/client/router-effects.ts +14 -17
- package/src/client/router-lifecycle.ts +181 -115
- package/src/client/router-pipeline.ts +94 -71
- package/src/client/router-types.ts +61 -7
- package/src/client/router.ts +147 -74
- package/src/client/rsc-fetch.ts +0 -13
- package/src/client/segment-cache.ts +43 -10
- package/src/client/state.ts +26 -0
- package/src/client/status-page-marker.tsx +32 -0
- package/src/dev-tools/holding-server.ts +4 -17
- package/src/index.ts +18 -34
- package/src/plugins/dev-server.ts +2 -1
- package/src/react-canary.d.ts +2 -0
- package/src/routing/codegen-write.ts +2 -0
- package/src/routing/interception.ts +2 -2
- package/src/routing/slot-placement.ts +2 -2
- package/src/server/access-gate.tsx +89 -21
- package/src/server/action-client.ts +2 -2
- package/src/server/action-handler.ts +23 -10
- package/src/server/actions.ts +81 -34
- package/src/server/als-registry.ts +3 -9
- package/src/server/children-interception.ts +1 -1
- package/src/server/default-status-page.ts +7 -47
- package/src/server/deny-boundary.ts +45 -28
- package/src/server/deny-renderer.ts +6 -2
- package/src/server/error-boundary-wrapper.ts +23 -4
- package/src/server/error-response-headers.ts +18 -0
- package/src/server/internal.ts +2 -10
- package/src/server/metadata-collector.ts +3 -18
- package/src/server/param-coercion.ts +13 -4
- package/src/server/pipeline-outcome.ts +35 -13
- package/src/server/pipeline-phases.ts +22 -16
- package/src/server/port-resolution.ts +3 -165
- package/src/server/prebuilt-builder.ts +4 -4
- package/src/server/primitives.ts +75 -11
- package/src/server/response-cache-policy.ts +45 -0
- package/src/server/route-element-builder.ts +149 -412
- package/src/server/route-element-helpers.ts +37 -0
- package/src/server/route-handler.ts +2 -2
- package/src/server/route-module-loader.ts +161 -0
- package/src/server/rsc-cache-key-guard.ts +2 -42
- package/src/server/rsc-entry/action-middleware-runner.ts +4 -4
- package/src/server/rsc-entry/api-handler.ts +5 -5
- package/src/server/rsc-entry/error-renderer.ts +3 -4
- package/src/server/rsc-entry/helpers.ts +1 -1
- package/src/server/rsc-entry/index.ts +3 -3
- package/src/server/rsc-entry/render-route.ts +4 -8
- package/src/server/rsc-entry/rsc-payload.ts +59 -42
- package/src/server/rsc-entry/rsc-stream.ts +48 -27
- package/src/server/rsc-entry/ssr-renderer.ts +6 -10
- package/src/server/rsc-error-envelope.ts +18 -0
- package/src/server/skippable-prefix.ts +105 -7
- package/src/server/slot-resolver.ts +43 -12
- package/src/server/slot-subtree-contain.ts +255 -0
- package/src/server/stream-utils.ts +12 -8
- package/src/server/utils/element-type.ts +18 -2
- package/src/shared/rsc-error-envelope.ts +0 -15
- package/src/shared/status-reason-phrase.ts +61 -0
- package/dist/_chunks/actions-BS-m5SLv.js.map +0 -1
- package/dist/_chunks/convention-lint-n3RJLgww.js.map +0 -1
- package/dist/_chunks/error-boundary-BvRCCmbN.js +0 -353
- package/dist/_chunks/error-boundary-BvRCCmbN.js.map +0 -1
- package/dist/_chunks/logger-DDirEsn7.js.map +0 -1
- package/dist/_chunks/mdx-file-CXyHGUpS.js +0 -25
- package/dist/_chunks/mdx-file-CXyHGUpS.js.map +0 -1
- package/dist/_chunks/router-ref-8gr8qsxN.js +0 -28
- package/dist/_chunks/router-ref-8gr8qsxN.js.map +0 -1
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +0 -40
- package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +0 -1
- package/dist/_chunks/scanner-tdFPvDYi.js.map +0 -1
- package/dist/_chunks/segment-keys-BhqoHiLc.js.map +0 -1
- package/dist/_chunks/ssr-data-BQGhTPAK.js.map +0 -1
- package/dist/server/tree-builder.d.ts +0 -150
- package/dist/server/tree-builder.d.ts.map +0 -1
- package/src/server/tree-builder.ts +0 -313
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Element tree construction for timber.js rendering.
|
|
3
|
-
*
|
|
4
|
-
* Builds a unified React element tree from a matched segment chain, bottom-up:
|
|
5
|
-
* page → status-code error boundaries → access gates → layout → repeat up segment chain
|
|
6
|
-
*
|
|
7
|
-
* The tree is rendered via a single `renderToReadableStream` call,
|
|
8
|
-
* giving one `React.cache` scope for the entire route.
|
|
9
|
-
*
|
|
10
|
-
* See design/02-rendering-pipeline.md §"Element Tree Construction"
|
|
11
|
-
*/
|
|
12
|
-
import type { ReactNode } from 'react';
|
|
13
|
-
import type { RouteFile, SegmentNode } from '../routing/types.ts';
|
|
14
|
-
import type { LoadedComponent } from './utils/element-type.ts';
|
|
15
|
-
/** A loaded module for a route file convention. */
|
|
16
|
-
export interface LoadedModule {
|
|
17
|
-
/** The default export (component, access function, etc.) */
|
|
18
|
-
default?: unknown;
|
|
19
|
-
/** Named exports (for route.ts method handlers, metadata, etc.) */
|
|
20
|
-
[key: string]: unknown;
|
|
21
|
-
}
|
|
22
|
-
/** Function that loads a route file's module. */
|
|
23
|
-
export type ModuleLoader = (file: RouteFile) => LoadedModule | Promise<LoadedModule>;
|
|
24
|
-
/**
|
|
25
|
-
* Function that creates a React element. Matches React.createElement signature.
|
|
26
|
-
*
|
|
27
|
-
* `props` is typed as `object | null` rather than `Record<string, unknown>` so
|
|
28
|
-
* that interface types with known keys (e.g. `AccessGateProps`,
|
|
29
|
-
* `ErrorBoundaryProps`) flow through without an explicit index-signature cast.
|
|
30
|
-
*/
|
|
31
|
-
export type CreateElement = (type: unknown, props: object | null, ...children: unknown[]) => ReactNode;
|
|
32
|
-
/**
|
|
33
|
-
* Resolved slot content for a layout.
|
|
34
|
-
* Key is slot name (without @), value is the element tree for that slot.
|
|
35
|
-
*/
|
|
36
|
-
export type SlotElements = Map<string, ReactNode>;
|
|
37
|
-
/** Configuration for the tree builder. */
|
|
38
|
-
export interface TreeBuilderConfig {
|
|
39
|
-
/** The matched segment chain from root to leaf. */
|
|
40
|
-
segments: SegmentNode[];
|
|
41
|
-
/** Loads a route file's module. */
|
|
42
|
-
loadModule: ModuleLoader;
|
|
43
|
-
/** React.createElement or equivalent. */
|
|
44
|
-
createElement: CreateElement;
|
|
45
|
-
/**
|
|
46
|
-
* Error boundary component for wrapping segments.
|
|
47
|
-
*
|
|
48
|
-
* This is injected by the caller rather than imported directly to avoid
|
|
49
|
-
* pulling 'use client' code into the server barrel (@timber-js/app/server).
|
|
50
|
-
* In the RSC environment, the RSC plugin transforms this import to a
|
|
51
|
-
* client reference proxy — the caller handles the import so the server
|
|
52
|
-
* barrel stays free of client dependencies.
|
|
53
|
-
*/
|
|
54
|
-
errorBoundaryComponent?: unknown;
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Framework-injected access gate component.
|
|
58
|
-
*
|
|
59
|
-
* When `verdict` is provided (from the pre-render pass), AccessGate replays
|
|
60
|
-
* the stored result synchronously — no re-execution, no async, immune to
|
|
61
|
-
* Suspense timing. When `verdict` is absent, falls back to calling `accessFn`
|
|
62
|
-
* (backward compat for tree-builder.ts which doesn't run a pre-render pass).
|
|
63
|
-
*/
|
|
64
|
-
export interface AccessGateProps {
|
|
65
|
-
accessFn: () => unknown;
|
|
66
|
-
/** Segment name for dev logging (e.g. "authenticated", "dashboard"). */
|
|
67
|
-
segmentName?: string;
|
|
68
|
-
/**
|
|
69
|
-
* Segment key of the gated segment (`computeSegmentKeys`). Decides whether
|
|
70
|
-
* a matched deny page renders here or is hoisted to the segment that owns
|
|
71
|
-
* it. Omitted by construction sites that build no deny chains — with no
|
|
72
|
-
* chain there is nothing to place. See TIM-1356.
|
|
73
|
-
*/
|
|
74
|
-
segmentKey?: string;
|
|
75
|
-
/**
|
|
76
|
-
* Index of this segment in the matched segment chain. Recorded in ALS
|
|
77
|
-
* when the gate catches a DenySignal so post-render consumers
|
|
78
|
-
* (buildSegmentInfo, X-Timber-Segments) can bound the segment list
|
|
79
|
-
* without a pre-render access pass. See TIM-1363.
|
|
80
|
-
*/
|
|
81
|
-
segmentIndex?: number;
|
|
82
|
-
/**
|
|
83
|
-
* Deny page fallback chain. When provided and a DenySignal is caught,
|
|
84
|
-
* AccessGate renders the matching deny page in-tree instead of throwing.
|
|
85
|
-
* This prevents the error from reaching React Flight, eliminating the
|
|
86
|
-
* second render pass. See TIM-666.
|
|
87
|
-
*
|
|
88
|
-
* Omitted for skipped segments — a deny there must hard-navigate to
|
|
89
|
-
* avoid rendering the deny page inside cached layout chrome (TIM-1074).
|
|
90
|
-
*/
|
|
91
|
-
denyPages?: import('./deny-boundary.ts').DenyPageEntry[];
|
|
92
|
-
/**
|
|
93
|
-
* When true, this gate wraps a skipped segment. On deny, the gate
|
|
94
|
-
* records the deny in ALS and rethrows so the response path can
|
|
95
|
-
* return rscErrorEnvelope for a client hard-navigate.
|
|
96
|
-
*/
|
|
97
|
-
skippedSegment?: boolean;
|
|
98
|
-
/**
|
|
99
|
-
* Shell settlement callback. Called when the gate finishes (pass or
|
|
100
|
-
* deny). Tracked by shellSettled so the HTTP status is not committed
|
|
101
|
-
* before async access checks complete. See TIM-1045, TIM-1363.
|
|
102
|
-
*/
|
|
103
|
-
onSettled?: () => void;
|
|
104
|
-
children: ReactNode;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Framework-injected slot access gate component.
|
|
108
|
-
* On denial, renders denied.tsx → default.tsx → null instead of failing the page.
|
|
109
|
-
*
|
|
110
|
-
* DeniedComponent is passed instead of a pre-built element so that
|
|
111
|
-
* SlotAccessGate can forward DenySignal.data as dangerouslyPassData
|
|
112
|
-
* and slotName as the slot prop after catching the signal.
|
|
113
|
-
*/
|
|
114
|
-
export interface SlotAccessGateProps {
|
|
115
|
-
accessFn: () => unknown;
|
|
116
|
-
/** The denied.tsx component (not a pre-built element). null if no denied.tsx exists. */
|
|
117
|
-
DeniedComponent: LoadedComponent | null;
|
|
118
|
-
/** Slot directory name without @ prefix (e.g. "admin", "sidebar"). */
|
|
119
|
-
slotName: string;
|
|
120
|
-
/** createElement function for building elements dynamically. */
|
|
121
|
-
createElement: CreateElement;
|
|
122
|
-
defaultFallback: ReactNode;
|
|
123
|
-
children: ReactNode;
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* Result of building the element tree.
|
|
127
|
-
*/
|
|
128
|
-
export interface TreeBuildResult {
|
|
129
|
-
/**
|
|
130
|
-
* The root React element tree ready for renderToReadableStream.
|
|
131
|
-
* `null` for API routes (route.ts), which don't render a React tree.
|
|
132
|
-
*/
|
|
133
|
-
tree: ReactNode;
|
|
134
|
-
/** Whether the leaf segment is a route.ts (API endpoint) rather than a page. */
|
|
135
|
-
isApiRoute: boolean;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Build the unified element tree from a matched segment chain.
|
|
139
|
-
*
|
|
140
|
-
* Construction is bottom-up:
|
|
141
|
-
* 1. Start with the page component (leaf segment)
|
|
142
|
-
* 2. Wrap in status-code error boundaries (fallback chain)
|
|
143
|
-
* 3. Wrap in AccessGate (if segment has access.ts)
|
|
144
|
-
* 4. Pass as children to the segment's layout
|
|
145
|
-
* 5. Repeat up the segment chain to root
|
|
146
|
-
*
|
|
147
|
-
* Parallel slots are resolved at each layout level and composed as named props.
|
|
148
|
-
*/
|
|
149
|
-
export declare function buildElementTree(config: TreeBuilderConfig): Promise<TreeBuildResult>;
|
|
150
|
-
//# sourceMappingURL=tree-builder.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tree-builder.d.ts","sourceRoot":"","sources":["../../src/server/tree-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAI/D,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,4DAA4D;IAC5D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,iDAAiD;AACjD,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,SAAS,KAAK,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;AAErF;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,CAC1B,IAAI,EAAE,OAAO,EACb,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,GAAG,QAAQ,EAAE,OAAO,EAAE,KACnB,SAAS,CAAC;AAEf;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AAElD,0CAA0C;AAC1C,MAAM,WAAW,iBAAiB;IAChC,mDAAmD;IACnD,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,mCAAmC;IACnC,UAAU,EAAE,YAAY,CAAC;IACzB,yCAAyC;IACzC,aAAa,EAAE,aAAa,CAAC;IAC7B;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAID;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,oBAAoB,EAAE,aAAa,EAAE,CAAC;IACzD;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,OAAO,CAAC;IACxB,wFAAwF;IACxF,eAAe,EAAE,eAAe,GAAG,IAAI,CAAC;IACxC,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,aAAa,EAAE,aAAa,CAAC;IAC7B,eAAe,EAAE,SAAS,CAAC;IAC3B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAID;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,EAAE,SAAS,CAAC;IAChB,gFAAgF;IAChF,UAAU,EAAE,OAAO,CAAC;CACrB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkF1F"}
|
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Element tree construction for timber.js rendering.
|
|
3
|
-
*
|
|
4
|
-
* Builds a unified React element tree from a matched segment chain, bottom-up:
|
|
5
|
-
* page → status-code error boundaries → access gates → layout → repeat up segment chain
|
|
6
|
-
*
|
|
7
|
-
* The tree is rendered via a single `renderToReadableStream` call,
|
|
8
|
-
* giving one `React.cache` scope for the entire route.
|
|
9
|
-
*
|
|
10
|
-
* See design/02-rendering-pipeline.md §"Element Tree Construction"
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { ReactNode } from 'react';
|
|
14
|
-
import type { RouteFile, SegmentNode } from '../routing/types.ts';
|
|
15
|
-
import { wrapSegmentWithErrorBoundaries } from './error-boundary-wrapper.ts';
|
|
16
|
-
import type { LoadedComponent } from './utils/element-type.ts';
|
|
17
|
-
|
|
18
|
-
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
/** A loaded module for a route file convention. */
|
|
21
|
-
export interface LoadedModule {
|
|
22
|
-
/** The default export (component, access function, etc.) */
|
|
23
|
-
default?: unknown;
|
|
24
|
-
/** Named exports (for route.ts method handlers, metadata, etc.) */
|
|
25
|
-
[key: string]: unknown;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/** Function that loads a route file's module. */
|
|
29
|
-
export type ModuleLoader = (file: RouteFile) => LoadedModule | Promise<LoadedModule>;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Function that creates a React element. Matches React.createElement signature.
|
|
33
|
-
*
|
|
34
|
-
* `props` is typed as `object | null` rather than `Record<string, unknown>` so
|
|
35
|
-
* that interface types with known keys (e.g. `AccessGateProps`,
|
|
36
|
-
* `ErrorBoundaryProps`) flow through without an explicit index-signature cast.
|
|
37
|
-
*/
|
|
38
|
-
export type CreateElement = (
|
|
39
|
-
type: unknown,
|
|
40
|
-
props: object | null,
|
|
41
|
-
...children: unknown[]
|
|
42
|
-
) => ReactNode;
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Resolved slot content for a layout.
|
|
46
|
-
* Key is slot name (without @), value is the element tree for that slot.
|
|
47
|
-
*/
|
|
48
|
-
export type SlotElements = Map<string, ReactNode>;
|
|
49
|
-
|
|
50
|
-
/** Configuration for the tree builder. */
|
|
51
|
-
export interface TreeBuilderConfig {
|
|
52
|
-
/** The matched segment chain from root to leaf. */
|
|
53
|
-
segments: SegmentNode[];
|
|
54
|
-
/** Loads a route file's module. */
|
|
55
|
-
loadModule: ModuleLoader;
|
|
56
|
-
/** React.createElement or equivalent. */
|
|
57
|
-
createElement: CreateElement;
|
|
58
|
-
/**
|
|
59
|
-
* Error boundary component for wrapping segments.
|
|
60
|
-
*
|
|
61
|
-
* This is injected by the caller rather than imported directly to avoid
|
|
62
|
-
* pulling 'use client' code into the server barrel (@timber-js/app/server).
|
|
63
|
-
* In the RSC environment, the RSC plugin transforms this import to a
|
|
64
|
-
* client reference proxy — the caller handles the import so the server
|
|
65
|
-
* barrel stays free of client dependencies.
|
|
66
|
-
*/
|
|
67
|
-
errorBoundaryComponent?: unknown;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ─── Component wrappers ──────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Framework-injected access gate component.
|
|
74
|
-
*
|
|
75
|
-
* When `verdict` is provided (from the pre-render pass), AccessGate replays
|
|
76
|
-
* the stored result synchronously — no re-execution, no async, immune to
|
|
77
|
-
* Suspense timing. When `verdict` is absent, falls back to calling `accessFn`
|
|
78
|
-
* (backward compat for tree-builder.ts which doesn't run a pre-render pass).
|
|
79
|
-
*/
|
|
80
|
-
export interface AccessGateProps {
|
|
81
|
-
accessFn: () => unknown;
|
|
82
|
-
/** Segment name for dev logging (e.g. "authenticated", "dashboard"). */
|
|
83
|
-
segmentName?: string;
|
|
84
|
-
/**
|
|
85
|
-
* Segment key of the gated segment (`computeSegmentKeys`). Decides whether
|
|
86
|
-
* a matched deny page renders here or is hoisted to the segment that owns
|
|
87
|
-
* it. Omitted by construction sites that build no deny chains — with no
|
|
88
|
-
* chain there is nothing to place. See TIM-1356.
|
|
89
|
-
*/
|
|
90
|
-
segmentKey?: string;
|
|
91
|
-
/**
|
|
92
|
-
* Index of this segment in the matched segment chain. Recorded in ALS
|
|
93
|
-
* when the gate catches a DenySignal so post-render consumers
|
|
94
|
-
* (buildSegmentInfo, X-Timber-Segments) can bound the segment list
|
|
95
|
-
* without a pre-render access pass. See TIM-1363.
|
|
96
|
-
*/
|
|
97
|
-
segmentIndex?: number;
|
|
98
|
-
/**
|
|
99
|
-
* Deny page fallback chain. When provided and a DenySignal is caught,
|
|
100
|
-
* AccessGate renders the matching deny page in-tree instead of throwing.
|
|
101
|
-
* This prevents the error from reaching React Flight, eliminating the
|
|
102
|
-
* second render pass. See TIM-666.
|
|
103
|
-
*
|
|
104
|
-
* Omitted for skipped segments — a deny there must hard-navigate to
|
|
105
|
-
* avoid rendering the deny page inside cached layout chrome (TIM-1074).
|
|
106
|
-
*/
|
|
107
|
-
denyPages?: import('./deny-boundary.ts').DenyPageEntry[];
|
|
108
|
-
/**
|
|
109
|
-
* When true, this gate wraps a skipped segment. On deny, the gate
|
|
110
|
-
* records the deny in ALS and rethrows so the response path can
|
|
111
|
-
* return rscErrorEnvelope for a client hard-navigate.
|
|
112
|
-
*/
|
|
113
|
-
skippedSegment?: boolean;
|
|
114
|
-
/**
|
|
115
|
-
* Shell settlement callback. Called when the gate finishes (pass or
|
|
116
|
-
* deny). Tracked by shellSettled so the HTTP status is not committed
|
|
117
|
-
* before async access checks complete. See TIM-1045, TIM-1363.
|
|
118
|
-
*/
|
|
119
|
-
onSettled?: () => void;
|
|
120
|
-
children: ReactNode;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Framework-injected slot access gate component.
|
|
125
|
-
* On denial, renders denied.tsx → default.tsx → null instead of failing the page.
|
|
126
|
-
*
|
|
127
|
-
* DeniedComponent is passed instead of a pre-built element so that
|
|
128
|
-
* SlotAccessGate can forward DenySignal.data as dangerouslyPassData
|
|
129
|
-
* and slotName as the slot prop after catching the signal.
|
|
130
|
-
*/
|
|
131
|
-
export interface SlotAccessGateProps {
|
|
132
|
-
accessFn: () => unknown;
|
|
133
|
-
/** The denied.tsx component (not a pre-built element). null if no denied.tsx exists. */
|
|
134
|
-
DeniedComponent: LoadedComponent | null;
|
|
135
|
-
/** Slot directory name without @ prefix (e.g. "admin", "sidebar"). */
|
|
136
|
-
slotName: string;
|
|
137
|
-
/** createElement function for building elements dynamically. */
|
|
138
|
-
createElement: CreateElement;
|
|
139
|
-
defaultFallback: ReactNode;
|
|
140
|
-
children: ReactNode;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ─── Tree Builder ────────────────────────────────────────────────────────────
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Result of building the element tree.
|
|
147
|
-
*/
|
|
148
|
-
export interface TreeBuildResult {
|
|
149
|
-
/**
|
|
150
|
-
* The root React element tree ready for renderToReadableStream.
|
|
151
|
-
* `null` for API routes (route.ts), which don't render a React tree.
|
|
152
|
-
*/
|
|
153
|
-
tree: ReactNode;
|
|
154
|
-
/** Whether the leaf segment is a route.ts (API endpoint) rather than a page. */
|
|
155
|
-
isApiRoute: boolean;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Build the unified element tree from a matched segment chain.
|
|
160
|
-
*
|
|
161
|
-
* Construction is bottom-up:
|
|
162
|
-
* 1. Start with the page component (leaf segment)
|
|
163
|
-
* 2. Wrap in status-code error boundaries (fallback chain)
|
|
164
|
-
* 3. Wrap in AccessGate (if segment has access.ts)
|
|
165
|
-
* 4. Pass as children to the segment's layout
|
|
166
|
-
* 5. Repeat up the segment chain to root
|
|
167
|
-
*
|
|
168
|
-
* Parallel slots are resolved at each layout level and composed as named props.
|
|
169
|
-
*/
|
|
170
|
-
export async function buildElementTree(config: TreeBuilderConfig): Promise<TreeBuildResult> {
|
|
171
|
-
const { segments, loadModule, createElement, errorBoundaryComponent } = config;
|
|
172
|
-
|
|
173
|
-
if (segments.length === 0) {
|
|
174
|
-
throw new Error('[timber] buildElementTree: empty segment chain');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const leaf = segments[segments.length - 1];
|
|
178
|
-
|
|
179
|
-
// API routes (route.ts) don't build a React tree
|
|
180
|
-
if (leaf.route && !leaf.page) {
|
|
181
|
-
return { tree: null, isApiRoute: true };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Start with the page component
|
|
185
|
-
const pageModule = leaf.page ? await loadModule(leaf.page) : null;
|
|
186
|
-
const PageComponent = pageModule?.default as LoadedComponent | undefined;
|
|
187
|
-
|
|
188
|
-
if (!PageComponent) {
|
|
189
|
-
throw new Error(
|
|
190
|
-
`[timber] No page component found for route at ${leaf.urlPath}. ` +
|
|
191
|
-
'Each route must have a page.tsx or route.ts.'
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Build the page element — params are accessed via getSegmentParams() from ALS
|
|
196
|
-
let element: ReactNode = createElement(PageComponent, {});
|
|
197
|
-
|
|
198
|
-
// Build tree bottom-up: wrap page, then walk segments from leaf to root
|
|
199
|
-
for (let i = segments.length - 1; i >= 0; i--) {
|
|
200
|
-
const segment = segments[i];
|
|
201
|
-
|
|
202
|
-
// Wrap in error boundaries (status-code files + error.tsx)
|
|
203
|
-
element = await wrapSegmentWithErrorBoundaries(segment, element, {
|
|
204
|
-
loadModule,
|
|
205
|
-
createElement,
|
|
206
|
-
errorBoundaryComponent,
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
// Wrap in AccessGate if segment has access.ts
|
|
210
|
-
if (segment.access) {
|
|
211
|
-
const accessModule = await loadModule(segment.access);
|
|
212
|
-
const accessFn = accessModule.default as AccessGateProps['accessFn'];
|
|
213
|
-
element = createElement('timber:access-gate', {
|
|
214
|
-
accessFn,
|
|
215
|
-
segmentName: segment.segmentName,
|
|
216
|
-
children: element,
|
|
217
|
-
} satisfies AccessGateProps);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Wrap in layout (if exists and not the leaf's page-level wrapping)
|
|
221
|
-
if (segment.layout) {
|
|
222
|
-
const layoutModule = await loadModule(segment.layout);
|
|
223
|
-
const LayoutComponent = layoutModule.default as LoadedComponent | undefined;
|
|
224
|
-
|
|
225
|
-
if (LayoutComponent) {
|
|
226
|
-
// Resolve parallel slots for this layout
|
|
227
|
-
const slotProps: Record<string, ReactNode> = {};
|
|
228
|
-
const slotNames = Object.keys(segment.slots);
|
|
229
|
-
if (slotNames.length > 0) {
|
|
230
|
-
for (const slotName of slotNames) {
|
|
231
|
-
const slotNode = segment.slots[slotName]!;
|
|
232
|
-
slotProps[slotName] = await buildSlotElement(
|
|
233
|
-
slotNode,
|
|
234
|
-
loadModule,
|
|
235
|
-
createElement,
|
|
236
|
-
errorBoundaryComponent
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/* eslint-disable react/no-children-prop -- createElement API */
|
|
242
|
-
element = createElement(LayoutComponent, {
|
|
243
|
-
...slotProps,
|
|
244
|
-
children: element,
|
|
245
|
-
});
|
|
246
|
-
/* eslint-enable react/no-children-prop */
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return { tree: element, isApiRoute: false };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
// ─── Slot Element Builder ────────────────────────────────────────────────────
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Build the element tree for a parallel slot.
|
|
258
|
-
*
|
|
259
|
-
* Slots have their own access.ts (SlotAccessGate) and error boundaries.
|
|
260
|
-
* On access denial: denied.tsx → default.tsx → null (graceful degradation).
|
|
261
|
-
*/
|
|
262
|
-
async function buildSlotElement(
|
|
263
|
-
slotNode: SegmentNode,
|
|
264
|
-
loadModule: ModuleLoader,
|
|
265
|
-
createElement: CreateElement,
|
|
266
|
-
errorBoundaryComponent: unknown
|
|
267
|
-
): Promise<ReactNode> {
|
|
268
|
-
// Load slot page
|
|
269
|
-
const pageModule = slotNode.page ? await loadModule(slotNode.page) : null;
|
|
270
|
-
const PageComponent = pageModule?.default as LoadedComponent | undefined;
|
|
271
|
-
|
|
272
|
-
// Load default.tsx fallback
|
|
273
|
-
const defaultModule = slotNode.default ? await loadModule(slotNode.default) : null;
|
|
274
|
-
const DefaultComponent = defaultModule?.default as LoadedComponent | undefined;
|
|
275
|
-
|
|
276
|
-
// If no page, render default.tsx or null
|
|
277
|
-
if (!PageComponent) {
|
|
278
|
-
return DefaultComponent ? createElement(DefaultComponent, {}) : null;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
let element: ReactNode = createElement(PageComponent, {});
|
|
282
|
-
|
|
283
|
-
// Wrap in error boundaries
|
|
284
|
-
element = await wrapSegmentWithErrorBoundaries(slotNode, element, {
|
|
285
|
-
loadModule,
|
|
286
|
-
createElement,
|
|
287
|
-
errorBoundaryComponent,
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Wrap in SlotAccessGate if slot has access.ts
|
|
291
|
-
if (slotNode.access) {
|
|
292
|
-
const accessModule = await loadModule(slotNode.access);
|
|
293
|
-
const accessFn = accessModule.default as SlotAccessGateProps['accessFn'];
|
|
294
|
-
|
|
295
|
-
// Load denied.tsx — pass component (not pre-built element) so
|
|
296
|
-
// SlotAccessGate can forward DenySignal.data dynamically. See TIM-488.
|
|
297
|
-
const deniedModule = slotNode.denied ? await loadModule(slotNode.denied) : null;
|
|
298
|
-
const DeniedComponent = (deniedModule?.default as LoadedComponent | undefined) ?? null;
|
|
299
|
-
|
|
300
|
-
const defaultFallback = DefaultComponent ? createElement(DefaultComponent, {}) : null;
|
|
301
|
-
|
|
302
|
-
element = createElement('timber:slot-access-gate', {
|
|
303
|
-
accessFn,
|
|
304
|
-
DeniedComponent,
|
|
305
|
-
slotName: slotNode.segmentName.replace(/^@/, ''),
|
|
306
|
-
createElement,
|
|
307
|
-
defaultFallback,
|
|
308
|
-
children: element,
|
|
309
|
-
} satisfies SlotAccessGateProps);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
return element;
|
|
313
|
-
}
|