@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.
Files changed (249) hide show
  1. package/dist/_chunks/{actions-BS-m5SLv.js → actions-d1hCqnU3.js} +35 -8
  2. package/dist/_chunks/actions-d1hCqnU3.js.map +1 -0
  3. package/dist/_chunks/als-registry-C6kcfprT.js.map +1 -1
  4. package/dist/_chunks/{cache-api-DqzgTEqk.js → cache-api-ByagcC-J.js} +2 -2
  5. package/dist/_chunks/{cache-api-DqzgTEqk.js.map → cache-api-ByagcC-J.js.map} +1 -1
  6. package/dist/_chunks/canonicalize-CgHoscYO.js +66 -0
  7. package/dist/_chunks/canonicalize-CgHoscYO.js.map +1 -0
  8. package/dist/_chunks/{chains-CZG7E5zg.js → chains-Bpb0W4ax.js} +3 -3
  9. package/dist/_chunks/{chains-CZG7E5zg.js.map → chains-Bpb0W4ax.js.map} +1 -1
  10. package/dist/_chunks/{cli-check-bE3H5Bjr.js → cli-check-D6VolrDV.js} +3 -3
  11. package/dist/_chunks/{cli-check-bE3H5Bjr.js.map → cli-check-D6VolrDV.js.map} +1 -1
  12. package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js → cli-schema-sync-D6rO-VcS.js} +2 -2
  13. package/dist/_chunks/{cli-schema-sync-DTy_-Msq.js.map → cli-schema-sync-D6rO-VcS.js.map} +1 -1
  14. package/dist/_chunks/{convention-lint-n3RJLgww.js → convention-lint-fRkwVwEH.js} +27 -4
  15. package/dist/_chunks/convention-lint-fRkwVwEH.js.map +1 -0
  16. package/dist/_chunks/error-boundary-BfPHZjm0.js +1050 -0
  17. package/dist/_chunks/error-boundary-BfPHZjm0.js.map +1 -0
  18. package/dist/_chunks/{live-graph-BXDsdzBv.js → live-graph-D_2D32Ad.js} +3 -3
  19. package/dist/_chunks/{live-graph-BXDsdzBv.js.map → live-graph-D_2D32Ad.js.map} +1 -1
  20. package/dist/_chunks/{logger-DDirEsn7.js → logger-uLBuGKDI.js} +471 -440
  21. package/dist/_chunks/logger-uLBuGKDI.js.map +1 -0
  22. package/dist/_chunks/{navigation-root-B00jjGd5.js → navigation-context-D0TU0Jog.js} +3 -101
  23. package/dist/_chunks/navigation-context-D0TU0Jog.js.map +1 -0
  24. package/dist/_chunks/navigation-root-mHSK9psY.js +126 -0
  25. package/dist/_chunks/{navigation-root-B00jjGd5.js.map → navigation-root-mHSK9psY.js.map} +1 -1
  26. package/dist/_chunks/{poison-scan-BoDLgbix.js → poison-scan-Bm9Yyqk9.js} +2 -2
  27. package/dist/_chunks/{poison-scan-BoDLgbix.js.map → poison-scan-Bm9Yyqk9.js.map} +1 -1
  28. package/dist/_chunks/{scanner-tdFPvDYi.js → scanner-AiazgH_f.js} +6 -5
  29. package/dist/_chunks/scanner-AiazgH_f.js.map +1 -0
  30. package/dist/_chunks/{segment-keys-BhqoHiLc.js → segment-keys-lqtdookO.js} +2 -65
  31. package/dist/_chunks/segment-keys-lqtdookO.js.map +1 -0
  32. package/dist/_chunks/{ssr-data-BQGhTPAK.js → ssr-data-D6T6Y3ef.js} +4 -26
  33. package/dist/_chunks/ssr-data-D6T6Y3ef.js.map +1 -0
  34. package/dist/_chunks/state-FippDgxN.js +52 -0
  35. package/dist/_chunks/state-FippDgxN.js.map +1 -0
  36. package/dist/_chunks/status-page-marker-DwQBrLBz.js +496 -0
  37. package/dist/_chunks/status-page-marker-DwQBrLBz.js.map +1 -0
  38. package/dist/_chunks/{walkers-DNX05dC0.js → walkers-B6XUtmqK.js} +2 -2
  39. package/dist/_chunks/{walkers-DNX05dC0.js.map → walkers-B6XUtmqK.js.map} +1 -1
  40. package/dist/analyze/crawl-entry.js +2 -2
  41. package/dist/analyze/graph-command.js +2 -2
  42. package/dist/cache/index.js +1 -1
  43. package/dist/cli.js +2 -2
  44. package/dist/client/browser-entry/action-dispatch.d.ts +6 -4
  45. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  46. package/dist/client/browser-entry/action-queue.d.ts +44 -0
  47. package/dist/client/browser-entry/action-queue.d.ts.map +1 -0
  48. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  49. package/dist/client/deny-last-resort.d.ts +29 -0
  50. package/dist/client/deny-last-resort.d.ts.map +1 -0
  51. package/dist/client/error-boundary.d.ts +48 -2
  52. package/dist/client/error-boundary.d.ts.map +1 -1
  53. package/dist/client/error-boundary.js +2 -2
  54. package/dist/client/history.d.ts +21 -2
  55. package/dist/client/history.d.ts.map +1 -1
  56. package/dist/client/index.js +34 -14
  57. package/dist/client/index.js.map +1 -1
  58. package/dist/client/internal.d.ts +1 -0
  59. package/dist/client/internal.d.ts.map +1 -1
  60. package/dist/client/internal.js +272 -1225
  61. package/dist/client/internal.js.map +1 -1
  62. package/dist/client/link.d.ts.map +1 -1
  63. package/dist/client/navigation-commit.d.ts +12 -19
  64. package/dist/client/navigation-commit.d.ts.map +1 -1
  65. package/dist/client/navigation-transition.d.ts +62 -11
  66. package/dist/client/navigation-transition.d.ts.map +1 -1
  67. package/dist/client/router-effects.d.ts +9 -8
  68. package/dist/client/router-effects.d.ts.map +1 -1
  69. package/dist/client/router-lifecycle.d.ts +60 -17
  70. package/dist/client/router-lifecycle.d.ts.map +1 -1
  71. package/dist/client/router-pipeline.d.ts +7 -4
  72. package/dist/client/router-pipeline.d.ts.map +1 -1
  73. package/dist/client/router-types.d.ts +63 -8
  74. package/dist/client/router-types.d.ts.map +1 -1
  75. package/dist/client/router.d.ts.map +1 -1
  76. package/dist/client/rsc-fetch.d.ts +0 -9
  77. package/dist/client/rsc-fetch.d.ts.map +1 -1
  78. package/dist/client/segment-cache.d.ts +23 -8
  79. package/dist/client/segment-cache.d.ts.map +1 -1
  80. package/dist/client/state.d.ts +16 -0
  81. package/dist/client/state.d.ts.map +1 -1
  82. package/dist/client/status-page-marker.d.ts +25 -0
  83. package/dist/client/status-page-marker.d.ts.map +1 -0
  84. package/dist/cookies/index.js +1 -1
  85. package/dist/dev-tools/holding-server.d.ts +4 -17
  86. package/dist/dev-tools/holding-server.d.ts.map +1 -1
  87. package/dist/index.d.ts.map +1 -1
  88. package/dist/index.js +74 -175
  89. package/dist/index.js.map +1 -1
  90. package/dist/plugins/dev-server.d.ts.map +1 -1
  91. package/dist/routing/codegen-write.d.ts.map +1 -1
  92. package/dist/routing/index.js +2 -2
  93. package/dist/routing/interception.d.ts +2 -2
  94. package/dist/routing/slot-placement.d.ts +2 -2
  95. package/dist/server/access-gate.d.ts +73 -1
  96. package/dist/server/access-gate.d.ts.map +1 -1
  97. package/dist/server/action-handler.d.ts.map +1 -1
  98. package/dist/server/actions.d.ts +16 -1
  99. package/dist/server/actions.d.ts.map +1 -1
  100. package/dist/server/als-registry.d.ts +3 -9
  101. package/dist/server/als-registry.d.ts.map +1 -1
  102. package/dist/server/children-interception.d.ts +1 -1
  103. package/dist/server/default-status-page.d.ts +2 -2
  104. package/dist/server/default-status-page.d.ts.map +1 -1
  105. package/dist/server/deny-boundary.d.ts +15 -9
  106. package/dist/server/deny-boundary.d.ts.map +1 -1
  107. package/dist/server/deny-renderer.d.ts.map +1 -1
  108. package/dist/server/error-boundary-wrapper.d.ts +21 -4
  109. package/dist/server/error-boundary-wrapper.d.ts.map +1 -1
  110. package/dist/server/error-response-headers.d.ts +3 -0
  111. package/dist/server/error-response-headers.d.ts.map +1 -0
  112. package/dist/server/index.js +3 -3
  113. package/dist/server/index.js.map +1 -1
  114. package/dist/server/internal.d.ts +1 -2
  115. package/dist/server/internal.d.ts.map +1 -1
  116. package/dist/server/internal.js +2339 -2506
  117. package/dist/server/internal.js.map +1 -1
  118. package/dist/server/metadata-collector.d.ts +2 -5
  119. package/dist/server/metadata-collector.d.ts.map +1 -1
  120. package/dist/server/param-coercion.d.ts +10 -3
  121. package/dist/server/param-coercion.d.ts.map +1 -1
  122. package/dist/server/pipeline-outcome.d.ts.map +1 -1
  123. package/dist/server/pipeline-phases.d.ts +11 -0
  124. package/dist/server/pipeline-phases.d.ts.map +1 -1
  125. package/dist/server/port-resolution.d.ts +3 -89
  126. package/dist/server/port-resolution.d.ts.map +1 -1
  127. package/dist/server/primitives.d.ts +38 -10
  128. package/dist/server/primitives.d.ts.map +1 -1
  129. package/dist/server/response-cache-policy.d.ts +3 -0
  130. package/dist/server/response-cache-policy.d.ts.map +1 -0
  131. package/dist/server/route-element-builder.d.ts +11 -41
  132. package/dist/server/route-element-builder.d.ts.map +1 -1
  133. package/dist/server/route-element-helpers.d.ts +12 -0
  134. package/dist/server/route-element-helpers.d.ts.map +1 -0
  135. package/dist/server/route-module-loader.d.ts +37 -0
  136. package/dist/server/route-module-loader.d.ts.map +1 -0
  137. package/dist/server/rsc-cache-key-guard.d.ts.map +1 -1
  138. package/dist/server/rsc-entry/action-middleware-runner.d.ts.map +1 -1
  139. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  140. package/dist/server/rsc-entry/render-route.d.ts.map +1 -1
  141. package/dist/server/rsc-entry/rsc-payload.d.ts +22 -1
  142. package/dist/server/rsc-entry/rsc-payload.d.ts.map +1 -1
  143. package/dist/server/rsc-entry/rsc-stream.d.ts +4 -11
  144. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  145. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  146. package/dist/server/rsc-error-envelope.d.ts +11 -0
  147. package/dist/server/rsc-error-envelope.d.ts.map +1 -0
  148. package/dist/server/skippable-prefix.d.ts +18 -15
  149. package/dist/server/skippable-prefix.d.ts.map +1 -1
  150. package/dist/server/slot-resolver.d.ts.map +1 -1
  151. package/dist/server/slot-subtree-contain.d.ts +54 -0
  152. package/dist/server/slot-subtree-contain.d.ts.map +1 -0
  153. package/dist/server/stream-utils.d.ts.map +1 -1
  154. package/dist/server/utils/element-type.d.ts +10 -0
  155. package/dist/server/utils/element-type.d.ts.map +1 -1
  156. package/dist/shared/rsc-error-envelope.d.ts +0 -9
  157. package/dist/shared/rsc-error-envelope.d.ts.map +1 -1
  158. package/dist/shared/status-reason-phrase.d.ts +26 -0
  159. package/dist/shared/status-reason-phrase.d.ts.map +1 -0
  160. package/docs/api/30-api-server.mdx +4 -2
  161. package/docs/api/31-api-client.mdx +5 -1
  162. package/docs/api/36-cli.mdx +5 -3
  163. package/docs/learn/12-error-handling.mdx +5 -1
  164. package/package.json +10 -10
  165. package/src/client/browser-entry/action-dispatch.ts +166 -99
  166. package/src/client/browser-entry/action-queue.ts +90 -0
  167. package/src/client/browser-entry/router-init.ts +60 -35
  168. package/src/client/deny-last-resort.tsx +54 -0
  169. package/src/client/error-boundary.tsx +144 -42
  170. package/src/client/history.ts +52 -3
  171. package/src/client/internal.ts +1 -0
  172. package/src/client/link.tsx +70 -35
  173. package/src/client/navigation-commit.ts +79 -27
  174. package/src/client/navigation-transition.ts +176 -127
  175. package/src/client/router-effects.ts +14 -17
  176. package/src/client/router-lifecycle.ts +181 -115
  177. package/src/client/router-pipeline.ts +94 -71
  178. package/src/client/router-types.ts +61 -7
  179. package/src/client/router.ts +147 -74
  180. package/src/client/rsc-fetch.ts +0 -13
  181. package/src/client/segment-cache.ts +43 -10
  182. package/src/client/state.ts +26 -0
  183. package/src/client/status-page-marker.tsx +32 -0
  184. package/src/dev-tools/holding-server.ts +4 -17
  185. package/src/index.ts +18 -34
  186. package/src/plugins/dev-server.ts +2 -1
  187. package/src/react-canary.d.ts +2 -0
  188. package/src/routing/codegen-write.ts +2 -0
  189. package/src/routing/interception.ts +2 -2
  190. package/src/routing/slot-placement.ts +2 -2
  191. package/src/server/access-gate.tsx +89 -21
  192. package/src/server/action-client.ts +2 -2
  193. package/src/server/action-handler.ts +23 -10
  194. package/src/server/actions.ts +81 -34
  195. package/src/server/als-registry.ts +3 -9
  196. package/src/server/children-interception.ts +1 -1
  197. package/src/server/default-status-page.ts +7 -47
  198. package/src/server/deny-boundary.ts +45 -28
  199. package/src/server/deny-renderer.ts +6 -2
  200. package/src/server/error-boundary-wrapper.ts +23 -4
  201. package/src/server/error-response-headers.ts +18 -0
  202. package/src/server/internal.ts +2 -10
  203. package/src/server/metadata-collector.ts +3 -18
  204. package/src/server/param-coercion.ts +13 -4
  205. package/src/server/pipeline-outcome.ts +35 -13
  206. package/src/server/pipeline-phases.ts +22 -16
  207. package/src/server/port-resolution.ts +3 -165
  208. package/src/server/prebuilt-builder.ts +4 -4
  209. package/src/server/primitives.ts +75 -11
  210. package/src/server/response-cache-policy.ts +45 -0
  211. package/src/server/route-element-builder.ts +149 -412
  212. package/src/server/route-element-helpers.ts +37 -0
  213. package/src/server/route-handler.ts +2 -2
  214. package/src/server/route-module-loader.ts +161 -0
  215. package/src/server/rsc-cache-key-guard.ts +2 -42
  216. package/src/server/rsc-entry/action-middleware-runner.ts +4 -4
  217. package/src/server/rsc-entry/api-handler.ts +5 -5
  218. package/src/server/rsc-entry/error-renderer.ts +3 -4
  219. package/src/server/rsc-entry/helpers.ts +1 -1
  220. package/src/server/rsc-entry/index.ts +3 -3
  221. package/src/server/rsc-entry/render-route.ts +4 -8
  222. package/src/server/rsc-entry/rsc-payload.ts +59 -42
  223. package/src/server/rsc-entry/rsc-stream.ts +48 -27
  224. package/src/server/rsc-entry/ssr-renderer.ts +6 -10
  225. package/src/server/rsc-error-envelope.ts +18 -0
  226. package/src/server/skippable-prefix.ts +105 -7
  227. package/src/server/slot-resolver.ts +43 -12
  228. package/src/server/slot-subtree-contain.ts +255 -0
  229. package/src/server/stream-utils.ts +12 -8
  230. package/src/server/utils/element-type.ts +18 -2
  231. package/src/shared/rsc-error-envelope.ts +0 -15
  232. package/src/shared/status-reason-phrase.ts +61 -0
  233. package/dist/_chunks/actions-BS-m5SLv.js.map +0 -1
  234. package/dist/_chunks/convention-lint-n3RJLgww.js.map +0 -1
  235. package/dist/_chunks/error-boundary-BvRCCmbN.js +0 -353
  236. package/dist/_chunks/error-boundary-BvRCCmbN.js.map +0 -1
  237. package/dist/_chunks/logger-DDirEsn7.js.map +0 -1
  238. package/dist/_chunks/mdx-file-CXyHGUpS.js +0 -25
  239. package/dist/_chunks/mdx-file-CXyHGUpS.js.map +0 -1
  240. package/dist/_chunks/router-ref-8gr8qsxN.js +0 -28
  241. package/dist/_chunks/router-ref-8gr8qsxN.js.map +0 -1
  242. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js +0 -40
  243. package/dist/_chunks/rsc-error-envelope-tT5PJs4q.js.map +0 -1
  244. package/dist/_chunks/scanner-tdFPvDYi.js.map +0 -1
  245. package/dist/_chunks/segment-keys-BhqoHiLc.js.map +0 -1
  246. package/dist/_chunks/ssr-data-BQGhTPAK.js.map +0 -1
  247. package/dist/server/tree-builder.d.ts +0 -150
  248. package/dist/server/tree-builder.d.ts.map +0 -1
  249. 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
- }