@timber-js/app 0.2.0-alpha.210 → 0.2.0-alpha.212

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 (158) hide show
  1. package/agent-skill.md +10 -5
  2. package/dist/_chunks/{actions-Rjk4htmA.js → actions-CCdnVtWm.js} +8 -6
  3. package/dist/_chunks/actions-CCdnVtWm.js.map +1 -0
  4. package/dist/_chunks/{als-registry-DaxkVjt5.js → als-registry-BZqHCtq-.js} +2 -4
  5. package/dist/_chunks/als-registry-BZqHCtq-.js.map +1 -0
  6. package/dist/_chunks/{cache-api-DGdYfNJn.js → cache-api-LA3sBpUS.js} +5 -5
  7. package/dist/_chunks/{cache-api-DGdYfNJn.js.map → cache-api-LA3sBpUS.js.map} +1 -1
  8. package/dist/_chunks/{chains-BoO51joc.js → chains-BfoPFraI.js} +5 -146
  9. package/dist/_chunks/chains-BfoPFraI.js.map +1 -0
  10. package/dist/_chunks/classify-BT66U83D.js +147 -0
  11. package/dist/_chunks/classify-BT66U83D.js.map +1 -0
  12. package/dist/_chunks/{cli-check-ajNY3B2e.js → cli-check-BfQ54-UJ.js} +3 -3
  13. package/dist/_chunks/{cli-check-ajNY3B2e.js.map → cli-check-BfQ54-UJ.js.map} +1 -1
  14. package/dist/_chunks/{cli-schema-sync-D2eI8jEg.js → cli-schema-sync-czh2dsLs.js} +2 -2
  15. package/dist/_chunks/{cli-schema-sync-D2eI8jEg.js.map → cli-schema-sync-czh2dsLs.js.map} +1 -1
  16. package/dist/_chunks/client-dep-entries-CQwpb8dI.js +66 -0
  17. package/dist/_chunks/client-dep-entries-CQwpb8dI.js.map +1 -0
  18. package/dist/_chunks/{convention-lint-DLmhGsRS.js → convention-lint-BEVW4EID.js} +3 -3
  19. package/dist/_chunks/{convention-lint-DLmhGsRS.js.map → convention-lint-BEVW4EID.js.map} +1 -1
  20. package/dist/_chunks/{dev-server-v97rQH4b.js → dev-server-FKxptbnI.js} +2 -2
  21. package/dist/_chunks/{dev-server-v97rQH4b.js.map → dev-server-FKxptbnI.js.map} +1 -1
  22. package/dist/_chunks/{json-lossy-check-CVuRs2hG.js → json-lossy-check-C8zBY2uZ.js} +2 -2
  23. package/dist/_chunks/{json-lossy-check-CVuRs2hG.js.map → json-lossy-check-C8zBY2uZ.js.map} +1 -1
  24. package/dist/_chunks/{live-graph-9cSnn_h9.js → live-graph-C_4v-fHv.js} +4 -3
  25. package/dist/_chunks/{live-graph-9cSnn_h9.js.map → live-graph-C_4v-fHv.js.map} +1 -1
  26. package/dist/_chunks/{logger-BP0LN6vP.js → logger-CbLdcy-W.js} +2 -2
  27. package/dist/_chunks/{logger-BP0LN6vP.js.map → logger-CbLdcy-W.js.map} +1 -1
  28. package/dist/_chunks/{poison-scan-vGV7Re0B.js → poison-scan-C92liMAr.js} +2 -4
  29. package/dist/_chunks/poison-scan-C92liMAr.js.map +1 -0
  30. package/dist/_chunks/{scanner-DmqdxzbW.js → scanner-CQt12vE2.js} +2 -2
  31. package/dist/_chunks/{scanner-DmqdxzbW.js.map → scanner-CQt12vE2.js.map} +1 -1
  32. package/dist/_chunks/{sizeof-BM1409x2.js → sizeof-QPE5nd3u.js} +2 -2
  33. package/dist/_chunks/{sizeof-BM1409x2.js.map → sizeof-QPE5nd3u.js.map} +1 -1
  34. package/dist/_chunks/{walkers-Czu2jXFq.js → walkers-DAT4avhZ.js} +3 -3
  35. package/dist/_chunks/{walkers-Czu2jXFq.js.map → walkers-DAT4avhZ.js.map} +1 -1
  36. package/dist/adapters/cloudflare-kv-cache.js +1 -1
  37. package/dist/analyze/classify.d.ts +2 -0
  38. package/dist/analyze/classify.d.ts.map +1 -1
  39. package/dist/analyze/crawl-entry.js +3 -2
  40. package/dist/analyze/crawl-entry.js.map +1 -1
  41. package/dist/analyze/graph-command.js +2 -2
  42. package/dist/analyze/poison-scan.d.ts.map +1 -1
  43. package/dist/cache/index.js +2 -2
  44. package/dist/cache/stores/memory.js +1 -1
  45. package/dist/cdn/workers-cache-purge.js +1 -1
  46. package/dist/cli.js +3 -3
  47. package/dist/client/browser-entry/action-dispatch.d.ts.map +1 -1
  48. package/dist/client/browser-entry/hydrate.d.ts.map +1 -1
  49. package/dist/client/browser-entry/router-init.d.ts.map +1 -1
  50. package/dist/client/form.d.ts +17 -64
  51. package/dist/client/form.d.ts.map +1 -1
  52. package/dist/client/index.d.ts +2 -2
  53. package/dist/client/index.d.ts.map +1 -1
  54. package/dist/client/index.js +23 -51
  55. package/dist/client/index.js.map +1 -1
  56. package/dist/client/internal.js +25 -25
  57. package/dist/client/internal.js.map +1 -1
  58. package/dist/client/navigation-api.d.ts +25 -63
  59. package/dist/client/navigation-api.d.ts.map +1 -1
  60. package/dist/client/router-lifecycle.d.ts +17 -11
  61. package/dist/client/router-lifecycle.d.ts.map +1 -1
  62. package/dist/client/router-pipeline.d.ts +0 -1
  63. package/dist/client/router-pipeline.d.ts.map +1 -1
  64. package/dist/client/router-types.d.ts +14 -45
  65. package/dist/client/router-types.d.ts.map +1 -1
  66. package/dist/client/router.d.ts.map +1 -1
  67. package/dist/index.d.ts.map +1 -1
  68. package/dist/index.js +44 -7
  69. package/dist/index.js.map +1 -1
  70. package/dist/plugins/client-dep-entries.d.ts +44 -0
  71. package/dist/plugins/client-dep-entries.d.ts.map +1 -0
  72. package/dist/plugins/content.d.ts.map +1 -1
  73. package/dist/plugins/entries.d.ts +6 -0
  74. package/dist/plugins/entries.d.ts.map +1 -1
  75. package/dist/plugins/shims.d.ts.map +1 -1
  76. package/dist/routing/index.js +2 -2
  77. package/dist/rsc-runtime/rsc.d.ts +1 -1
  78. package/dist/rsc-runtime/rsc.d.ts.map +1 -1
  79. package/dist/server/action-client.d.ts +9 -5
  80. package/dist/server/action-client.d.ts.map +1 -1
  81. package/dist/server/action-handler.d.ts +27 -8
  82. package/dist/server/action-handler.d.ts.map +1 -1
  83. package/dist/server/als-registry.d.ts +22 -2
  84. package/dist/server/als-registry.d.ts.map +1 -1
  85. package/dist/server/client-error-message.d.ts +12 -0
  86. package/dist/server/client-error-message.d.ts.map +1 -0
  87. package/dist/server/form-state-embed.d.ts +32 -0
  88. package/dist/server/form-state-embed.d.ts.map +1 -0
  89. package/dist/server/index.d.ts +0 -2
  90. package/dist/server/index.d.ts.map +1 -1
  91. package/dist/server/index.js +7 -40
  92. package/dist/server/index.js.map +1 -1
  93. package/dist/server/internal.js +10 -6
  94. package/dist/server/internal.js.map +1 -1
  95. package/dist/server/logger.d.ts +1 -0
  96. package/dist/server/logger.d.ts.map +1 -1
  97. package/dist/server/pipeline.d.ts +20 -6
  98. package/dist/server/pipeline.d.ts.map +1 -1
  99. package/dist/server/request-context.d.ts +27 -2
  100. package/dist/server/request-context.d.ts.map +1 -1
  101. package/dist/server/route-element-builder.d.ts.map +1 -1
  102. package/dist/server/rsc-entry/action-dispatcher.d.ts +6 -5
  103. package/dist/server/rsc-entry/action-dispatcher.d.ts.map +1 -1
  104. package/dist/server/rsc-entry/error-renderer.d.ts.map +1 -1
  105. package/dist/server/rsc-entry/rsc-stream.d.ts.map +1 -1
  106. package/dist/server/rsc-entry/ssr-renderer.d.ts.map +1 -1
  107. package/dist/server/ssr-bridge-types.d.ts +8 -0
  108. package/dist/server/ssr-bridge-types.d.ts.map +1 -1
  109. package/dist/server/ssr-entry.d.ts.map +1 -1
  110. package/dist/server/ssr-render.d.ts +3 -0
  111. package/dist/server/ssr-render.d.ts.map +1 -1
  112. package/docs/api/31-api-client.mdx +9 -3
  113. package/docs/learn/08-forms-and-actions.mdx +21 -28
  114. package/package.json +1 -1
  115. package/src/analyze/classify.ts +1 -1
  116. package/src/analyze/poison-scan.ts +1 -4
  117. package/src/client/browser-entry/action-dispatch.ts +74 -18
  118. package/src/client/browser-entry/hydrate.ts +18 -0
  119. package/src/client/browser-entry/router-init.ts +4 -18
  120. package/src/client/form.tsx +33 -98
  121. package/src/client/index.ts +2 -2
  122. package/src/client/navigation-api.ts +47 -173
  123. package/src/client/navigation-transition.ts +2 -2
  124. package/src/client/router-lifecycle.ts +40 -20
  125. package/src/client/router-pipeline.ts +3 -9
  126. package/src/client/router-types.ts +14 -49
  127. package/src/client/router.ts +38 -58
  128. package/src/index.ts +21 -2
  129. package/src/plugins/client-dep-entries.ts +78 -0
  130. package/src/plugins/content.ts +26 -1
  131. package/src/plugins/entries.ts +7 -0
  132. package/src/plugins/shims.ts +0 -2
  133. package/src/rsc-runtime/rsc.ts +3 -0
  134. package/src/rsc-runtime/vendor-types.d.ts +14 -0
  135. package/src/server/action-client.ts +18 -20
  136. package/src/server/action-handler.ts +133 -95
  137. package/src/server/als-registry.ts +23 -9
  138. package/src/server/client-error-message.ts +18 -0
  139. package/src/server/form-state-embed.ts +63 -0
  140. package/src/server/index.ts +0 -4
  141. package/src/server/logger.ts +6 -1
  142. package/src/server/pipeline.ts +27 -8
  143. package/src/server/request-context.ts +39 -2
  144. package/src/server/route-element-builder.ts +12 -1
  145. package/src/server/rsc-entry/action-dispatcher.ts +37 -34
  146. package/src/server/rsc-entry/error-renderer.ts +2 -1
  147. package/src/server/rsc-entry/rsc-stream.ts +20 -11
  148. package/src/server/rsc-entry/ssr-renderer.ts +14 -1
  149. package/src/server/ssr-bridge-types.ts +9 -0
  150. package/src/server/ssr-entry.ts +1 -0
  151. package/src/server/ssr-render.ts +5 -0
  152. package/dist/_chunks/actions-Rjk4htmA.js.map +0 -1
  153. package/dist/_chunks/als-registry-DaxkVjt5.js.map +0 -1
  154. package/dist/_chunks/chains-BoO51joc.js.map +0 -1
  155. package/dist/_chunks/poison-scan-vGV7Re0B.js.map +0 -1
  156. package/dist/server/form-flash.d.ts +0 -78
  157. package/dist/server/form-flash.d.ts.map +0 -1
  158. package/src/server/form-flash.ts +0 -89
@@ -1,4 +1,4 @@
1
- import { v as swallow } from "./logger-BP0LN6vP.js";
1
+ import { v as swallow } from "./logger-CbLdcy-W.js";
2
2
  //#region src/cache/sizeof.ts
3
3
  /**
4
4
  * Lightweight byte-size estimation for cache entries.
@@ -33,4 +33,4 @@ function estimateByteSize(value) {
33
33
  //#endregion
34
34
  export { estimateByteSize as t };
35
35
 
36
- //# sourceMappingURL=sizeof-BM1409x2.js.map
36
+ //# sourceMappingURL=sizeof-QPE5nd3u.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sizeof-BM1409x2.js","names":[],"sources":["../../src/cache/sizeof.ts"],"sourcesContent":["/**\n * Lightweight byte-size estimation for cache entries.\n *\n * Estimates the in-memory byte cost of a JavaScript value using\n * JSON.stringify().length * 2 (UTF-16 char width). This is a rough\n * approximation — it doesn't account for V8 object overhead, Map\n * metadata, or non-serializable values — but it's fast and good\n * enough for cache budget enforcement.\n *\n * Values that fail JSON serialization (circular references, BigInt,\n * etc.) return 0, allowing the entry to be cached without counting\n * toward the byte budget. This is a conservative choice: it's better\n * to cache and undercount than to reject the entry.\n */\n\nimport { swallow } from '../server/logger.ts';\n\n/**\n * Estimate the byte size of a value.\n *\n * Uses `JSON.stringify(value).length * 2` to approximate UTF-16\n * in-memory size. Returns 0 if the value is not serializable.\n */\nexport function estimateByteSize(value: unknown): number {\n try {\n const json = JSON.stringify(value);\n if (json === undefined) return 0;\n // Each JS string character is 2 bytes (UTF-16)\n return json.length * 2;\n } catch (e) {\n // Circular reference or non-serializable value\n swallow(e, 'sizeof estimation failed', { level: 'debug' });\n return 0;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,iBAAiB,OAAwB;CACvD,IAAI;EACF,MAAM,OAAO,KAAK,UAAU,KAAK;EACjC,IAAI,SAAS,KAAA,GAAW,OAAO;EAE/B,OAAO,KAAK,SAAS;CACvB,SAAS,GAAG;EAEV,QAAQ,GAAG,4BAA4B,EAAE,OAAO,QAAQ,CAAC;EACzD,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"sizeof-QPE5nd3u.js","names":[],"sources":["../../src/cache/sizeof.ts"],"sourcesContent":["/**\n * Lightweight byte-size estimation for cache entries.\n *\n * Estimates the in-memory byte cost of a JavaScript value using\n * JSON.stringify().length * 2 (UTF-16 char width). This is a rough\n * approximation — it doesn't account for V8 object overhead, Map\n * metadata, or non-serializable values — but it's fast and good\n * enough for cache budget enforcement.\n *\n * Values that fail JSON serialization (circular references, BigInt,\n * etc.) return 0, allowing the entry to be cached without counting\n * toward the byte budget. This is a conservative choice: it's better\n * to cache and undercount than to reject the entry.\n */\n\nimport { swallow } from '../server/logger.ts';\n\n/**\n * Estimate the byte size of a value.\n *\n * Uses `JSON.stringify(value).length * 2` to approximate UTF-16\n * in-memory size. Returns 0 if the value is not serializable.\n */\nexport function estimateByteSize(value: unknown): number {\n try {\n const json = JSON.stringify(value);\n if (json === undefined) return 0;\n // Each JS string character is 2 bytes (UTF-16)\n return json.length * 2;\n } catch (e) {\n // Circular reference or non-serializable value\n swallow(e, 'sizeof estimation failed', { level: 'debug' });\n return 0;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,SAAgB,iBAAiB,OAAwB;CACvD,IAAI;EACF,MAAM,OAAO,KAAK,UAAU,KAAK;EACjC,IAAI,SAAS,KAAA,GAAW,OAAO;EAE/B,OAAO,KAAK,SAAS;CACvB,SAAS,GAAG;EAEV,QAAQ,GAAG,4BAA4B,EAAE,OAAO,QAAQ,CAAC;EACzD,OAAO;CACT;AACF"}
@@ -1,6 +1,6 @@
1
1
  import { n as classifyUrlSegment } from "./segment-classify-C539Pa2O.js";
2
- import { u as readFileCached } from "./scanner-DmqdxzbW.js";
3
- import { n as findSchemaFile, r as parseExistingSchemaKeys } from "./cli-schema-sync-D2eI8jEg.js";
2
+ import { u as readFileCached } from "./scanner-CQt12vE2.js";
3
+ import { n as findSchemaFile, r as parseExistingSchemaKeys } from "./cli-schema-sync-czh2dsLs.js";
4
4
  //#region src/routing/schema-validation.ts
5
5
  /**
6
6
  * Schema key/value validation against the filesystem route tree.
@@ -141,4 +141,4 @@ function walk(node, chain, result, includeSlots) {
141
141
  //#endregion
142
142
  export { collectDynamicSegmentsFromTree as n, validateSchemaAgainstRoutes as r, collectLeafRoutes as t };
143
143
 
144
- //# sourceMappingURL=walkers-Czu2jXFq.js.map
144
+ //# sourceMappingURL=walkers-DAT4avhZ.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"walkers-Czu2jXFq.js","names":[],"sources":["../../src/routing/schema-validation.ts","../../src/routing/walkers.ts"],"sourcesContent":["/**\n * Schema key/value validation against the filesystem route tree.\n *\n * Runs at codegen time (build + dev) to catch mismatches between\n * app/schema.ts keys and the actual dynamic segments on disk.\n *\n * Design doc: design/41-global-params.md\n */\n\nimport { readFileCached } from './file-cache.ts';\nimport type { RouteTree, SegmentNode } from './types.ts';\nimport { findSchemaFile, parseExistingSchemaKeys } from '../cli-schema-sync.ts';\nimport { classifyUrlSegment } from './segment-classify.ts';\n\nexport interface SchemaWarning {\n type: 'stale-key' | 'missing-key' | 'bracket-mismatch';\n key: string;\n message: string;\n filesystemKey?: string;\n}\n\n/**\n * Collect all unique bracket-keyed dynamic segment names from a route tree.\n * Returns entries like '[artistSlug]', '[...slug]', '[[...topic]]'.\n */\nexport function collectDynamicSegmentsFromTree(tree: RouteTree): Set<string> {\n const segments = new Set<string>();\n walkTree(tree.root, segments);\n return segments;\n}\n\nfunction walkTree(node: SegmentNode, segments: Set<string>): void {\n if (node.paramName && node.segmentName) {\n const classified = classifyUrlSegment(node.segmentName);\n if (classified.kind !== 'static') {\n // Use bare bracket form for schema keys — affixed segments like\n // `img-[id].png` register as `[id]`, matching runtime codec lookup.\n if (classified.kind === 'dynamic' && (classified.prefix || classified.suffix)) {\n segments.add(`[${classified.name}]`);\n } else {\n segments.add(node.segmentName);\n }\n }\n }\n for (const child of node.children) {\n walkTree(child, segments);\n }\n for (const slot of Object.values(node.slots)) {\n walkTree(slot, segments);\n }\n}\n\n/**\n * Extract the param name from a bracket key.\n * '[id]' → 'id', '[...slug]' → 'slug', '[[...topic]]' → 'topic'\n */\nfunction extractParamName(bracketKey: string): string | null {\n const seg = classifyUrlSegment(bracketKey);\n if (seg.kind === 'static') return null;\n return seg.name;\n}\n\n/**\n * Validate schema keys against the filesystem route tree.\n *\n * Returns warnings for:\n * - Stale keys: schema keys that don't match any filesystem segment\n * - Bracket form mismatches: same param name, different bracket form\n * - Missing keys: filesystem segments not registered in schema (informational)\n */\nexport function validateSchemaAgainstRoutes(tree: RouteTree, appDir: string): SchemaWarning[] {\n const schemaPath = findSchemaFile(appDir);\n if (!schemaPath) return [];\n\n const content = readFileCached(schemaPath);\n const schemaKeys = parseExistingSchemaKeys(content);\n\n if (schemaKeys.length === 0) return [];\n\n const filesystemSegments = collectDynamicSegmentsFromTree(tree);\n\n return validateKeys(schemaKeys, filesystemSegments);\n}\n\n/**\n * Core validation: compare schema keys against filesystem segments.\n * Exported separately for testing without filesystem dependencies.\n */\nexport function validateKeys(\n schemaKeys: string[],\n filesystemSegments: Set<string>\n): SchemaWarning[] {\n const warnings: SchemaWarning[] = [];\n\n // Build a map of param name → bracket form for filesystem segments\n const fsParamToKey = new Map<string, string>();\n for (const fsKey of filesystemSegments) {\n const name = extractParamName(fsKey);\n if (name) {\n fsParamToKey.set(name, fsKey);\n }\n }\n\n // Build a set of schema param names for the missing-key check\n const schemaParamNames = new Set<string>();\n\n for (const schemaKey of schemaKeys) {\n const schemaParamName = extractParamName(schemaKey);\n if (!schemaParamName) continue;\n\n schemaParamNames.add(schemaParamName);\n\n if (filesystemSegments.has(schemaKey)) {\n // Exact match — no issue\n continue;\n }\n\n // Check if the param name exists but with a different bracket form\n const fsKey = fsParamToKey.get(schemaParamName);\n if (fsKey) {\n warnings.push({\n type: 'bracket-mismatch',\n key: schemaKey,\n filesystemKey: fsKey,\n message:\n `Schema key '${schemaKey}' does not match filesystem bracket form '${fsKey}'. ` +\n `Update the schema key to '${fsKey}'.`,\n });\n } else {\n warnings.push({\n type: 'stale-key',\n key: schemaKey,\n message:\n `Schema key '${schemaKey}' does not match any dynamic segment in the filesystem. ` +\n `Remove it from app/schema.ts or create the corresponding route segment.`,\n });\n }\n }\n\n // Check for filesystem segments not in schema (informational)\n for (const fsKey of filesystemSegments) {\n const name = extractParamName(fsKey);\n if (name && !schemaParamNames.has(name)) {\n warnings.push({\n type: 'missing-key',\n key: fsKey,\n message:\n `Dynamic segment '${fsKey}' has no codec in app/schema.ts (defaults to string). ` +\n `Run: timber schema sync`,\n });\n }\n }\n\n return warnings;\n}\n","/**\n * Shared route-tree walkers (TIM-848).\n *\n * Tiny helpers that walk a `SegmentNode<TFile>` tree generically. Both the\n * build-time tree (`SegmentNode<RouteFile>`) and the runtime manifest\n * tree (`SegmentNode<ManifestFile>`) flow through these helpers because\n * the walker only reads the structural fields shared by both shapes\n * (`children`, `slots`, `page`, `route`, `urlPath`).\n *\n * Before this module, three near-identical `collectRoutes` functions\n * lived in `plugins/dev-404-page.ts`, `plugins/build-report.ts`, and\n * `routing/codegen.ts`. The codegen one is special-purpose (it\n * accumulates `ParamEntry[]` and resolves codec chains) and stays\n * local; the other two now share `collectLeafRoutes` from this file.\n */\n\nimport type { SegmentNode } from './types.ts';\n\n/** A leaf route discovered while walking the segment tree. */\nexport interface LeafRoute<TFile> {\n /** URL path of the leaf (root is \"/\"). */\n urlPath: string;\n /** Segment chain from root to this leaf, inclusive. */\n segments: SegmentNode<TFile>[];\n /** The page file at this leaf, if any. */\n page?: TFile;\n /** The route handler file at this leaf, if any. */\n route?: TFile;\n}\n\n/** Options for `collectLeafRoutes`. */\nexport interface CollectLeafRoutesOptions {\n /**\n * If true, recurse into parallel slots and emit slot leaves alongside\n * the main route tree. Defaults to `false` because slots render\n * alongside their parent at the same URL and are not separately\n * URL-addressable. The build report excludes slots; route-listing\n * UIs that want to show \"all leaves with a page handler\" can opt in.\n */\n includeSlots?: boolean;\n}\n\n/**\n * Walk a segment tree and collect every leaf with a `page` or `route`\n * handler. Generic over `TFile` so it works on both the build-time\n * scanner output and the runtime manifest tree.\n *\n * - Pages and route handlers at the same URL produce two distinct\n * entries (the build report deduplicates by URL afterward).\n * - Parallel slots are skipped unless `includeSlots: true` (slots\n * share their parent's URL and are not addressable on their own).\n * - Intercepting subtrees are skipped unconditionally. They render only\n * on soft navigation and add no URL depth, so their computed `urlPath`s\n * are not addresses — listing them would advertise routes (`/[year]`)\n * that no request can reach. This matches every other consumer of the\n * tree that produces URLs: the sitemap generator, the static generator,\n * prerendering and the prebuilt-payload builder all skip them too.\n * - Result is sorted by `urlPath` for deterministic output.\n */\nexport function collectLeafRoutes<TFile>(\n root: SegmentNode<TFile>,\n options: CollectLeafRoutesOptions = {}\n): LeafRoute<TFile>[] {\n const { includeSlots = false } = options;\n const result: LeafRoute<TFile>[] = [];\n walk(root, [], result, includeSlots);\n result.sort((a, b) => a.urlPath.localeCompare(b.urlPath));\n return result;\n}\n\nfunction walk<TFile>(\n node: SegmentNode<TFile>,\n chain: SegmentNode<TFile>[],\n result: LeafRoute<TFile>[],\n includeSlots: boolean\n): void {\n if (node.segmentType === 'intercepting') return;\n\n const currentChain = [...chain, node];\n const path = node.urlPath || '/';\n\n if (node.page) {\n result.push({ urlPath: path, segments: currentChain, page: node.page });\n }\n if (node.route) {\n result.push({ urlPath: path, segments: currentChain, route: node.route });\n }\n\n for (const child of node.children) {\n walk(child, currentChain, result, includeSlots);\n }\n\n if (includeSlots) {\n for (const slotNode of Object.values(node.slots)) {\n walk(slotNode, currentChain, result, includeSlots);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"walkers-DAT4avhZ.js","names":[],"sources":["../../src/routing/schema-validation.ts","../../src/routing/walkers.ts"],"sourcesContent":["/**\n * Schema key/value validation against the filesystem route tree.\n *\n * Runs at codegen time (build + dev) to catch mismatches between\n * app/schema.ts keys and the actual dynamic segments on disk.\n *\n * Design doc: design/41-global-params.md\n */\n\nimport { readFileCached } from './file-cache.ts';\nimport type { RouteTree, SegmentNode } from './types.ts';\nimport { findSchemaFile, parseExistingSchemaKeys } from '../cli-schema-sync.ts';\nimport { classifyUrlSegment } from './segment-classify.ts';\n\nexport interface SchemaWarning {\n type: 'stale-key' | 'missing-key' | 'bracket-mismatch';\n key: string;\n message: string;\n filesystemKey?: string;\n}\n\n/**\n * Collect all unique bracket-keyed dynamic segment names from a route tree.\n * Returns entries like '[artistSlug]', '[...slug]', '[[...topic]]'.\n */\nexport function collectDynamicSegmentsFromTree(tree: RouteTree): Set<string> {\n const segments = new Set<string>();\n walkTree(tree.root, segments);\n return segments;\n}\n\nfunction walkTree(node: SegmentNode, segments: Set<string>): void {\n if (node.paramName && node.segmentName) {\n const classified = classifyUrlSegment(node.segmentName);\n if (classified.kind !== 'static') {\n // Use bare bracket form for schema keys — affixed segments like\n // `img-[id].png` register as `[id]`, matching runtime codec lookup.\n if (classified.kind === 'dynamic' && (classified.prefix || classified.suffix)) {\n segments.add(`[${classified.name}]`);\n } else {\n segments.add(node.segmentName);\n }\n }\n }\n for (const child of node.children) {\n walkTree(child, segments);\n }\n for (const slot of Object.values(node.slots)) {\n walkTree(slot, segments);\n }\n}\n\n/**\n * Extract the param name from a bracket key.\n * '[id]' → 'id', '[...slug]' → 'slug', '[[...topic]]' → 'topic'\n */\nfunction extractParamName(bracketKey: string): string | null {\n const seg = classifyUrlSegment(bracketKey);\n if (seg.kind === 'static') return null;\n return seg.name;\n}\n\n/**\n * Validate schema keys against the filesystem route tree.\n *\n * Returns warnings for:\n * - Stale keys: schema keys that don't match any filesystem segment\n * - Bracket form mismatches: same param name, different bracket form\n * - Missing keys: filesystem segments not registered in schema (informational)\n */\nexport function validateSchemaAgainstRoutes(tree: RouteTree, appDir: string): SchemaWarning[] {\n const schemaPath = findSchemaFile(appDir);\n if (!schemaPath) return [];\n\n const content = readFileCached(schemaPath);\n const schemaKeys = parseExistingSchemaKeys(content);\n\n if (schemaKeys.length === 0) return [];\n\n const filesystemSegments = collectDynamicSegmentsFromTree(tree);\n\n return validateKeys(schemaKeys, filesystemSegments);\n}\n\n/**\n * Core validation: compare schema keys against filesystem segments.\n * Exported separately for testing without filesystem dependencies.\n */\nexport function validateKeys(\n schemaKeys: string[],\n filesystemSegments: Set<string>\n): SchemaWarning[] {\n const warnings: SchemaWarning[] = [];\n\n // Build a map of param name → bracket form for filesystem segments\n const fsParamToKey = new Map<string, string>();\n for (const fsKey of filesystemSegments) {\n const name = extractParamName(fsKey);\n if (name) {\n fsParamToKey.set(name, fsKey);\n }\n }\n\n // Build a set of schema param names for the missing-key check\n const schemaParamNames = new Set<string>();\n\n for (const schemaKey of schemaKeys) {\n const schemaParamName = extractParamName(schemaKey);\n if (!schemaParamName) continue;\n\n schemaParamNames.add(schemaParamName);\n\n if (filesystemSegments.has(schemaKey)) {\n // Exact match — no issue\n continue;\n }\n\n // Check if the param name exists but with a different bracket form\n const fsKey = fsParamToKey.get(schemaParamName);\n if (fsKey) {\n warnings.push({\n type: 'bracket-mismatch',\n key: schemaKey,\n filesystemKey: fsKey,\n message:\n `Schema key '${schemaKey}' does not match filesystem bracket form '${fsKey}'. ` +\n `Update the schema key to '${fsKey}'.`,\n });\n } else {\n warnings.push({\n type: 'stale-key',\n key: schemaKey,\n message:\n `Schema key '${schemaKey}' does not match any dynamic segment in the filesystem. ` +\n `Remove it from app/schema.ts or create the corresponding route segment.`,\n });\n }\n }\n\n // Check for filesystem segments not in schema (informational)\n for (const fsKey of filesystemSegments) {\n const name = extractParamName(fsKey);\n if (name && !schemaParamNames.has(name)) {\n warnings.push({\n type: 'missing-key',\n key: fsKey,\n message:\n `Dynamic segment '${fsKey}' has no codec in app/schema.ts (defaults to string). ` +\n `Run: timber schema sync`,\n });\n }\n }\n\n return warnings;\n}\n","/**\n * Shared route-tree walkers (TIM-848).\n *\n * Tiny helpers that walk a `SegmentNode<TFile>` tree generically. Both the\n * build-time tree (`SegmentNode<RouteFile>`) and the runtime manifest\n * tree (`SegmentNode<ManifestFile>`) flow through these helpers because\n * the walker only reads the structural fields shared by both shapes\n * (`children`, `slots`, `page`, `route`, `urlPath`).\n *\n * Before this module, three near-identical `collectRoutes` functions\n * lived in `plugins/dev-404-page.ts`, `plugins/build-report.ts`, and\n * `routing/codegen.ts`. The codegen one is special-purpose (it\n * accumulates `ParamEntry[]` and resolves codec chains) and stays\n * local; the other two now share `collectLeafRoutes` from this file.\n */\n\nimport type { SegmentNode } from './types.ts';\n\n/** A leaf route discovered while walking the segment tree. */\nexport interface LeafRoute<TFile> {\n /** URL path of the leaf (root is \"/\"). */\n urlPath: string;\n /** Segment chain from root to this leaf, inclusive. */\n segments: SegmentNode<TFile>[];\n /** The page file at this leaf, if any. */\n page?: TFile;\n /** The route handler file at this leaf, if any. */\n route?: TFile;\n}\n\n/** Options for `collectLeafRoutes`. */\nexport interface CollectLeafRoutesOptions {\n /**\n * If true, recurse into parallel slots and emit slot leaves alongside\n * the main route tree. Defaults to `false` because slots render\n * alongside their parent at the same URL and are not separately\n * URL-addressable. The build report excludes slots; route-listing\n * UIs that want to show \"all leaves with a page handler\" can opt in.\n */\n includeSlots?: boolean;\n}\n\n/**\n * Walk a segment tree and collect every leaf with a `page` or `route`\n * handler. Generic over `TFile` so it works on both the build-time\n * scanner output and the runtime manifest tree.\n *\n * - Pages and route handlers at the same URL produce two distinct\n * entries (the build report deduplicates by URL afterward).\n * - Parallel slots are skipped unless `includeSlots: true` (slots\n * share their parent's URL and are not addressable on their own).\n * - Intercepting subtrees are skipped unconditionally. They render only\n * on soft navigation and add no URL depth, so their computed `urlPath`s\n * are not addresses — listing them would advertise routes (`/[year]`)\n * that no request can reach. This matches every other consumer of the\n * tree that produces URLs: the sitemap generator, the static generator,\n * prerendering and the prebuilt-payload builder all skip them too.\n * - Result is sorted by `urlPath` for deterministic output.\n */\nexport function collectLeafRoutes<TFile>(\n root: SegmentNode<TFile>,\n options: CollectLeafRoutesOptions = {}\n): LeafRoute<TFile>[] {\n const { includeSlots = false } = options;\n const result: LeafRoute<TFile>[] = [];\n walk(root, [], result, includeSlots);\n result.sort((a, b) => a.urlPath.localeCompare(b.urlPath));\n return result;\n}\n\nfunction walk<TFile>(\n node: SegmentNode<TFile>,\n chain: SegmentNode<TFile>[],\n result: LeafRoute<TFile>[],\n includeSlots: boolean\n): void {\n if (node.segmentType === 'intercepting') return;\n\n const currentChain = [...chain, node];\n const path = node.urlPath || '/';\n\n if (node.page) {\n result.push({ urlPath: path, segments: currentChain, page: node.page });\n }\n if (node.route) {\n result.push({ urlPath: path, segments: currentChain, route: node.route });\n }\n\n for (const child of node.children) {\n walk(child, currentChain, result, includeSlots);\n }\n\n if (includeSlots) {\n for (const slotNode of Object.values(node.slots)) {\n walk(slotNode, currentChain, result, includeSlots);\n }\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as warnLossyJsonValue } from "../_chunks/json-lossy-check-CVuRs2hG.js";
1
+ import { t as warnLossyJsonValue } from "../_chunks/json-lossy-check-C8zBY2uZ.js";
2
2
  import { l as getCloudflareBindings } from "../_chunks/cloudflare-Byxl8FWP.js";
3
3
  //#region src/adapters/cloudflare-kv-cache.ts
4
4
  var LEGACY_GRACE_MS = 2592e6;
@@ -85,6 +85,8 @@ export interface ModuleClassification {
85
85
  */
86
86
  subtreeUnknown: boolean;
87
87
  }
88
+ /** File extensions eligible for directive detection. */
89
+ export declare const PARSEABLE_EXTENSIONS: RegExp;
88
90
  /**
89
91
  * Parse language for a source file, from its extension. Never force
90
92
  * `tsx` onto plain `.ts`: JSX-ambiguous TypeScript such as
@@ -1 +1 @@
1
- {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/analyze/classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAcH,MAAM,MAAM,UAAU,GAClB,QAAQ,GACR,iBAAiB,GACjB,iBAAiB,GACjB,QAAQ,GACR,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,IAAI,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAC;IACf,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,IAAI,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IACtD;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAKD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,CAI9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAerE;AAED,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQhG;AAoBD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,iBAAiB,EACtB,GAAG,CAAC,EAAE,eAAe,GACpB,oBAAoB,CA2BtB;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAC7B,YAAY,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACzC,GAAG,CAAC,EAAE,eAAe,GACpB,oBAAoB,EAAE,CAIxB"}
1
+ {"version":3,"file":"classify.d.ts","sourceRoot":"","sources":["../../src/analyze/classify.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAcH,MAAM,MAAM,UAAU,GAClB,QAAQ,GACR,iBAAiB,GACjB,iBAAiB,GACjB,QAAQ,GACR,eAAe,GACf,cAAc,CAAC;AAEnB,MAAM,MAAM,SAAS,GAAG,YAAY,GAAG,YAAY,GAAG,IAAI,CAAC;AAE3D;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAC;IACf,qDAAqD;IACrD,KAAK,EAAE,OAAO,CAAC;IACf;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uDAAuD;IACvD,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,IAAI,EAAE;QAAE,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC;IACtD;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,wDAAwD;AACxD,eAAO,MAAM,oBAAoB,QAAyC,CAAC;AAE3E;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,KAAK,GAAG,KAAK,CAI9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAerE;AAED,2CAA2C;AAC3C,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAQhG;AAoBD;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,iBAAiB,EACtB,GAAG,CAAC,EAAE,eAAe,GACpB,oBAAoB,CA2BtB;AAED,0EAA0E;AAC1E,wBAAgB,eAAe,CAC7B,YAAY,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACzC,GAAG,CAAC,EAAE,eAAe,GACpB,oBAAoB,EAAE,CAIxB"}
@@ -1,7 +1,8 @@
1
1
  import { i as scanProjectSourceFiles, n as isScannableSourceFile, t as buildSourceExtensions } from "../_chunks/scan-BoP17W3w.js";
2
2
  import { c as resolveAppDir, i as isTimberPluginApi } from "../_chunks/plugin-context-BPBPc3lL.js";
3
- import { a as detectDirective, i as classifyModules, n as stitchImportChains } from "../_chunks/chains-BoO51joc.js";
4
- import { n as importSignals, r as poisoningsFor, t as collectImportSpecifiers } from "../_chunks/poison-scan-vGV7Re0B.js";
3
+ import { i as detectDirective, r as classifyModules } from "../_chunks/classify-BT66U83D.js";
4
+ import { n as stitchImportChains } from "../_chunks/chains-BfoPFraI.js";
5
+ import { n as importSignals, r as poisoningsFor, t as collectImportSpecifiers } from "../_chunks/poison-scan-C92liMAr.js";
5
6
  import { normalizePath } from "vite";
6
7
  import { join, resolve } from "node:path";
7
8
  import { readFile, realpath, writeFile } from "node:fs/promises";
@@ -1 +1 @@
1
- {"version":3,"file":"crawl-entry.js","names":[],"sources":["../../src/analyze/crawl.ts","../../src/analyze/crawl-entry.ts"],"sourcesContent":["/**\n * crawl — programmatic middleware-mode crawl (the cold path).\n *\n * Boots `createServer({ middlewareMode: true })` — no HTTP port, no route\n * rendering — and BFS-walks `env.transformRequest(url)` → importedModules\n * edges from `virtual:timber-rsc-entry`, then from every discovered\n * `'use client'` boundary through the SSR environment. This reuses Vite's\n * actual resolution (aliases, tsconfig paths, react-server export\n * conditions, virtual modules), so it cannot drift from what dev or the\n * build does; hand-rolled resolution was evaluated and rejected\n * (design/47 §\"Verified Prototype Findings\").\n *\n * Cost model (design/47 §6): 2.5 s on small apps to ~10 s on dep-heavy\n * plugin stacks, with a transient ~1.3 GB RSS — callers MUST run this in\n * a short-lived child process (see crawl-entry.ts) and cache the\n * classification output, not the Vite transforms. The crawl always\n * covers the full project: one crawl per project state, never per\n * question — marginal per-file cost is ~zero once booted.\n *\n * `TIMBER_ANALYZE=1` is set before the server boots so platform dev\n * plugins (wrangler bindings — a measured ~6.8 s remote-bindings\n * handshake on website) skip work the crawl never needs: classification\n * wants import edges, not live Cloudflare connections.\n *\n * Design docs: 47-module-environment-tooling.md §6, §8.\n */\n\nimport { readFile, realpath } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { ViteDevServer } from 'vite';\nimport { normalizePath } from 'vite';\nimport {\n classifyModules,\n detectDirective,\n type Directive,\n type ModuleClassification,\n} from './classify.ts';\nimport { buildSourceExtensions, scanProjectSourceFiles, isScannableSourceFile } from './scan.ts';\nimport { stitchImportChains, type ChainGraph, type ImportChain } from './chains.ts';\nimport {\n collectImportSpecifiers,\n importSignals,\n poisoningsFor,\n type Poisoning,\n} from './poison-scan.ts';\nimport { isTimberPluginApi, resolveAppDir } from '../plugin-context.ts';\n\nconst RSC_ENTRY_URL = 'virtual:timber-rsc-entry';\n\n/**\n * Marker preceding the JSON report on crawl-entry's stdout. Lives here\n * (not in crawl-entry.ts) so consumers can import it without executing\n * the child entry's main().\n */\nexport const CRAWL_RESULT_MARKER = '__TIMBER_CRAWL_RESULT__';\n\nexport interface CrawlOptions {\n /** Project root (the app directory containing vite.config). */\n root: string;\n /** Explicit Vite config file; discovered from root when omitted. */\n configFile?: string;\n /** Overall crawl deadline. Default 120s. */\n timeoutMs?: number;\n}\n\nexport interface CrawlError {\n /** 'scan' covers disk-scan and directive-read failures. */\n environment: 'rsc' | 'ssr' | 'scan';\n id: string;\n message: string;\n}\n\n/**\n * One project file's full graph answer — the classification plus the\n * remaining JSON-contract fields (`boundary`, `chains`, `poisonings`)\n * that need the open graphs / original sources to compute. This is the\n * per-module unit `timber graph` caches and serves.\n */\nexport interface GraphModuleReport extends ModuleClassification {\n /** True when the file itself carries `'use client'`. */\n boundary: boolean;\n /** Boundary-aware import chains (design/47 §7); [] when in no graph. */\n chains: ImportChain[];\n /** Report-only poisoning conflicts (design/47 §3); [] when clean. */\n poisonings: Poisoning[];\n}\n\nexport interface CrawlReport {\n root: string;\n /**\n * True when the crawl deadline expired mid-walk — the report is\n * truncated, not wrong, and the condition is transient (a slow\n * machine, a cold dep-optimizer). Consumers must not cache an\n * incomplete report: with unchanged files it would be served forever,\n * never retrying after the transient cause cleared. Deterministic\n * per-module transform failures do NOT set this — they recur on\n * retry, so caching them is correct.\n */\n incomplete: boolean;\n /** Classification of every project file, sorted by path. */\n modules: GraphModuleReport[];\n /**\n * Transform failures encountered during the BFS. These are classified,\n * not swallowed — CSS/asset URLs and non-transformable modules land\n * here (44 on website in the §6 measurement) and the crawl continues.\n */\n errors: CrawlError[];\n stats: {\n rscGraphModules: number;\n ssrGraphModules: number;\n projectFiles: number;\n durationMs: number;\n };\n}\n\n/** Marker error for deadline expiry — structurally distinguishable\n * from a deterministic transform failure (consumers must not cache a\n * deadline-truncated report; a transform failure recurs on retry and\n * caches fine). */\nclass CrawlDeadlineError extends Error {}\n\n/** Race a promise against the crawl deadline. */\nfunction withDeadline<T>(promise: Promise<T>, remainingMs: number, label: string): Promise<T> {\n return new Promise<T>((resolvePromise, rejectPromise) => {\n const timer = setTimeout(\n () =>\n rejectPromise(\n new CrawlDeadlineError(`crawl deadline exceeded while transforming ${label}`)\n ),\n Math.max(remainingMs, 1)\n );\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolvePromise(value);\n },\n (error) => {\n clearTimeout(timer);\n rejectPromise(error);\n }\n );\n });\n}\n\n/** Duck-typed view of the Vite dev environment surface the crawl uses. */\nexport interface CrawlEnvironment {\n transformRequest(url: string): Promise<unknown>;\n moduleGraph: {\n getModuleByUrl(url: string): Promise<CrawlModuleNode | undefined>;\n };\n}\n\ninterface CrawlModuleNode {\n id: string | null;\n url: string;\n file: string | null;\n importedModules: Set<CrawlModuleNode>;\n transformResult: unknown;\n}\n\ninterface EnvCrawlResult {\n /** Absolute paths of project files reached in this environment. */\n projectFiles: Set<string>;\n /** Project files whose own transform failed (membership unconfirmed subtree). */\n failedFiles: Set<string>;\n /** Total graph modules visited (project + deps + virtual). */\n visited: number;\n /** The deadline expired mid-walk — the result is truncated. */\n deadlineHit: boolean;\n}\n\n/**\n * Whether a graph node's file belongs in the report. `root` must be\n * slash-normalized; module-node paths already are (Vite normalizes\n * them, even on Windows — comparing against a backslashed root would\n * reject every physical module there and misreport the whole app as\n * pending). The rule set is the scan's own (isScannableSourceFile), so\n * report membership, disk-scan coverage, and the cache fingerprint are\n * one set — a graph-reachable file the fingerprint never tracked would\n * let its edits serve a stale cache (codex review, PR #1056).\n */\nfunction isProjectFile(file: string | null, root: string, extensions?: RegExp): file is string {\n if (!file) return false;\n return isScannableSourceFile(normalizePath(file), root, extensions);\n}\n\n/**\n * BFS one environment from the given seed URLs, transforming every\n * discovered module unconditionally. Transforming (rather than reading\n * live graphs) is what closes the §8 blind spot: modules behind\n * unexecuted dynamic imports are transformed here like any other edge.\n *\n * Exported for tests (deadline behavior is exercised with a fake env).\n */\nexport async function crawlEnvironment(\n env: CrawlEnvironment,\n envName: 'rsc' | 'ssr',\n seeds: string[],\n root: string,\n deadlineAt: number,\n errors: CrawlError[],\n extensions?: RegExp\n): Promise<EnvCrawlResult> {\n const queue = [...seeds];\n const enqueued = new Set(queue);\n const projectFiles = new Set<string>();\n const failedFiles = new Set<string>();\n let visited = 0;\n let deadlineHit = false;\n\n // Index-cursor BFS — the queue only grows, so this walks each entry once.\n for (let cursor = 0; cursor < queue.length; cursor++) {\n const url = queue[cursor];\n const remaining = deadlineAt - Date.now();\n if (remaining <= 0) {\n errors.push({ environment: envName, id: url, message: 'crawl deadline exceeded' });\n deadlineHit = true;\n break;\n }\n\n let node: CrawlModuleNode | undefined;\n try {\n await withDeadline(env.transformRequest(url), remaining, url);\n node = await env.moduleGraph.getModuleByUrl(url);\n } catch (error) {\n if (error instanceof CrawlDeadlineError) deadlineHit = true;\n errors.push({\n environment: envName,\n id: url,\n message: error instanceof Error ? error.message : String(error),\n });\n // The URL may still have a graph node with a file — it IS a graph\n // member (reachable), its transform just failed. Record membership\n // AND the failure so classification reports it as reachable with\n // subtreeUnknown, not as pending/unreferenced.\n node = await env.moduleGraph.getModuleByUrl(url).catch(() => undefined);\n if (node && isProjectFile(node.file, root, extensions)) {\n projectFiles.add(node.file);\n failedFiles.add(node.file);\n }\n continue;\n }\n\n if (!node) continue;\n visited++;\n if (isProjectFile(node.file, root, extensions)) projectFiles.add(node.file);\n\n for (const child of node.importedModules) {\n const childUrl = child.url ?? child.id;\n if (!childUrl || enqueued.has(childUrl)) continue;\n enqueued.add(childUrl);\n queue.push(childUrl);\n }\n }\n\n return { projectFiles, failedFiles, visited, deadlineHit };\n}\n\n/** Root-relative Vite URL for an absolute project file path.\n * Both arguments must be slash-normalized (the crawl normalizes at\n * the boundary, so callers always satisfy this). */\nfunction fileToUrl(file: string, root: string): string {\n return file.slice(root.length);\n}\n\n/** What one read of a module's original source yields. */\ninterface SourceFacts {\n directive: Directive;\n /** Import-specifier exclusivity signals (poison-scan). */\n signals: Poisoning[];\n}\n\n/**\n * Read a file's first directive and poison signals from its original\n * source on disk, in one read. A failure is recorded, not swallowed —\n * a lost 'use client' here would silently drop the whole client\n * subtree from the SSR crawl.\n */\nasync function sourceFactsOf(file: string, errors: CrawlError[]): Promise<SourceFacts> {\n try {\n const code = await readFile(file, 'utf8');\n return {\n directive: detectDirective(code, file),\n signals: importSignals(collectImportSpecifiers(code, file)),\n };\n } catch (error) {\n errors.push({\n environment: 'scan',\n id: file,\n message: `directive read failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n return { directive: null, signals: [] };\n }\n}\n\n/**\n * Crawl a project cold and classify every project file.\n *\n * Boots a middleware-mode Vite server with the app's own config (all\n * timber plugins, real resolution), crawls the RSC graph from the entry,\n * detects directives from original sources, crawls the SSR graph from\n * every `'use client'` boundary, and intersects the memberships.\n */\nexport async function crawlProject(options: CrawlOptions): Promise<CrawlReport> {\n const startedAt = Date.now();\n const deadlineAt = startedAt + (options.timeoutMs ?? 120_000);\n // Signal platform dev plugins (wrangler bindings) to skip boot work the\n // crawl never uses. Must be set before createServer runs plugin hooks.\n process.env.TIMBER_ANALYZE = '1';\n\n const { createServer } = await import('vite');\n const server: ViteDevServer = await createServer({\n root: options.root,\n configFile: options.configFile,\n server: { middlewareMode: true, watch: null },\n logLevel: 'error',\n });\n\n try {\n // The root must live in the same path space as the graph nodes.\n // By default Vite resolves through symlinks, so nodes carry\n // realpaths and a symlinked root (pnpm workspaces, ~ links) would\n // string-mismatch every file and misclassify the whole project as\n // unreferenced (design/43 open question 3). Under\n // resolve.preserveSymlinks the graphs keep the SYMLINK paths, and\n // realpathing would create exactly the mismatch it normally\n // prevents — honor the resolved setting. Slash-normalize either\n // way: module-node paths are slash-normalized even on Windows.\n const root = normalizePath(\n server.config.resolve.preserveSymlinks\n ? server.config.root\n : await realpath(server.config.root)\n );\n // Structural assignment, no cast: CrawlEnvironment/CrawlModuleNode\n // are supertypes of Vite's DevEnvironment/EnvironmentModuleNode for\n // the members the BFS reads, so an upstream shape change fails here\n // at compile time instead of inside the walk.\n const rscEnv: CrawlEnvironment | undefined = server.environments.rsc;\n const ssrEnv: CrawlEnvironment | undefined = server.environments.ssr;\n if (!rscEnv?.transformRequest || !ssrEnv?.transformRequest) {\n throw new Error('[timber] crawl requires the rsc and ssr Vite environments');\n }\n\n const errors: CrawlError[] = [];\n\n // appDir and pageExtensions come from the booted server's own\n // resolved timber config (the root-sync plugin api — the single\n // supported way to read it; cf. cli-check.ts). Resolved before\n // Phase 1 so the scan extension set is available to isProjectFile\n // and scanProjectSourceFiles — custom pageExtensions must be\n // admitted throughout, or graph membership disagrees with the\n // disk scan and fingerprint (TIM-1392).\n const rootSyncPlugin = server.config.plugins.find((p) => p.name === 'timber-root-sync');\n let appDir: string;\n let pageExtensions: readonly string[] | undefined;\n if (rootSyncPlugin && isTimberPluginApi(rootSyncPlugin.api)) {\n const pluginCtx = rootSyncPlugin.api.getContext();\n appDir = pluginCtx.appDir;\n pageExtensions = pluginCtx.config.pageExtensions;\n } else {\n try {\n appDir = resolveAppDir(root);\n } catch {\n // No app directory at all — provisional convention hints just\n // never fire; graph-membership classification is unaffected.\n appDir = join(root, 'app');\n }\n }\n // Normalize once at the boundary: appDir comes from node:path\n // join() (backslashes on Windows) but all downstream comparisons\n // — classify, chains, isRouteRoot — expect slash-normalized paths.\n appDir = normalizePath(appDir);\n const extensions = buildSourceExtensions(pageExtensions);\n\n // Phase 1 — RSC graph from the entry.\n const rsc = await crawlEnvironment(\n rscEnv,\n 'rsc',\n [RSC_ENTRY_URL],\n root,\n deadlineAt,\n errors,\n extensions\n );\n\n // Phase 2 — directives + poison signals for everything discovered so\n // far plus files on disk that no graph reached (the provisional/\n // pending set). The disk walk is the same one the graph cache\n // fingerprints (scan.ts) so cache validity covers exactly this set.\n const diskFiles: string[] = [];\n await scanProjectSourceFiles(\n root,\n diskFiles,\n (failure) => errors.push({ environment: 'scan', id: failure.dir, message: failure.message }),\n 0,\n extensions\n );\n // One path space throughout: graph paths are already slash-\n // normalized, disk-walk paths are node-joined (backslashed on\n // Windows) — mixing them in one Set would duplicate every file.\n const allFiles = new Set([...rsc.projectFiles, ...diskFiles.map(normalizePath)]);\n const facts = new Map<string, SourceFacts>();\n for (const file of allFiles) {\n facts.set(file, await sourceFactsOf(file, errors));\n }\n\n // Phase 3 — SSR graph from every RSC-REACHABLE 'use client'\n // boundary. SSR renders exactly the tree the browser hydrates, so\n // this yields the client tree (boundaries plus everything they\n // transitively import). Disk-only boundaries are deliberately NOT\n // seeded: an unreferenced 'use client' file must keep its\n // provisional pending classification, not get promoted to a\n // confirmed boundary (and drag its imports in as client-internal)\n // by the act of analyzing it.\n const boundarySeeds = [...rsc.projectFiles]\n .filter((file) => facts.get(file)?.directive === 'use client')\n .map((file) => fileToUrl(file, root));\n const ssr = await crawlEnvironment(\n ssrEnv,\n 'ssr',\n boundarySeeds,\n root,\n deadlineAt,\n errors,\n extensions\n );\n\n // SSR-only discoveries (client-internal files) need facts too.\n for (const file of ssr.projectFiles) {\n if (!facts.has(file)) {\n facts.set(file, await sourceFactsOf(file, errors));\n allFiles.add(file);\n }\n }\n\n const classifications = classifyModules(\n [...allFiles].map((file) => ({\n file,\n inRsc: rsc.projectFiles.has(file),\n inSsr: ssr.projectFiles.has(file),\n // The crawl transforms unconditionally, so the untransformed\n // frontier only arises where the transform itself failed.\n ssrTransformed: ssr.projectFiles.has(file) ? !ssr.failedFiles.has(file) : undefined,\n directive: facts.get(file)?.directive ?? null,\n })),\n { appDir, pageExtensions }\n );\n\n // Phase 4 — chains, from the live graphs of the still-open server.\n // ChainGraph is a structural supertype of Vite's module graphs\n // (TIM-1382), so the assignment is cast-free and an upstream shape\n // change fails at compile time.\n const rscChainGraph: ChainGraph = server.environments.rsc.moduleGraph;\n const ssrChainGraph: ChainGraph = server.environments.ssr.moduleGraph;\n const chainCtx = {\n rsc: rscChainGraph,\n ssr: ssrChainGraph,\n appDir,\n pageExtensions,\n // facts keys are already slash-normalized (allFiles is built from\n // graph membership + diskFiles.map(normalizePath)), so no\n // per-lookup normalization is needed.\n getDirective: (file: string) => facts.get(file)?.directive ?? null,\n };\n\n const modules: GraphModuleReport[] = classifications.map((classification) => ({\n ...classification,\n boundary: facts.get(classification.file)?.directive === 'use client',\n chains: stitchImportChains(classification.file, chainCtx),\n poisonings: poisoningsFor(\n classification.kind,\n classification.pending,\n facts.get(classification.file)?.signals ?? []\n ),\n }));\n\n return {\n root,\n incomplete: rsc.deadlineHit || ssr.deadlineHit,\n modules,\n errors,\n stats: {\n rscGraphModules: rsc.visited,\n ssrGraphModules: ssr.visited,\n projectFiles: allFiles.size,\n durationMs: Date.now() - startedAt,\n },\n };\n } finally {\n // Bound the cleanup: after a deadline expiry the very transforms\n // that hung are what server.close() waits on — an unbounded await\n // here would hang the child past its own --timeout and the report\n // would never be emitted. The child's process.exit reaps whatever\n // close left behind.\n await Promise.race([\n server.close().catch(() => {}),\n new Promise<void>((resolveTimer) => setTimeout(resolveTimer, 5_000)),\n ]);\n }\n}\n","/**\n * crawl-entry — child-process entry point for the cold crawl.\n *\n * The crawl's memory footprint is transient but large (~1.3 GB RSS on\n * plugin-heavy apps — design/47 §6), so consumers spawn this module as a\n * short-lived child process instead of crawling in-process:\n *\n * node --import tsx/esm crawl-entry.ts --root <dir> [--config <file>]\n * [--out <file>] [--timeout <ms>]\n *\n * With `--out`, the JSON report is written to that file. Otherwise it is\n * printed to stdout after a marker line — plugins in the booted app may\n * write their own stdout noise, so consumers parse everything after the\n * marker, not the whole stream.\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { crawlProject, CRAWL_RESULT_MARKER } from './crawl.ts';\n\nfunction readFlag(argv: string[], name: string): string | undefined {\n const index = argv.indexOf(name);\n return index !== -1 ? argv[index + 1] : undefined;\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const rootArg = readFlag(argv, '--root');\n if (!rootArg) {\n process.stderr.write('usage: crawl-entry --root <dir> [--config <file>] [--out <file>]\\n');\n process.exitCode = 2;\n return;\n }\n\n const timeoutArg = readFlag(argv, '--timeout');\n const report = await crawlProject({\n root: resolve(rootArg),\n configFile: readFlag(argv, '--config'),\n timeoutMs: timeoutArg ? Number(timeoutArg) : undefined,\n });\n\n const json = JSON.stringify(report);\n const outFile = readFlag(argv, '--out');\n if (outFile) {\n await writeFile(resolve(outFile), json);\n } else {\n // Await the flush: stdout to a pipe is async, and process.exit()\n // below does NOT flush pending pipe writes — a large report would\n // be truncated right after the marker.\n await new Promise<void>((resolveWrite, rejectWrite) => {\n process.stdout.write(`\\n${CRAWL_RESULT_MARKER}\\n${json}\\n`, (error) =>\n error ? rejectWrite(error) : resolveWrite()\n );\n });\n }\n}\n\nmain().then(\n () => {\n // The booted app's plugins may hold handles (watchers, sockets) past\n // server.close(); the crawl is a read-only child, so exit explicitly.\n process.exit(process.exitCode ?? 0);\n },\n (error) => {\n process.stderr.write(`[timber] crawl failed: ${error?.stack ?? String(error)}\\n`);\n process.exit(1);\n }\n);\n"],"mappings":"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"}
1
+ {"version":3,"file":"crawl-entry.js","names":[],"sources":["../../src/analyze/crawl.ts","../../src/analyze/crawl-entry.ts"],"sourcesContent":["/**\n * crawl — programmatic middleware-mode crawl (the cold path).\n *\n * Boots `createServer({ middlewareMode: true })` — no HTTP port, no route\n * rendering — and BFS-walks `env.transformRequest(url)` → importedModules\n * edges from `virtual:timber-rsc-entry`, then from every discovered\n * `'use client'` boundary through the SSR environment. This reuses Vite's\n * actual resolution (aliases, tsconfig paths, react-server export\n * conditions, virtual modules), so it cannot drift from what dev or the\n * build does; hand-rolled resolution was evaluated and rejected\n * (design/47 §\"Verified Prototype Findings\").\n *\n * Cost model (design/47 §6): 2.5 s on small apps to ~10 s on dep-heavy\n * plugin stacks, with a transient ~1.3 GB RSS — callers MUST run this in\n * a short-lived child process (see crawl-entry.ts) and cache the\n * classification output, not the Vite transforms. The crawl always\n * covers the full project: one crawl per project state, never per\n * question — marginal per-file cost is ~zero once booted.\n *\n * `TIMBER_ANALYZE=1` is set before the server boots so platform dev\n * plugins (wrangler bindings — a measured ~6.8 s remote-bindings\n * handshake on website) skip work the crawl never needs: classification\n * wants import edges, not live Cloudflare connections.\n *\n * Design docs: 47-module-environment-tooling.md §6, §8.\n */\n\nimport { readFile, realpath } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { ViteDevServer } from 'vite';\nimport { normalizePath } from 'vite';\nimport {\n classifyModules,\n detectDirective,\n type Directive,\n type ModuleClassification,\n} from './classify.ts';\nimport { buildSourceExtensions, scanProjectSourceFiles, isScannableSourceFile } from './scan.ts';\nimport { stitchImportChains, type ChainGraph, type ImportChain } from './chains.ts';\nimport {\n collectImportSpecifiers,\n importSignals,\n poisoningsFor,\n type Poisoning,\n} from './poison-scan.ts';\nimport { isTimberPluginApi, resolveAppDir } from '../plugin-context.ts';\n\nconst RSC_ENTRY_URL = 'virtual:timber-rsc-entry';\n\n/**\n * Marker preceding the JSON report on crawl-entry's stdout. Lives here\n * (not in crawl-entry.ts) so consumers can import it without executing\n * the child entry's main().\n */\nexport const CRAWL_RESULT_MARKER = '__TIMBER_CRAWL_RESULT__';\n\nexport interface CrawlOptions {\n /** Project root (the app directory containing vite.config). */\n root: string;\n /** Explicit Vite config file; discovered from root when omitted. */\n configFile?: string;\n /** Overall crawl deadline. Default 120s. */\n timeoutMs?: number;\n}\n\nexport interface CrawlError {\n /** 'scan' covers disk-scan and directive-read failures. */\n environment: 'rsc' | 'ssr' | 'scan';\n id: string;\n message: string;\n}\n\n/**\n * One project file's full graph answer — the classification plus the\n * remaining JSON-contract fields (`boundary`, `chains`, `poisonings`)\n * that need the open graphs / original sources to compute. This is the\n * per-module unit `timber graph` caches and serves.\n */\nexport interface GraphModuleReport extends ModuleClassification {\n /** True when the file itself carries `'use client'`. */\n boundary: boolean;\n /** Boundary-aware import chains (design/47 §7); [] when in no graph. */\n chains: ImportChain[];\n /** Report-only poisoning conflicts (design/47 §3); [] when clean. */\n poisonings: Poisoning[];\n}\n\nexport interface CrawlReport {\n root: string;\n /**\n * True when the crawl deadline expired mid-walk — the report is\n * truncated, not wrong, and the condition is transient (a slow\n * machine, a cold dep-optimizer). Consumers must not cache an\n * incomplete report: with unchanged files it would be served forever,\n * never retrying after the transient cause cleared. Deterministic\n * per-module transform failures do NOT set this — they recur on\n * retry, so caching them is correct.\n */\n incomplete: boolean;\n /** Classification of every project file, sorted by path. */\n modules: GraphModuleReport[];\n /**\n * Transform failures encountered during the BFS. These are classified,\n * not swallowed — CSS/asset URLs and non-transformable modules land\n * here (44 on website in the §6 measurement) and the crawl continues.\n */\n errors: CrawlError[];\n stats: {\n rscGraphModules: number;\n ssrGraphModules: number;\n projectFiles: number;\n durationMs: number;\n };\n}\n\n/** Marker error for deadline expiry — structurally distinguishable\n * from a deterministic transform failure (consumers must not cache a\n * deadline-truncated report; a transform failure recurs on retry and\n * caches fine). */\nclass CrawlDeadlineError extends Error {}\n\n/** Race a promise against the crawl deadline. */\nfunction withDeadline<T>(promise: Promise<T>, remainingMs: number, label: string): Promise<T> {\n return new Promise<T>((resolvePromise, rejectPromise) => {\n const timer = setTimeout(\n () =>\n rejectPromise(\n new CrawlDeadlineError(`crawl deadline exceeded while transforming ${label}`)\n ),\n Math.max(remainingMs, 1)\n );\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolvePromise(value);\n },\n (error) => {\n clearTimeout(timer);\n rejectPromise(error);\n }\n );\n });\n}\n\n/** Duck-typed view of the Vite dev environment surface the crawl uses. */\nexport interface CrawlEnvironment {\n transformRequest(url: string): Promise<unknown>;\n moduleGraph: {\n getModuleByUrl(url: string): Promise<CrawlModuleNode | undefined>;\n };\n}\n\ninterface CrawlModuleNode {\n id: string | null;\n url: string;\n file: string | null;\n importedModules: Set<CrawlModuleNode>;\n transformResult: unknown;\n}\n\ninterface EnvCrawlResult {\n /** Absolute paths of project files reached in this environment. */\n projectFiles: Set<string>;\n /** Project files whose own transform failed (membership unconfirmed subtree). */\n failedFiles: Set<string>;\n /** Total graph modules visited (project + deps + virtual). */\n visited: number;\n /** The deadline expired mid-walk — the result is truncated. */\n deadlineHit: boolean;\n}\n\n/**\n * Whether a graph node's file belongs in the report. `root` must be\n * slash-normalized; module-node paths already are (Vite normalizes\n * them, even on Windows — comparing against a backslashed root would\n * reject every physical module there and misreport the whole app as\n * pending). The rule set is the scan's own (isScannableSourceFile), so\n * report membership, disk-scan coverage, and the cache fingerprint are\n * one set — a graph-reachable file the fingerprint never tracked would\n * let its edits serve a stale cache (codex review, PR #1056).\n */\nfunction isProjectFile(file: string | null, root: string, extensions?: RegExp): file is string {\n if (!file) return false;\n return isScannableSourceFile(normalizePath(file), root, extensions);\n}\n\n/**\n * BFS one environment from the given seed URLs, transforming every\n * discovered module unconditionally. Transforming (rather than reading\n * live graphs) is what closes the §8 blind spot: modules behind\n * unexecuted dynamic imports are transformed here like any other edge.\n *\n * Exported for tests (deadline behavior is exercised with a fake env).\n */\nexport async function crawlEnvironment(\n env: CrawlEnvironment,\n envName: 'rsc' | 'ssr',\n seeds: string[],\n root: string,\n deadlineAt: number,\n errors: CrawlError[],\n extensions?: RegExp\n): Promise<EnvCrawlResult> {\n const queue = [...seeds];\n const enqueued = new Set(queue);\n const projectFiles = new Set<string>();\n const failedFiles = new Set<string>();\n let visited = 0;\n let deadlineHit = false;\n\n // Index-cursor BFS — the queue only grows, so this walks each entry once.\n for (let cursor = 0; cursor < queue.length; cursor++) {\n const url = queue[cursor];\n const remaining = deadlineAt - Date.now();\n if (remaining <= 0) {\n errors.push({ environment: envName, id: url, message: 'crawl deadline exceeded' });\n deadlineHit = true;\n break;\n }\n\n let node: CrawlModuleNode | undefined;\n try {\n await withDeadline(env.transformRequest(url), remaining, url);\n node = await env.moduleGraph.getModuleByUrl(url);\n } catch (error) {\n if (error instanceof CrawlDeadlineError) deadlineHit = true;\n errors.push({\n environment: envName,\n id: url,\n message: error instanceof Error ? error.message : String(error),\n });\n // The URL may still have a graph node with a file — it IS a graph\n // member (reachable), its transform just failed. Record membership\n // AND the failure so classification reports it as reachable with\n // subtreeUnknown, not as pending/unreferenced.\n node = await env.moduleGraph.getModuleByUrl(url).catch(() => undefined);\n if (node && isProjectFile(node.file, root, extensions)) {\n projectFiles.add(node.file);\n failedFiles.add(node.file);\n }\n continue;\n }\n\n if (!node) continue;\n visited++;\n if (isProjectFile(node.file, root, extensions)) projectFiles.add(node.file);\n\n for (const child of node.importedModules) {\n const childUrl = child.url ?? child.id;\n if (!childUrl || enqueued.has(childUrl)) continue;\n enqueued.add(childUrl);\n queue.push(childUrl);\n }\n }\n\n return { projectFiles, failedFiles, visited, deadlineHit };\n}\n\n/** Root-relative Vite URL for an absolute project file path.\n * Both arguments must be slash-normalized (the crawl normalizes at\n * the boundary, so callers always satisfy this). */\nfunction fileToUrl(file: string, root: string): string {\n return file.slice(root.length);\n}\n\n/** What one read of a module's original source yields. */\ninterface SourceFacts {\n directive: Directive;\n /** Import-specifier exclusivity signals (poison-scan). */\n signals: Poisoning[];\n}\n\n/**\n * Read a file's first directive and poison signals from its original\n * source on disk, in one read. A failure is recorded, not swallowed —\n * a lost 'use client' here would silently drop the whole client\n * subtree from the SSR crawl.\n */\nasync function sourceFactsOf(file: string, errors: CrawlError[]): Promise<SourceFacts> {\n try {\n const code = await readFile(file, 'utf8');\n return {\n directive: detectDirective(code, file),\n signals: importSignals(collectImportSpecifiers(code, file)),\n };\n } catch (error) {\n errors.push({\n environment: 'scan',\n id: file,\n message: `directive read failed: ${error instanceof Error ? error.message : String(error)}`,\n });\n return { directive: null, signals: [] };\n }\n}\n\n/**\n * Crawl a project cold and classify every project file.\n *\n * Boots a middleware-mode Vite server with the app's own config (all\n * timber plugins, real resolution), crawls the RSC graph from the entry,\n * detects directives from original sources, crawls the SSR graph from\n * every `'use client'` boundary, and intersects the memberships.\n */\nexport async function crawlProject(options: CrawlOptions): Promise<CrawlReport> {\n const startedAt = Date.now();\n const deadlineAt = startedAt + (options.timeoutMs ?? 120_000);\n // Signal platform dev plugins (wrangler bindings) to skip boot work the\n // crawl never uses. Must be set before createServer runs plugin hooks.\n process.env.TIMBER_ANALYZE = '1';\n\n const { createServer } = await import('vite');\n const server: ViteDevServer = await createServer({\n root: options.root,\n configFile: options.configFile,\n server: { middlewareMode: true, watch: null },\n logLevel: 'error',\n });\n\n try {\n // The root must live in the same path space as the graph nodes.\n // By default Vite resolves through symlinks, so nodes carry\n // realpaths and a symlinked root (pnpm workspaces, ~ links) would\n // string-mismatch every file and misclassify the whole project as\n // unreferenced (design/43 open question 3). Under\n // resolve.preserveSymlinks the graphs keep the SYMLINK paths, and\n // realpathing would create exactly the mismatch it normally\n // prevents — honor the resolved setting. Slash-normalize either\n // way: module-node paths are slash-normalized even on Windows.\n const root = normalizePath(\n server.config.resolve.preserveSymlinks\n ? server.config.root\n : await realpath(server.config.root)\n );\n // Structural assignment, no cast: CrawlEnvironment/CrawlModuleNode\n // are supertypes of Vite's DevEnvironment/EnvironmentModuleNode for\n // the members the BFS reads, so an upstream shape change fails here\n // at compile time instead of inside the walk.\n const rscEnv: CrawlEnvironment | undefined = server.environments.rsc;\n const ssrEnv: CrawlEnvironment | undefined = server.environments.ssr;\n if (!rscEnv?.transformRequest || !ssrEnv?.transformRequest) {\n throw new Error('[timber] crawl requires the rsc and ssr Vite environments');\n }\n\n const errors: CrawlError[] = [];\n\n // appDir and pageExtensions come from the booted server's own\n // resolved timber config (the root-sync plugin api — the single\n // supported way to read it; cf. cli-check.ts). Resolved before\n // Phase 1 so the scan extension set is available to isProjectFile\n // and scanProjectSourceFiles — custom pageExtensions must be\n // admitted throughout, or graph membership disagrees with the\n // disk scan and fingerprint (TIM-1392).\n const rootSyncPlugin = server.config.plugins.find((p) => p.name === 'timber-root-sync');\n let appDir: string;\n let pageExtensions: readonly string[] | undefined;\n if (rootSyncPlugin && isTimberPluginApi(rootSyncPlugin.api)) {\n const pluginCtx = rootSyncPlugin.api.getContext();\n appDir = pluginCtx.appDir;\n pageExtensions = pluginCtx.config.pageExtensions;\n } else {\n try {\n appDir = resolveAppDir(root);\n } catch {\n // No app directory at all — provisional convention hints just\n // never fire; graph-membership classification is unaffected.\n appDir = join(root, 'app');\n }\n }\n // Normalize once at the boundary: appDir comes from node:path\n // join() (backslashes on Windows) but all downstream comparisons\n // — classify, chains, isRouteRoot — expect slash-normalized paths.\n appDir = normalizePath(appDir);\n const extensions = buildSourceExtensions(pageExtensions);\n\n // Phase 1 — RSC graph from the entry.\n const rsc = await crawlEnvironment(\n rscEnv,\n 'rsc',\n [RSC_ENTRY_URL],\n root,\n deadlineAt,\n errors,\n extensions\n );\n\n // Phase 2 — directives + poison signals for everything discovered so\n // far plus files on disk that no graph reached (the provisional/\n // pending set). The disk walk is the same one the graph cache\n // fingerprints (scan.ts) so cache validity covers exactly this set.\n const diskFiles: string[] = [];\n await scanProjectSourceFiles(\n root,\n diskFiles,\n (failure) => errors.push({ environment: 'scan', id: failure.dir, message: failure.message }),\n 0,\n extensions\n );\n // One path space throughout: graph paths are already slash-\n // normalized, disk-walk paths are node-joined (backslashed on\n // Windows) — mixing them in one Set would duplicate every file.\n const allFiles = new Set([...rsc.projectFiles, ...diskFiles.map(normalizePath)]);\n const facts = new Map<string, SourceFacts>();\n for (const file of allFiles) {\n facts.set(file, await sourceFactsOf(file, errors));\n }\n\n // Phase 3 — SSR graph from every RSC-REACHABLE 'use client'\n // boundary. SSR renders exactly the tree the browser hydrates, so\n // this yields the client tree (boundaries plus everything they\n // transitively import). Disk-only boundaries are deliberately NOT\n // seeded: an unreferenced 'use client' file must keep its\n // provisional pending classification, not get promoted to a\n // confirmed boundary (and drag its imports in as client-internal)\n // by the act of analyzing it.\n const boundarySeeds = [...rsc.projectFiles]\n .filter((file) => facts.get(file)?.directive === 'use client')\n .map((file) => fileToUrl(file, root));\n const ssr = await crawlEnvironment(\n ssrEnv,\n 'ssr',\n boundarySeeds,\n root,\n deadlineAt,\n errors,\n extensions\n );\n\n // SSR-only discoveries (client-internal files) need facts too.\n for (const file of ssr.projectFiles) {\n if (!facts.has(file)) {\n facts.set(file, await sourceFactsOf(file, errors));\n allFiles.add(file);\n }\n }\n\n const classifications = classifyModules(\n [...allFiles].map((file) => ({\n file,\n inRsc: rsc.projectFiles.has(file),\n inSsr: ssr.projectFiles.has(file),\n // The crawl transforms unconditionally, so the untransformed\n // frontier only arises where the transform itself failed.\n ssrTransformed: ssr.projectFiles.has(file) ? !ssr.failedFiles.has(file) : undefined,\n directive: facts.get(file)?.directive ?? null,\n })),\n { appDir, pageExtensions }\n );\n\n // Phase 4 — chains, from the live graphs of the still-open server.\n // ChainGraph is a structural supertype of Vite's module graphs\n // (TIM-1382), so the assignment is cast-free and an upstream shape\n // change fails at compile time.\n const rscChainGraph: ChainGraph = server.environments.rsc.moduleGraph;\n const ssrChainGraph: ChainGraph = server.environments.ssr.moduleGraph;\n const chainCtx = {\n rsc: rscChainGraph,\n ssr: ssrChainGraph,\n appDir,\n pageExtensions,\n // facts keys are already slash-normalized (allFiles is built from\n // graph membership + diskFiles.map(normalizePath)), so no\n // per-lookup normalization is needed.\n getDirective: (file: string) => facts.get(file)?.directive ?? null,\n };\n\n const modules: GraphModuleReport[] = classifications.map((classification) => ({\n ...classification,\n boundary: facts.get(classification.file)?.directive === 'use client',\n chains: stitchImportChains(classification.file, chainCtx),\n poisonings: poisoningsFor(\n classification.kind,\n classification.pending,\n facts.get(classification.file)?.signals ?? []\n ),\n }));\n\n return {\n root,\n incomplete: rsc.deadlineHit || ssr.deadlineHit,\n modules,\n errors,\n stats: {\n rscGraphModules: rsc.visited,\n ssrGraphModules: ssr.visited,\n projectFiles: allFiles.size,\n durationMs: Date.now() - startedAt,\n },\n };\n } finally {\n // Bound the cleanup: after a deadline expiry the very transforms\n // that hung are what server.close() waits on — an unbounded await\n // here would hang the child past its own --timeout and the report\n // would never be emitted. The child's process.exit reaps whatever\n // close left behind.\n await Promise.race([\n server.close().catch(() => {}),\n new Promise<void>((resolveTimer) => setTimeout(resolveTimer, 5_000)),\n ]);\n }\n}\n","/**\n * crawl-entry — child-process entry point for the cold crawl.\n *\n * The crawl's memory footprint is transient but large (~1.3 GB RSS on\n * plugin-heavy apps — design/47 §6), so consumers spawn this module as a\n * short-lived child process instead of crawling in-process:\n *\n * node --import tsx/esm crawl-entry.ts --root <dir> [--config <file>]\n * [--out <file>] [--timeout <ms>]\n *\n * With `--out`, the JSON report is written to that file. Otherwise it is\n * printed to stdout after a marker line — plugins in the booted app may\n * write their own stdout noise, so consumers parse everything after the\n * marker, not the whole stream.\n */\n\nimport { writeFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\nimport { crawlProject, CRAWL_RESULT_MARKER } from './crawl.ts';\n\nfunction readFlag(argv: string[], name: string): string | undefined {\n const index = argv.indexOf(name);\n return index !== -1 ? argv[index + 1] : undefined;\n}\n\nasync function main(): Promise<void> {\n const argv = process.argv.slice(2);\n const rootArg = readFlag(argv, '--root');\n if (!rootArg) {\n process.stderr.write('usage: crawl-entry --root <dir> [--config <file>] [--out <file>]\\n');\n process.exitCode = 2;\n return;\n }\n\n const timeoutArg = readFlag(argv, '--timeout');\n const report = await crawlProject({\n root: resolve(rootArg),\n configFile: readFlag(argv, '--config'),\n timeoutMs: timeoutArg ? Number(timeoutArg) : undefined,\n });\n\n const json = JSON.stringify(report);\n const outFile = readFlag(argv, '--out');\n if (outFile) {\n await writeFile(resolve(outFile), json);\n } else {\n // Await the flush: stdout to a pipe is async, and process.exit()\n // below does NOT flush pending pipe writes — a large report would\n // be truncated right after the marker.\n await new Promise<void>((resolveWrite, rejectWrite) => {\n process.stdout.write(`\\n${CRAWL_RESULT_MARKER}\\n${json}\\n`, (error) =>\n error ? rejectWrite(error) : resolveWrite()\n );\n });\n }\n}\n\nmain().then(\n () => {\n // The booted app's plugins may hold handles (watchers, sockets) past\n // server.close(); the crawl is a read-only child, so exit explicitly.\n process.exit(process.exitCode ?? 0);\n },\n (error) => {\n process.stderr.write(`[timber] crawl failed: ${error?.stack ?? String(error)}\\n`);\n process.exit(1);\n }\n);\n"],"mappings":"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"}
@@ -378,7 +378,7 @@ async function printSingleFile(input, target, module, answer, json) {
378
378
  console.log(` environments: ${envs.length > 0 ? envs.join(", ") : "none"}`);
379
379
  if (module.subtreeUnknown) console.log(" note: reachable, but its subtree is unknown (never transformed)");
380
380
  if (module.chains.length > 0) {
381
- const { formatChains } = await import("../_chunks/chains-BoO51joc.js").then((n) => n.t);
381
+ const { formatChains } = await import("../_chunks/chains-BfoPFraI.js").then((n) => n.t);
382
382
  console.log(" import chains:");
383
383
  console.log(formatChains(module.chains, root));
384
384
  }
@@ -397,7 +397,7 @@ async function printDevServerText(data, configDir) {
397
397
  console.log(` environments: ${envList.length > 0 ? envList.join(", ") : "none"}`);
398
398
  if (data.subtreeUnknown) console.log(" note: reachable, but its subtree is unknown (never transformed)");
399
399
  if (data.chains.length > 0) {
400
- const { formatChains } = await import("../_chunks/chains-BoO51joc.js").then((n) => n.t);
400
+ const { formatChains } = await import("../_chunks/chains-BfoPFraI.js").then((n) => n.t);
401
401
  console.log(" import chains:");
402
402
  console.log(formatChains(data.chains, configDir));
403
403
  }
@@ -1 +1 @@
1
- {"version":3,"file":"poison-scan.d.ts","sourceRoot":"","sources":["../../src/analyze/poison-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAE7D,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,OAAO,EAAE,aAAa,GAAG,aAAa,CAAC;IACvC,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;CACrB;AAiBD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAsD5E;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAyB/D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,SAAS,EAAE,GACnB,SAAS,EAAE,CAUb"}
1
+ {"version":3,"file":"poison-scan.d.ts","sourceRoot":"","sources":["../../src/analyze/poison-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAAqC,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnF,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB,yEAAyE;IACzE,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,OAAO,EAAE,aAAa,GAAG,aAAa,CAAC;IACvC,sDAAsD;IACtD,WAAW,EAAE,MAAM,CAAC;CACrB;AAcD;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAsD5E;AAED,8DAA8D;AAC9D,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAAE,CAyB/D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAChB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,SAAS,EAAE,GACnB,SAAS,EAAE,CAUb"}
@@ -1,3 +1,3 @@
1
- import { _ as setDefaultSingleflightTimeout, f as MemoryCacheHandler, g as setCacheHandler, h as getCacheHandler, m as RedisCacheHandler, p as TagAwareCacheHandler, t as cache } from "../_chunks/cache-api-DGdYfNJn.js";
2
- import { t as estimateByteSize } from "../_chunks/sizeof-BM1409x2.js";
1
+ import { _ as setDefaultSingleflightTimeout, f as MemoryCacheHandler, g as setCacheHandler, h as getCacheHandler, m as RedisCacheHandler, p as TagAwareCacheHandler, t as cache } from "../_chunks/cache-api-LA3sBpUS.js";
2
+ import { t as estimateByteSize } from "../_chunks/sizeof-QPE5nd3u.js";
3
3
  export { MemoryCacheHandler, RedisCacheHandler, TagAwareCacheHandler, cache, estimateByteSize, getCacheHandler, setCacheHandler, setDefaultSingleflightTimeout };
@@ -1,4 +1,4 @@
1
- import { t as estimateByteSize } from "../../_chunks/sizeof-BM1409x2.js";
1
+ import { t as estimateByteSize } from "../../_chunks/sizeof-QPE5nd3u.js";
2
2
  //#region src/cache/stores/memory.ts
3
3
  var MemoryCacheStore = class {
4
4
  store = /* @__PURE__ */ new Map();
@@ -1,4 +1,4 @@
1
- import { u as workersCachePurgeAls } from "../_chunks/als-registry-DaxkVjt5.js";
1
+ import { l as workersCachePurgeAls } from "../_chunks/als-registry-BZqHCtq-.js";
2
2
  //#region src/server/workers-cache-bridge.ts
3
3
  /**
4
4
  * Per-request Workers Cache purge bridge — ALS bridge for Cloudflare adapter.
package/dist/cli.js CHANGED
@@ -90,7 +90,7 @@ async function runDev(options, _deps) {
90
90
  return;
91
91
  }
92
92
  const { fork } = await import("node:child_process");
93
- const { RESTART_EXIT_CODE } = await import("./_chunks/dev-server-v97rQH4b.js").then((n) => n.t);
93
+ const { RESTART_EXIT_CODE } = await import("./_chunks/dev-server-FKxptbnI.js").then((n) => n.t);
94
94
  while (true) {
95
95
  const result = await new Promise((resolve) => {
96
96
  const child = fork(process.argv[1], process.argv.slice(2), {
@@ -169,7 +169,7 @@ async function runPreview(options, _deps) {
169
169
  * `--project` names the tsconfig handed to tsgo.
170
170
  */
171
171
  async function runCheck(options, _deps) {
172
- const { checkRoutes, runTypeCheck } = await import("./_chunks/cli-check-ajNY3B2e.js");
172
+ const { checkRoutes, runTypeCheck } = await import("./_chunks/cli-check-BfQ54-UJ.js");
173
173
  const { root } = await checkRoutes({ viteConfigFile: options.config }, _deps);
174
174
  await runTypeCheck({
175
175
  root,
@@ -185,7 +185,7 @@ async function runCheck(options, _deps) {
185
185
  async function runSchema(args) {
186
186
  const subcommand = args[0];
187
187
  if (subcommand !== "sync") throw new Error(`Unknown schema subcommand: ${subcommand ?? "(none)"}. Usage: timber schema sync`);
188
- const { runSchemaSync, defaultCodecForSegment } = await import("./_chunks/cli-schema-sync-D2eI8jEg.js").then((n) => n.t);
188
+ const { runSchemaSync, defaultCodecForSegment } = await import("./_chunks/cli-schema-sync-czh2dsLs.js").then((n) => n.t);
189
189
  const result = runSchemaSync(process.cwd());
190
190
  if (result.filesystemSegments.length === 0) {
191
191
  console.log("[timber] No dynamic segments found in app/.");
@@ -1 +1 @@
1
- {"version":3,"file":"action-dispatch.d.ts","sourceRoot":"","sources":["../../../src/client/browser-entry/action-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAyBH,wBAAgB,kBAAkB,IAAI,IAAI,CAmMzC"}
1
+ {"version":3,"file":"action-dispatch.d.ts","sourceRoot":"","sources":["../../../src/client/browser-entry/action-dispatch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAWH,wBAAgB,kBAAkB,IAAI,IAAI,CAyQzC"}
@@ -1 +1 @@
1
- {"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../../../src/client/browser-entry/hydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAItD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAwCzD,UAAU,cAAc;IACtB,qDAAqD;IACrD,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,0EAA0E;IAC1E,SAAS,EAAE,aAAa,CAAC;IACzB,mFAAmF;IACnF,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;CACtC;AAcD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,cAAc,GAAG,IAAI,CAmClF"}
1
+ {"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../../../src/client/browser-entry/hydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAItD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACvD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAwCzD,UAAU,cAAc;IACtB,qDAAqD;IACrD,SAAS,EAAE,eAAe,GAAG,IAAI,CAAC;IAClC,0EAA0E;IAC1E,SAAS,EAAE,aAAa,CAAC;IACzB,mFAAmF;IACnF,OAAO,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;CACtC;AA8BD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,UAAU,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,cAAc,GAAG,IAAI,CAoClF"}
@@ -1 +1 @@
1
- {"version":3,"file":"router-init.d.ts","sourceRoot":"","sources":["../../../src/client/browser-entry/router-init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAK9B,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,EAAE,gBAAgB,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,gBAAgB,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACjD;;;;;OAKG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,KAAK,IAAI,KAAK,IAAI,CAAC;CAC5F;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAwV/E"}
1
+ {"version":3,"file":"router-init.d.ts","sourceRoot":"","sources":["../../../src/client/browser-entry/router-init.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAOH,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG/D,OAAO,EAGL,KAAK,uBAAuB,EAC7B,MAAM,sBAAsB,CAAC;AAK9B,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,EAAE,gBAAgB,CAAC;IACzB,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,gBAAgB,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACjD;;;;;OAKG;IACH,OAAO,EAAE,CAAC,IAAI,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,YAAY,KAAK,IAAI,KAAK,IAAI,CAAC;CAC5F;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CA0U/E"}
@@ -1,62 +1,14 @@
1
1
  /**
2
2
  * Client-side form utilities for server actions.
3
3
  *
4
- * Exports a typed `useActionState` that understands the action builder's result shape.
5
- * Result is typed to:
6
- * { data: T } | { validationErrors: Record<string, string[]> } | { serverError: { code, data? } } | null
7
- *
8
- * The action builder emits a function that satisfies both the direct call signature
9
- * and React's `(prevState, formData) => Promise<State>` contract.
4
+ * Forms use React's own `useActionState`: an action built with
5
+ * `createActionClient` already has the `(prevState, payload)` signature it
6
+ * calls, and the result is typed from it. `parseFormErrors` reads the errors
7
+ * out of that result.
10
8
  *
11
9
  * See design/08-forms-and-actions.md §"Client-Side Form Mechanics"
12
10
  */
13
- import type { ActionFn, ActionResult, InputHint, ValidationErrors } from '../server/action-client.ts';
14
- import type { FormFlashData } from '../server/form-flash.ts';
15
- /**
16
- * The action function type accepted by useActionState.
17
- * Must satisfy React's (prevState, formData) => Promise<State> contract.
18
- */
19
- export type UseActionStateFn<TData> = (prevState: ActionResult<TData> | null, formData: FormData) => Promise<ActionResult<TData>>;
20
- /**
21
- * Return type of useActionState.
22
- * [result, formAction, isPending, errors]
23
- * The 4th element is auto-derived from result via useFormErrors logic.
24
- */
25
- export type UseActionStateReturn<TData> = [
26
- result: ActionResult<TData> | null,
27
- formAction: (formData: FormData) => void,
28
- isPending: boolean,
29
- errors: FormErrorsResult
30
- ];
31
- /**
32
- * Typed wrapper around React 19's `useActionState` that understands
33
- * the timber action builder's result shape.
34
- *
35
- * @param action - A server action created with createActionClient or a raw 'use server' function.
36
- * @param initialState - Initial state, typically `null`. Pass `getFormFlash()` for no-JS
37
- * progressive enhancement — the flash seeds the initial state so the form has a
38
- * single source of truth for both with-JS and no-JS paths.
39
- * @param permalink - Optional permalink for progressive enhancement (no-JS fallback URL).
40
- *
41
- * @example
42
- * ```tsx
43
- * 'use client'
44
- * import { useActionState } from '@timber-js/app/client'
45
- * import { createTodo } from './actions'
46
- *
47
- * export function NewTodoForm({ flash }) {
48
- * const [result, action, isPending] = useActionState(createTodo, flash)
49
- * return (
50
- * <form action={action}>
51
- * <input name="title" />
52
- * {result?.validationErrors?.title && <p>{result.validationErrors.title}</p>}
53
- * <button disabled={isPending}>Add</button>
54
- * </form>
55
- * )
56
- * }
57
- * ```
58
- */
59
- export declare function useActionState<TData>(action: UseActionStateFn<TData>, initialState: ActionResult<TData> | FormFlashData | null, permalink?: string): UseActionStateReturn<TData>;
11
+ import type { ActionFn, ActionResult, InputHint } from '../server/action-client.ts';
60
12
  /**
61
13
  * Hook for calling a server action imperatively (not via a form).
62
14
  * Returns [execute, isPending] where execute accepts the input directly.
@@ -73,7 +25,7 @@ export declare function useFormAction<TData = unknown, TInput = unknown>(action:
73
25
  (...args: undefined extends TInput ? [input?: InputHint<TInput>] : [input: InputHint<TInput>]) => Promise<ActionResult<TData>>,
74
26
  boolean
75
27
  ];
76
- /** Return type of the errors element in useActionState. */
28
+ /** What `parseFormErrors` reads out of an action result. */
77
29
  export interface FormErrorsResult {
78
30
  /** Per-field validation errors keyed by field name. */
79
31
  fieldErrors: Record<string, string[]>;
@@ -90,15 +42,16 @@ export interface FormErrorsResult {
90
42
  getFieldError: (field: string) => string | null;
91
43
  }
92
44
  /**
93
- * Derive FormErrorsResult from an action result.
94
- * Used internally by useActionState 4th tuple element.
95
- * @internal — exported for test access only.
45
+ * Read the errors out of an action result — the state `useActionState`
46
+ * returns. `_root` validation errors are form-level; every other key is a
47
+ * field.
48
+ *
49
+ * @example
50
+ * ```tsx
51
+ * const [result, action, isPending] = useActionState(createTodo, null)
52
+ * const errors = parseFormErrors(result)
53
+ * errors.getFieldError('title') // first message for the field, or null
54
+ * ```
96
55
  */
97
- export declare function deriveFormErrors<TData>(result: ActionResult<TData> | {
98
- validationErrors?: ValidationErrors;
99
- serverError?: {
100
- code: string;
101
- data?: Record<string, unknown>;
102
- };
103
- } | null): FormErrorsResult;
56
+ export declare function parseFormErrors<TData>(result: ActionResult<TData> | null | undefined): FormErrorsResult;
104
57
  //# sourceMappingURL=form.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/client/form.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,SAAS,EACT,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAI7D;;;GAGG;AACH,MAAM,MAAM,gBAAgB,CAAC,KAAK,IAAI,CACpC,SAAS,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,EACrC,QAAQ,EAAE,QAAQ,KACf,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;AAElC;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,CAAC,KAAK,IAAI;IACxC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI;IAClC,UAAU,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI;IACxC,SAAS,EAAE,OAAO;IAClB,MAAM,EAAE,gBAAgB;CACzB,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAClC,MAAM,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAC/B,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,aAAa,GAAG,IAAI,EACxD,SAAS,CAAC,EAAE,MAAM,GACjB,oBAAoB,CAAC,KAAK,CAAC,CAU7B;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,OAAO,EAC7D,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,GAClF;IACD,CACE,GAAG,IAAI,EAAE,SAAS,SAAS,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,KACzF,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO;CACR,CAeA;AAID,2DAA2D;AAC3D,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,4CAA4C;IAC5C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,uDAAuD;IACvD,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACjD;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EACpC,MAAM,EACF,YAAY,CAAC,KAAK,CAAC,GACnB;IACE,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,WAAW,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC;CAChE,GACD,IAAI,GACP,gBAAgB,CA6ClB"}
1
+ {"version":3,"file":"form.d.ts","sourceRoot":"","sources":["../../src/client/form.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,SAAS,EAEV,MAAM,4BAA4B,CAAC;AAIpC;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,KAAK,GAAG,OAAO,EAAE,MAAM,GAAG,OAAO,EAC7D,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,GAClF;IACD,CACE,GAAG,IAAI,EAAE,SAAS,SAAS,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,KACzF,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO;CACR,CAuBA;AAID,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACtC,4CAA4C;IAC5C,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,uDAAuD;IACvD,WAAW,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,IAAI,CAAC;IACrE,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,wDAAwD;IACxD,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACjD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,KAAK,EACnC,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,SAAS,GAC7C,gBAAgB,CA2ClB"}
@@ -13,8 +13,8 @@ export type { NavigationTransitionType } from './router-types.ts';
13
13
  export { usePathname } from './use-pathname.ts';
14
14
  export { replaceUrl } from './shallow-url.ts';
15
15
  export { useSelectedLayoutSegment, useSelectedLayoutSegments, } from './use-selected-layout-segment.ts';
16
- export { useActionState, useFormAction } from './form.tsx';
17
- export type { UseActionStateFn, UseActionStateReturn, FormErrorsResult } from './form.tsx';
16
+ export { parseFormErrors, useFormAction } from './form.tsx';
17
+ export type { FormErrorsResult } from './form.tsx';
18
18
  export { useSegmentParams } from './use-segment-params.ts';
19
19
  export { useQueryStates } from './use-query-states.ts';
20
20
  export type { ClientCookieOptions, CookieSetter } from './use-cookie.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACpG,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAC9E,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnG,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA4CxF,MAAM,WAAW,YAAY;CAG5B;AACD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACxE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1E,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EACL,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC3D,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAK3F,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAY3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,IAAI,EAAE,iBAAiB,EAAE,WAAW,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACpG,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAC9E,YAAY,EAAE,SAAS,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACnG,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA4CxF,MAAM,WAAW,YAAY;CAG5B;AACD,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACxE,YAAY,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC1E,YAAY,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EACL,wBAAwB,EACxB,yBAAyB,GAC1B,MAAM,kCAAkC,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC5D,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAKnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAY3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAIvD,YAAY,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}