@timber-js/app 0.2.0-alpha.163 → 0.2.0-alpha.164

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 (102) hide show
  1. package/dist/_chunks/{actions-cjklt63G.js → actions-CSDD6x7U.js} +2 -2
  2. package/dist/_chunks/{actions-cjklt63G.js.map → actions-CSDD6x7U.js.map} +1 -1
  3. package/dist/_chunks/{cache-api-CzYUlgXA.js → cache-api-eb1gydM7.js} +41 -13
  4. package/dist/_chunks/cache-api-eb1gydM7.js.map +1 -0
  5. package/dist/_chunks/{cli-schema-sync-NfLbLnDw.js → cli-schema-sync-mGfRbjh2.js} +2 -2
  6. package/dist/_chunks/{cli-schema-sync-NfLbLnDw.js.map → cli-schema-sync-mGfRbjh2.js.map} +1 -1
  7. package/dist/_chunks/{plugin-context-DeAxFRMq.js → plugin-context-BnaiU_cF.js} +37 -2
  8. package/dist/_chunks/plugin-context-BnaiU_cF.js.map +1 -0
  9. package/dist/_chunks/{walkers-9mz9T7mb.js → walkers-BL3MCMgO.js} +2 -2
  10. package/dist/_chunks/{walkers-9mz9T7mb.js.map → walkers-BL3MCMgO.js.map} +1 -1
  11. package/dist/adapters/cloudflare-kv-cache.d.ts +1 -0
  12. package/dist/adapters/cloudflare-kv-cache.d.ts.map +1 -1
  13. package/dist/adapters/cloudflare-kv-cache.js.map +1 -1
  14. package/dist/adapters/nitro.d.ts +11 -0
  15. package/dist/adapters/nitro.d.ts.map +1 -1
  16. package/dist/adapters/nitro.js +77 -64
  17. package/dist/adapters/nitro.js.map +1 -1
  18. package/dist/cache/index.d.ts +3 -0
  19. package/dist/cache/index.d.ts.map +1 -1
  20. package/dist/cache/index.js +1 -1
  21. package/dist/cache/redis-handler.d.ts +1 -0
  22. package/dist/cache/redis-handler.d.ts.map +1 -1
  23. package/dist/cache/tag-aware-handler.d.ts +1 -0
  24. package/dist/cache/tag-aware-handler.d.ts.map +1 -1
  25. package/dist/cache/timber-cache.d.ts.map +1 -1
  26. package/dist/cli.js +2 -2
  27. package/dist/client/slot-context.d.ts +29 -0
  28. package/dist/client/slot-context.d.ts.map +1 -0
  29. package/dist/client/slot-outlet.d.ts +16 -0
  30. package/dist/client/slot-outlet.d.ts.map +1 -0
  31. package/dist/client/slot-provider.d.ts +20 -0
  32. package/dist/client/slot-provider.d.ts.map +1 -0
  33. package/dist/config-types.d.ts +2 -1
  34. package/dist/config-types.d.ts.map +1 -1
  35. package/dist/dev-tools/logs.d.ts.map +1 -1
  36. package/dist/index.js +293 -124
  37. package/dist/index.js.map +1 -1
  38. package/dist/plugin-context.d.ts +27 -0
  39. package/dist/plugin-context.d.ts.map +1 -1
  40. package/dist/plugins/cache.d.ts.map +1 -1
  41. package/dist/plugins/client-chunks.d.ts.map +1 -1
  42. package/dist/plugins/dev-server.d.ts.map +1 -1
  43. package/dist/plugins/prebuilt-options-analysis.d.ts +40 -0
  44. package/dist/plugins/prebuilt-options-analysis.d.ts.map +1 -0
  45. package/dist/plugins/prebuilt.d.ts.map +1 -1
  46. package/dist/plugins/prerender-sugar.d.ts.map +1 -1
  47. package/dist/routing/index.js +2 -2
  48. package/dist/server/html-injector-core.d.ts +30 -9
  49. package/dist/server/html-injector-core.d.ts.map +1 -1
  50. package/dist/server/html-injectors.d.ts.map +1 -1
  51. package/dist/server/index.js +1 -1
  52. package/dist/server/internal.js +346 -51
  53. package/dist/server/internal.js.map +1 -1
  54. package/dist/server/node-stream-transforms.d.ts.map +1 -1
  55. package/dist/server/pipeline-phases.d.ts.map +1 -1
  56. package/dist/server/prebuilt/cache-key.d.ts +4 -0
  57. package/dist/server/prebuilt/cache-key.d.ts.map +1 -1
  58. package/dist/server/prebuilt/key-discipline.d.ts +23 -0
  59. package/dist/server/prebuilt/key-discipline.d.ts.map +1 -0
  60. package/dist/server/prebuilt/slots.d.ts +74 -0
  61. package/dist/server/prebuilt/slots.d.ts.map +1 -0
  62. package/dist/server/prebuilt-builder.d.ts.map +1 -1
  63. package/dist/server/prebuilt-runtime.d.ts +12 -2
  64. package/dist/server/prebuilt-runtime.d.ts.map +1 -1
  65. package/dist/server/rsc-entry/deny-fallback.d.ts +29 -0
  66. package/dist/server/rsc-entry/deny-fallback.d.ts.map +1 -0
  67. package/dist/server/rsc-entry/index.d.ts.map +1 -1
  68. package/docs/api/34-api-config.mdx +165 -3
  69. package/docs/learn/00-introduction.mdx +78 -44
  70. package/docs/learn/13-configuration.mdx +27 -8
  71. package/package.json +3 -2
  72. package/src/adapters/cloudflare-kv-cache.ts +5 -1
  73. package/src/adapters/nitro.ts +82 -64
  74. package/src/cache/index.ts +25 -2
  75. package/src/cache/redis-handler.ts +27 -7
  76. package/src/cache/tag-aware-handler.ts +5 -1
  77. package/src/cache/timber-cache.ts +21 -10
  78. package/src/client/slot-context.ts +48 -0
  79. package/src/client/slot-outlet.tsx +22 -0
  80. package/src/client/slot-provider.tsx +25 -0
  81. package/src/config-types.ts +2 -1
  82. package/src/dev-tools/logs.ts +7 -0
  83. package/src/plugin-context.ts +54 -0
  84. package/src/plugins/cache.ts +1 -2
  85. package/src/plugins/client-chunks.ts +42 -1
  86. package/src/plugins/dev-server.ts +12 -69
  87. package/src/plugins/prebuilt-options-analysis.ts +175 -0
  88. package/src/plugins/prebuilt.ts +82 -127
  89. package/src/plugins/prerender-sugar.ts +1 -2
  90. package/src/server/html-injector-core.ts +85 -27
  91. package/src/server/html-injectors.ts +5 -1
  92. package/src/server/node-stream-transforms.ts +6 -1
  93. package/src/server/pipeline-phases.ts +4 -1
  94. package/src/server/prebuilt/cache-key.ts +74 -0
  95. package/src/server/prebuilt/key-discipline.ts +53 -0
  96. package/src/server/prebuilt/slots.ts +167 -0
  97. package/src/server/prebuilt-builder.ts +57 -23
  98. package/src/server/prebuilt-runtime.ts +144 -60
  99. package/src/server/rsc-entry/deny-fallback.ts +92 -0
  100. package/src/server/rsc-entry/index.ts +16 -70
  101. package/dist/_chunks/cache-api-CzYUlgXA.js.map +0 -1
  102. package/dist/_chunks/plugin-context-DeAxFRMq.js.map +0 -1
package/dist/index.js CHANGED
@@ -2,23 +2,26 @@ import { a as __esmMin, o as __exportAll, s as __toCommonJS } from "./_chunks/di
2
2
  import { c as isDynamicMetadataExtension, l as isMetadataRouteServePath, r as canonicalize } from "./_chunks/canonicalize-P41GR6tY.js";
3
3
  import { t as matchUrlParts } from "./_chunks/tree-match-D2l830j2.js";
4
4
  import { h as swallow } from "./_chunks/logger-B_O6-mdJ.js";
5
- import { i as scanRoutes } from "./_chunks/cli-schema-sync-NfLbLnDw.js";
6
- import { a as generateRouteMap, c as fileHasDirective, d as getPrerenderExport, i as validateSchemaAgainstRoutes, l as fileHasExport, n as collectInterceptionRewrites, o as fileHasAnyExport, s as fileHasDefaultExport, t as collectLeafRoutes, u as fileHasStarExport } from "./_chunks/walkers-9mz9T7mb.js";
7
- import { a as resolveAppDir, c as createNoopTimer, n as loadTimberConfigFile, o as resolveBuildDir, r as mergeFileConfig, s as resolveClientJavascript, t as createPluginContext } from "./_chunks/plugin-context-DeAxFRMq.js";
5
+ import { i as scanRoutes } from "./_chunks/cli-schema-sync-mGfRbjh2.js";
6
+ import { a as generateRouteMap, c as fileHasDirective, d as getPrerenderExport, i as validateSchemaAgainstRoutes, l as fileHasExport, n as collectInterceptionRewrites, o as fileHasAnyExport, s as fileHasDefaultExport, t as collectLeafRoutes, u as fileHasStarExport } from "./_chunks/walkers-BL3MCMgO.js";
7
+ import { a as resolveAppDir, c as createNoopTimer, n as loadTimberConfigFile, o as resolveBuildDir, r as mergeFileConfig, s as resolveClientJavascript, t as createPluginContext } from "./_chunks/plugin-context-BnaiU_cF.js";
8
8
  import { t as fnv1aHash } from "./_chunks/fast-hash-D6hIVt1Y.js";
9
9
  import { t as formatSize } from "./_chunks/format-CfwjgPz9.js";
10
10
  import { basename, dirname, extname, join, normalize, relative, resolve, win32 } from "node:path";
11
11
  import { builtinModules, createRequire } from "node:module";
12
12
  import react, { reactCompilerPreset } from "@vitejs/plugin-react";
13
13
  import { existsSync, readFileSync, statSync } from "node:fs";
14
+ import nodeHTTP from "node:http";
15
+ import { Readable, pipeline } from "node:stream";
16
+ import { pipeline as pipeline$1 } from "node:stream/promises";
17
+ import "node:https";
18
+ import "node:http2";
14
19
  import { fileURLToPath, pathToFileURL } from "node:url";
15
20
  import { styleText } from "node:util";
16
21
  import { constants, createGzip, gzipSync } from "node:zlib";
17
- import { Readable, pipeline } from "node:stream";
18
22
  import { copyFile, mkdir, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
19
23
  import { createHash, randomUUID } from "node:crypto";
20
24
  import { parseAst } from "vite";
21
- import http from "node:http";
22
25
  //#region src/plugins/content.ts
23
26
  var CONFIG_FILE_NAMES$1 = [
24
27
  "content-collections.ts",
@@ -104,6 +107,98 @@ function timberContent(ctx) {
104
107
  };
105
108
  }
106
109
  //#endregion
110
+ //#region ../../node_modules/.pnpm/srvx@0.11.17/node_modules/srvx/dist/adapters/node.mjs
111
+ async function sendNodeResponse(nodeRes, webRes) {
112
+ if (!webRes) {
113
+ nodeRes.statusCode = 500;
114
+ return endNodeResponse(nodeRes);
115
+ }
116
+ if (webRes._toNodeResponse) {
117
+ const res = webRes._toNodeResponse();
118
+ if (res.body) {
119
+ if (res.body instanceof ReadableStream) {
120
+ writeHead(nodeRes, res.status, res.statusText, res.headers);
121
+ return streamBody(res.body, nodeRes);
122
+ } else if (typeof res.body?.pipe === "function") return pipeBody(res.body, nodeRes, res.status, res.statusText, res.headers);
123
+ writeHead(nodeRes, res.status, res.statusText, res.headers);
124
+ nodeRes.write(res.body);
125
+ } else writeHead(nodeRes, res.status, res.statusText, res.headers);
126
+ return endNodeResponse(nodeRes);
127
+ }
128
+ const rawHeaders = [...webRes.headers];
129
+ writeHead(nodeRes, webRes.status, webRes.statusText, rawHeaders);
130
+ return webRes.body ? streamBody(webRes.body, nodeRes) : endNodeResponse(nodeRes);
131
+ }
132
+ function writeHead(nodeRes, status, statusText, rawHeaders) {
133
+ const writeHeaders = rawHeaders.flat();
134
+ if (!nodeRes.headersSent) if (nodeRes.req?.httpVersion === "2.0") nodeRes.writeHead(status, writeHeaders);
135
+ else nodeRes.writeHead(status, statusText, writeHeaders);
136
+ }
137
+ function endNodeResponse(nodeRes) {
138
+ return new Promise((resolve) => nodeRes.end(resolve));
139
+ }
140
+ function pipeBody(stream, nodeRes, status, statusText, headers) {
141
+ if (nodeRes.destroyed) {
142
+ stream.destroy?.();
143
+ return;
144
+ }
145
+ if (typeof stream.on !== "function" || typeof stream.destroy !== "function") {
146
+ writeHead(nodeRes, status, statusText, headers);
147
+ stream.pipe(nodeRes);
148
+ return new Promise((resolve) => nodeRes.on("close", resolve));
149
+ }
150
+ if (stream.destroyed) {
151
+ writeHead(nodeRes, 500, "Internal Server Error", []);
152
+ return endNodeResponse(nodeRes);
153
+ }
154
+ return new Promise((resolve) => {
155
+ function onEarlyError() {
156
+ stream.off("readable", onReadable);
157
+ stream.destroy();
158
+ writeHead(nodeRes, 500, "Internal Server Error", []);
159
+ endNodeResponse(nodeRes).then(resolve);
160
+ }
161
+ function onReadable() {
162
+ stream.off("error", onEarlyError);
163
+ if (nodeRes.destroyed) {
164
+ stream.destroy();
165
+ return resolve();
166
+ }
167
+ writeHead(nodeRes, status, statusText, headers);
168
+ pipeline$1(stream, nodeRes).catch(() => {}).then(() => resolve());
169
+ }
170
+ stream.once("error", onEarlyError);
171
+ stream.once("readable", onReadable);
172
+ });
173
+ }
174
+ function streamBody(stream, nodeRes) {
175
+ if (nodeRes.destroyed) {
176
+ stream.cancel();
177
+ return;
178
+ }
179
+ const reader = stream.getReader();
180
+ function streamCancel(error) {
181
+ reader.cancel(error).catch(() => {});
182
+ if (error) nodeRes.destroy(error);
183
+ }
184
+ function streamHandle({ done, value }) {
185
+ try {
186
+ if (done) nodeRes.end();
187
+ else if (nodeRes.write(value)) reader.read().then(streamHandle, streamCancel);
188
+ else nodeRes.once("drain", () => reader.read().then(streamHandle, streamCancel));
189
+ } catch (error) {
190
+ streamCancel(error instanceof Error ? error : void 0);
191
+ }
192
+ }
193
+ nodeRes.on("close", streamCancel);
194
+ nodeRes.on("error", streamCancel);
195
+ reader.read().then(streamHandle, streamCancel);
196
+ return reader.closed.catch(streamCancel).finally(() => {
197
+ nodeRes.off("close", streamCancel);
198
+ nodeRes.off("error", streamCancel);
199
+ });
200
+ }
201
+ //#endregion
107
202
  //#region src/dev-tools/stack-classifier.ts
108
203
  /** Parse file/line/col from a stack frame line. */
109
204
  function parseFrame(frameLine) {
@@ -1500,7 +1595,10 @@ function createTimberMiddleware(server, ctx) {
1500
1595
  try {
1501
1596
  const webRequest = toWebRequest(req);
1502
1597
  const wrapper = server[Symbol.for("timber:dev-request-wrapper")];
1503
- await sendWebResponse(res, compressResponse(webRequest, wrapper ? await wrapper(() => handler(webRequest)) : await handler(webRequest)));
1598
+ const finalResponse = compressResponse(webRequest, wrapper ? await wrapper(() => handler(webRequest)) : await handler(webRequest));
1599
+ res.writeHead(finalResponse.status, [...finalResponse.headers].flat());
1600
+ res.flushHeaders();
1601
+ await sendNodeResponse(res, finalResponse);
1504
1602
  } catch (error) {
1505
1603
  if (error instanceof Error) {
1506
1604
  const displayError = error.name === "SsrStreamError" && error.cause instanceof Error ? error.cause : error;
@@ -1596,44 +1694,6 @@ function nodeReadableToWebStream(nodeStream) {
1596
1694
  } });
1597
1695
  }
1598
1696
  /**
1599
- * Write a Web Response to a Node ServerResponse.
1600
- *
1601
- * Copies status code, headers, and streams the body.
1602
- */
1603
- async function sendWebResponse(nodeRes, webResponse) {
1604
- nodeRes.statusCode = webResponse.status;
1605
- webResponse.headers.forEach((value, key) => {
1606
- if (key.toLowerCase() !== "set-cookie") nodeRes.setHeader(key, value);
1607
- });
1608
- const setCookies = webResponse.headers.getSetCookie();
1609
- if (setCookies.length > 0) nodeRes.setHeader("Set-Cookie", setCookies);
1610
- if (!webResponse.body) {
1611
- nodeRes.end();
1612
- return;
1613
- }
1614
- nodeRes.flushHeaders();
1615
- const reader = webResponse.body.getReader();
1616
- let clientDisconnected = false;
1617
- const onClose = () => {
1618
- clientDisconnected = true;
1619
- reader.cancel("Client disconnected").catch(() => {});
1620
- };
1621
- nodeRes.on("close", onClose);
1622
- try {
1623
- while (true) {
1624
- const { done, value } = await reader.read();
1625
- if (done) break;
1626
- nodeRes.write(value);
1627
- }
1628
- } catch (err) {
1629
- if (!clientDisconnected) throw err;
1630
- } finally {
1631
- nodeRes.off("close", onClose);
1632
- reader.releaseLock();
1633
- if (!nodeRes.writableEnded) nodeRes.end();
1634
- }
1635
- }
1636
- /**
1637
1697
  * Check if a URL is a Vite-internal request that should be passed through.
1638
1698
  */
1639
1699
  function isViteInternal(url) {
@@ -6521,6 +6581,7 @@ function patchConsole(server, projectRoot) {
6521
6581
  console[level] = (...args) => {
6522
6582
  originals.get(level)(...args);
6523
6583
  if (isFrameworkInternalCaller()) return;
6584
+ if (typeof args[0] === "string" && args[0].includes("[console.")) return;
6524
6585
  try {
6525
6586
  const payload = {
6526
6587
  level,
@@ -6748,13 +6809,39 @@ function findAppBoundary(path, appPrefix) {
6748
6809
  };
6749
6810
  }
6750
6811
  /**
6812
+ * Check if a string is a bare import specifier (package name, not a path).
6813
+ * Bare specifiers don't start with `.`, `/`, or contain `:/`.
6814
+ */
6815
+ function isBareSpecifier(id) {
6816
+ return !id.startsWith(".") && !id.startsWith("/") && !id.includes(":/");
6817
+ }
6818
+ /**
6819
+ * Look up facade ancestors for a module ID, handling bare package specifiers.
6820
+ *
6821
+ * The facade ancestry map is keyed by absolute resolved paths (from Rollup's
6822
+ * moduleIds), but the RSC plugin passes bare package names (e.g. 'streamdown')
6823
+ * as meta.id for npm package client references (TIM-1108). When a direct
6824
+ * lookup misses and the id is a bare specifier, scan map keys for a matching
6825
+ * node_modules path.
6826
+ */
6827
+ function lookupFacades(id, sharedModuleFacades) {
6828
+ const direct = sharedModuleFacades.get(id);
6829
+ if (direct) return direct;
6830
+ if (!isBareSpecifier(id)) return void 0;
6831
+ const needle = `/node_modules/${id}/`;
6832
+ let merged;
6833
+ for (const [key, facades] of sharedModuleFacades) if (key.includes(needle)) if (!merged) merged = [...facades];
6834
+ else merged.push(...facades);
6835
+ return merged;
6836
+ }
6837
+ /**
6751
6838
  * For a `shared:` client reference, check the facade ancestry map to
6752
6839
  * see if all consuming facades belong to the same route boundary.
6753
6840
  * Returns the boundary segment name, or null if the module is
6754
6841
  * genuinely shared across multiple boundaries.
6755
6842
  */
6756
6843
  function reclassifySharedModule(meta, appPrefix, root, sharedModuleFacades) {
6757
- const facades = sharedModuleFacades.get(meta.id);
6844
+ const facades = lookupFacades(meta.id, sharedModuleFacades);
6758
6845
  if (!facades || facades.length === 0) return null;
6759
6846
  const boundaries = /* @__PURE__ */ new Set();
6760
6847
  for (const facade of facades) {
@@ -7316,7 +7403,7 @@ function timberCacheTransform(ctx) {
7316
7403
  if (id.includes("node_modules")) return;
7317
7404
  let program;
7318
7405
  try {
7319
- program = parseAst(code, { lang: "tsx" });
7406
+ program = ctx.parseCached.parse(code);
7320
7407
  } catch {
7321
7408
  return;
7322
7409
  }
@@ -7510,6 +7597,122 @@ function recordCallSiteProps(ctx, componentId, props) {
7510
7597
  }
7511
7598
  }
7512
7599
  //#endregion
7600
+ //#region src/plugins/prebuilt-options-analysis.ts
7601
+ var EXPRESSION_WRAPPER_TYPES = /* @__PURE__ */ new Set([
7602
+ "TSAsExpression",
7603
+ "TSSatisfiesExpression",
7604
+ "TSNonNullExpression",
7605
+ "ParenthesizedExpression"
7606
+ ]);
7607
+ /** Unwrap TS expression wrappers (`as`, `satisfies`, `!`, parens). */
7608
+ function unwrapExpression(node) {
7609
+ let current = node;
7610
+ while (EXPRESSION_WRAPPER_TYPES.has(current.type) && current.expression) current = current.expression;
7611
+ return current;
7612
+ }
7613
+ /**
7614
+ * Statically classify a `slots` value node against resolveSlotNames'
7615
+ * runtime rules (server/prebuilt/slots.ts):
7616
+ *
7617
+ * - `yes` — certainly an ACTIVE shape: dense, non-empty array literal of
7618
+ * non-empty string literals.
7619
+ * - `no` — certainly NOT active (absent/empty/malformed): registration
7620
+ * falls back to non-slot semantics with `prerender` preserved.
7621
+ * - `unknown` — identifiers, spreads, holes, non-literal elements.
7622
+ */
7623
+ function classifySlotsValue(node) {
7624
+ const value = unwrapExpression(node);
7625
+ if (value.type === "ArrayExpression") {
7626
+ const elements = value.elements ?? [];
7627
+ if (elements.length === 0) return "no";
7628
+ let allValidStrings = true;
7629
+ for (const el of elements) {
7630
+ if (el === null) return "no";
7631
+ if (el.type === "SpreadElement") return "unknown";
7632
+ const entry = unwrapExpression(el);
7633
+ if (entry.type !== "Literal") {
7634
+ allValidStrings = false;
7635
+ continue;
7636
+ }
7637
+ if (typeof entry.value !== "string" || entry.value.length === 0) return "no";
7638
+ }
7639
+ return allValidStrings ? "yes" : "unknown";
7640
+ }
7641
+ if (value.type === "Literal") return "no";
7642
+ if (value.type === "Identifier" && value.name === "undefined") return "no";
7643
+ return "unknown";
7644
+ }
7645
+ /** Statically classify whether a `ttl` value gives a runtime lifetime (`ttl !== undefined`). */
7646
+ function classifyTtlValue(node) {
7647
+ const value = unwrapExpression(node);
7648
+ if (value.type === "Literal") return "yes";
7649
+ if (value.type === "Identifier" && value.name === "undefined") return "no";
7650
+ return "unknown";
7651
+ }
7652
+ /** Statically classify whether a `tags` value gives a runtime lifetime (`tags != null`). */
7653
+ function classifyTagsValue(node) {
7654
+ const value = unwrapExpression(node);
7655
+ if (value.type === "ArrayExpression") return "yes";
7656
+ if (value.type === "ArrowFunctionExpression" || value.type === "FunctionExpression") return "yes";
7657
+ if (value.type === "Literal") return value.value === null ? "no" : "yes";
7658
+ if (value.type === "Identifier" && value.name === "undefined") return "no";
7659
+ return "unknown";
7660
+ }
7661
+ /**
7662
+ * Can this call site's options put the component in the prerender tier?
7663
+ * The answer is static only when the options argument is an object
7664
+ * literal:
7665
+ *
7666
+ * - literal with `prerender: true` → yes
7667
+ * - literal with `prerender: false`/absent → no (spread-free)
7668
+ * - literal with statically ACTIVE `slots` + certain runtime lifetime
7669
+ * → no — registration strips `prerender` for active slot components
7670
+ * (TIM-1173), so attributing the callsite would make the post-build
7671
+ * hook import the RSC entry and enumerate route params for entries
7672
+ * the capture pass then skips (codex P2 on PR #890)
7673
+ * - anything else (identifier, spread, non-literal values)
7674
+ * → conservatively yes — the capture pass re-checks at runtime
7675
+ *
7676
+ * Properties walk in source order with last-write-wins, mirroring
7677
+ * object-literal evaluation: `{ prerender: false, ...opts }` may end up
7678
+ * prerenderable (the spread can override), while `{ ...opts,
7679
+ * prerender: false }` is statically false (codex P2 on PR #836).
7680
+ * Computed keys are treated like spreads — they may evaluate to any of
7681
+ * the tracked names.
7682
+ */
7683
+ function mayPrerender(call) {
7684
+ if (call.arguments.length < 2) return false;
7685
+ const options = unwrapExpression(call.arguments[1]);
7686
+ if (options.type !== "ObjectExpression") return true;
7687
+ const properties = options.properties ?? [];
7688
+ let verdict = "absent";
7689
+ let slotsActive = "no";
7690
+ let ttl = "no";
7691
+ let tags = "no";
7692
+ for (const prop of properties) {
7693
+ if (prop.type === "SpreadElement" || prop.computed) {
7694
+ verdict = "unknown";
7695
+ slotsActive = "unknown";
7696
+ ttl = "unknown";
7697
+ tags = "unknown";
7698
+ continue;
7699
+ }
7700
+ if (prop.type !== "Property") continue;
7701
+ const property = prop;
7702
+ const keyName = property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0;
7703
+ if (keyName === "prerender") {
7704
+ const value = unwrapExpression(property.value);
7705
+ if (value.type === "Literal") verdict = value.value === true ? "true" : "false";
7706
+ else verdict = "unknown";
7707
+ } else if (keyName === "slots") slotsActive = classifySlotsValue(property.value);
7708
+ else if (keyName === "ttl") ttl = classifyTtlValue(property.value);
7709
+ else if (keyName === "tags") tags = classifyTagsValue(property.value);
7710
+ }
7711
+ if (verdict !== "true" && verdict !== "unknown") return false;
7712
+ if (slotsActive === "yes" && (ttl === "yes" || tags === "yes")) return false;
7713
+ return true;
7714
+ }
7715
+ //#endregion
7513
7716
  //#region src/plugins/prebuilt-capture.ts
7514
7717
  /**
7515
7718
  * Post-build prebuilt capture — Node-side orchestration (TIM-1119).
@@ -7735,59 +7938,6 @@ function importInsertionOffset(program) {
7735
7938
  for (const stmt of program.body) if (!(stmt.type === "ExpressionStatement" && typeof stmt.directive === "string")) return stmt.start;
7736
7939
  return program.body.length > 0 ? program.body[program.body.length - 1].end : 0;
7737
7940
  }
7738
- /** TS/paren wrappers that may sit between a declarator and the call. */
7739
- var EXPRESSION_WRAPPER_TYPES = /* @__PURE__ */ new Set([
7740
- "TSAsExpression",
7741
- "TSSatisfiesExpression",
7742
- "TSNonNullExpression",
7743
- "ParenthesizedExpression"
7744
- ]);
7745
- function unwrapExpression(node) {
7746
- let current = node;
7747
- while (EXPRESSION_WRAPPER_TYPES.has(current.type) && current.expression) current = current.expression;
7748
- return current;
7749
- }
7750
- /**
7751
- * Can this call site's options put the component in the prerender tier?
7752
- *
7753
- * Runtime-tier call sites (`ttl`/`tags` without `prerender`) must do NO
7754
- * build work — recording them would make the post-build hook import the
7755
- * built RSC entry (running production startup) and load route modules for
7756
- * routes that have nothing to prerender (codex P2 on PR #836). The answer
7757
- * is static only when the options argument is an object literal:
7758
- *
7759
- * - literal with `prerender: true` → yes
7760
- * - literal with `prerender: false`/absent → no (spread-free)
7761
- * - anything else (identifier, spread, non-literal `prerender` value)
7762
- * → conservatively yes — the capture pass re-checks the real options
7763
- * at runtime and skips non-prerender registrations; over-inclusion
7764
- * costs a wasted entry load, under-inclusion would silently skip
7765
- * prerendering.
7766
- */
7767
- function mayPrerender(call) {
7768
- if (call.arguments.length < 2) return false;
7769
- const options = unwrapExpression(call.arguments[1]);
7770
- if (options.type !== "ObjectExpression") return true;
7771
- const properties = options.properties ?? [];
7772
- let verdict = "absent";
7773
- for (const prop of properties) {
7774
- if (prop.type === "SpreadElement") {
7775
- verdict = "unknown";
7776
- continue;
7777
- }
7778
- if (prop.type !== "Property") continue;
7779
- const property = prop;
7780
- if (property.computed) {
7781
- verdict = "unknown";
7782
- continue;
7783
- }
7784
- if ((property.key.type === "Identifier" ? property.key.name : property.key.type === "Literal" ? String(property.key.value) : void 0) !== "prerender") continue;
7785
- const value = unwrapExpression(property.value);
7786
- if (value.type === "Literal") verdict = value.value === true ? "true" : "false";
7787
- else verdict = "unknown";
7788
- }
7789
- return verdict === "true" || verdict === "unknown";
7790
- }
7791
7941
  /**
7792
7942
  * Collect matching call sites from the allowlisted module-scope positions:
7793
7943
  * top-level (possibly exported) variable declarator initializers and
@@ -7874,13 +8024,8 @@ function collectPageAndLayoutFiles(root) {
7874
8024
  walk(root);
7875
8025
  return files;
7876
8026
  }
7877
- /**
7878
- * For a route file (page/layout), check if it imports from any prebuilt-
7879
- * declaring module and extract JSX call-site props for those bindings.
7880
- * Uses `getModuleInfo` to get the RESOLVED import targets and the module's
7881
- * transformed code (available on Rollup's ModuleInfo at generateBundle).
7882
- */
7883
- function collectCrossModuleProps(ctx, moduleId, getInfo) {
8027
+ var DEFAULT_SENTINEL = "\0default";
8028
+ async function collectCrossModuleProps(ctx, moduleId, getInfo, resolve) {
7884
8029
  const bindingNames = ctx.prebuiltBindingNames;
7885
8030
  const info = getInfo(moduleId);
7886
8031
  if (!info) return;
@@ -7891,6 +8036,7 @@ function collectCrossModuleProps(ctx, moduleId, getInfo) {
7891
8036
  for (const declId of declaringImports) {
7892
8037
  const map = bindingNames.get(declId);
7893
8038
  for (const [name, compId] of map) {
8039
+ if (name === DEFAULT_SENTINEL) continue;
7894
8040
  if (exportNameToId.has(name)) collisions.add(name);
7895
8041
  exportNameToId.set(name, compId);
7896
8042
  }
@@ -7907,24 +8053,32 @@ function collectCrossModuleProps(ctx, moduleId, getInfo) {
7907
8053
  if (!source) return;
7908
8054
  let program;
7909
8055
  try {
7910
- program = parseAst(source, { lang: "tsx" });
8056
+ program = ctx.parseCached.parse(source);
7911
8057
  } catch {
7912
8058
  return;
7913
8059
  }
8060
+ const declaringSet = new Set(declaringImports);
7914
8061
  const localToComponentId = /* @__PURE__ */ new Map();
7915
8062
  for (const stmt of program.body) {
7916
8063
  if (stmt.type !== "ImportDeclaration") continue;
7917
8064
  const importDecl = stmt;
7918
8065
  for (const spec of importDecl.specifiers) if (spec.type === "ImportSpecifier") {
7919
8066
  const exportedName = spec.imported?.name ?? spec.local.name;
7920
- const compId = exportNameToId.get(exportedName);
7921
- if (compId) localToComponentId.set(spec.local.name, compId);
8067
+ if (exportedName === "default") {
8068
+ const resolvedId = await resolve(importDecl.source.value, moduleId);
8069
+ if (resolvedId && declaringSet.has(resolvedId)) {
8070
+ const compId = bindingNames.get(resolvedId).get(DEFAULT_SENTINEL);
8071
+ if (compId) localToComponentId.set(spec.local.name, compId);
8072
+ }
8073
+ } else {
8074
+ const compId = exportNameToId.get(exportedName);
8075
+ if (compId) localToComponentId.set(spec.local.name, compId);
8076
+ }
7922
8077
  } else if (spec.type === "ImportDefaultSpecifier") {
7923
- const compId = exportNameToId.get("Component") ?? exportNameToId.get("default");
7924
- if (compId) localToComponentId.set(spec.local.name, compId);
7925
- else if (exportNameToId.size === 1) {
7926
- const [, soleId] = [...exportNameToId.entries()][0];
7927
- localToComponentId.set(spec.local.name, soleId);
8078
+ const resolvedId = await resolve(importDecl.source.value, moduleId);
8079
+ if (resolvedId && declaringSet.has(resolvedId)) {
8080
+ const compId = bindingNames.get(resolvedId).get(DEFAULT_SENTINEL);
8081
+ if (compId) localToComponentId.set(spec.local.name, compId);
7928
8082
  }
7929
8083
  }
7930
8084
  }
@@ -7949,7 +8103,7 @@ function timberPrebuilt(ctx) {
7949
8103
  if (this?.environment?.name === "client") return;
7950
8104
  let program;
7951
8105
  try {
7952
- program = parseAst(code, { lang: "tsx" });
8106
+ program = ctx.parseCached.parse(code);
7953
8107
  } catch {
7954
8108
  return;
7955
8109
  }
@@ -7978,16 +8132,28 @@ function timberPrebuilt(ctx) {
7978
8132
  ctx.prebuiltModuleComponents.set(id, componentIds);
7979
8133
  const bindingMap = /* @__PURE__ */ new Map();
7980
8134
  let componentIdIdx = 0;
7981
- for (const { call, displayName } of callSites) if (!ctx.dev && mayPrerender(call)) bindingMap.set(displayName, componentIds[componentIdIdx++]);
7982
- for (const stmt of program.body) if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration && stmt.declaration.type === "Identifier") {
7983
- const name = stmt.declaration.name;
7984
- if (bindingMap.has(name) && !bindingMap.has("default")) bindingMap.set("default", bindingMap.get(name));
8135
+ for (const { call, stmt, displayName } of callSites) if (!ctx.dev && mayPrerender(call)) {
8136
+ const compId = componentIds[componentIdIdx++];
8137
+ if (stmt.type === "ExportDefaultDeclaration") bindingMap.set(DEFAULT_SENTINEL, compId);
8138
+ else bindingMap.set(displayName, compId);
8139
+ }
8140
+ for (const stmt of program.body) {
8141
+ if (stmt.type === "ExportDefaultDeclaration" && stmt.declaration && stmt.declaration.type === "Identifier") {
8142
+ const name = stmt.declaration.name;
8143
+ if (bindingMap.has(name) && !bindingMap.has(DEFAULT_SENTINEL)) bindingMap.set(DEFAULT_SENTINEL, bindingMap.get(name));
8144
+ }
8145
+ if (stmt.type === "ExportNamedDeclaration" && Array.isArray(stmt.specifiers)) {
8146
+ for (const spec of stmt.specifiers) if (spec.exported.name === "default") {
8147
+ const compId = bindingMap.get(spec.local.name);
8148
+ if (compId && !bindingMap.has(DEFAULT_SENTINEL)) bindingMap.set(DEFAULT_SENTINEL, compId);
8149
+ }
8150
+ }
7985
8151
  }
7986
8152
  if (bindingMap.size > 0) {
7987
8153
  ctx.prebuiltBindingNames ??= /* @__PURE__ */ new Map();
7988
8154
  ctx.prebuiltBindingNames.set(id, bindingMap);
7989
8155
  }
7990
- const declaredBindings = new Set(bindingMap.keys());
8156
+ const declaredBindings = new Set([...bindingMap.keys()].filter((k) => k !== DEFAULT_SENTINEL));
7991
8157
  if (declaredBindings.size > 0) {
7992
8158
  const usages = collectJsxUsages(program, declaredBindings);
7993
8159
  for (const [bindingName, propsList] of usages) {
@@ -8013,7 +8179,7 @@ function timberPrebuilt(ctx) {
8013
8179
  * SHARED components work — a component imported by several routes is
8014
8180
  * captured under every importing route's param combos.
8015
8181
  */
8016
- generateBundle(_options, bundle) {
8182
+ async generateBundle(_options, bundle) {
8017
8183
  if (ctx.dev) return;
8018
8184
  if (this?.environment?.name !== "rsc") return;
8019
8185
  ctx.routePrebuiltComponents = void 0;
@@ -8021,6 +8187,9 @@ function timberPrebuilt(ctx) {
8021
8187
  if (!moduleComponents || moduleComponents.size === 0 || !ctx.routeTree) return;
8022
8188
  const self = this;
8023
8189
  const getInfo = (id) => self.getModuleInfo?.(id) ?? null;
8190
+ const resolveFn = async (source, importer) => {
8191
+ return (await self.resolve?.(source, importer))?.id ?? null;
8192
+ };
8024
8193
  const routeComponents = /* @__PURE__ */ new Map();
8025
8194
  for (const filePath of collectPageAndLayoutFiles(ctx.routeTree.root)) {
8026
8195
  const ids = collectReachablePrebuiltComponents(filePath, moduleComponents, getInfo);
@@ -8054,7 +8223,7 @@ function timberPrebuilt(ctx) {
8054
8223
  visited.add(modId);
8055
8224
  if (!scannedModules.has(modId)) {
8056
8225
  scannedModules.add(modId);
8057
- collectCrossModuleProps(ctx, modId, getInfo);
8226
+ await collectCrossModuleProps(ctx, modId, getInfo, resolveFn);
8058
8227
  }
8059
8228
  const modInfo = getInfo(modId);
8060
8229
  if (!modInfo) continue;
@@ -8225,7 +8394,7 @@ function timberPrerenderSugar(ctx) {
8225
8394
  if (!value) return;
8226
8395
  let program;
8227
8396
  try {
8228
- program = parseAst(code, { lang: "tsx" });
8397
+ program = ctx.parseCached.parse(code);
8229
8398
  } catch {
8230
8399
  return;
8231
8400
  }
@@ -9043,7 +9212,7 @@ var HOLDING_PAGE_HTML = [
9043
9212
  * ```
9044
9213
  */
9045
9214
  function createHoldingServer() {
9046
- const server = http.createServer((req, res) => {
9215
+ const server = nodeHTTP.createServer((req, res) => {
9047
9216
  const accept = req.headers.accept ?? "";
9048
9217
  if (accept.includes("application/json") && !accept.includes("text/html")) {
9049
9218
  res.writeHead(503, {