@field/cascade 0.10.0 → 0.12.0

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 (229) hide show
  1. package/AGENTS.md +25 -5
  2. package/ARCHITECTURE.md +5 -4
  3. package/CHANGELOG.md +382 -0
  4. package/README.md +1 -2
  5. package/dist/assets/AgentPanel-1JugFjE4.css +1 -0
  6. package/dist/assets/AgentPanel-BDL5MddA.js +5 -0
  7. package/dist/assets/{CodeEditor-DSZBpzdy.js → CodeEditor-C-xYG-Wn.js} +120 -120
  8. package/dist/assets/CodePanel-CtXd50m6.js +2 -0
  9. package/dist/assets/CookInfoPanel-B3NMhKJF.css +1 -0
  10. package/dist/assets/CookInfoPanel-DG4_TRsX.js +2 -0
  11. package/dist/assets/CustomNodeDialog-CHfZUs-r.js +1 -0
  12. package/dist/assets/DefinitionNode-CaRTXQ_b.js +1 -0
  13. package/dist/assets/DefinitionPanel-C3CxZY9r.js +1 -0
  14. package/dist/assets/DefinitionPanel-VNLnAiQD.css +1 -0
  15. package/dist/assets/OpenGraphDialog-BLdr6k_q.js +1 -0
  16. package/dist/assets/ProjectSettings-ZnvmIAir.js +2 -0
  17. package/dist/assets/{SettingsDialog-p25h3tj0.js → SettingsDialog-CBX3Wa87.js} +1 -1
  18. package/dist/assets/TimelinePanel-CDeR0arY.css +1 -0
  19. package/dist/assets/TimelinePanel-CWgnFH1e.js +1 -0
  20. package/dist/assets/{VersionHistoryDialog-DzFLUotB.js → VersionHistoryDialog-BOSoW1R-.js} +1 -1
  21. package/dist/assets/assert-BO2_erPD.js +1 -0
  22. package/dist/assets/assert-hvykGCnP.js +1 -0
  23. package/dist/assets/builder-BmBKcDix.js +1 -0
  24. package/dist/assets/camera-DFF7YA3F.js +1 -0
  25. package/dist/assets/checks-DAvyO9Gf.js +1 -0
  26. package/dist/assets/contactSheet-N1NcaWxw.js +2 -0
  27. package/dist/assets/{core-BICrEt0y.js → core-DB-MkGjj.js} +3 -4
  28. package/dist/assets/coreTypes-DtnDATZ7.js +1 -0
  29. package/dist/assets/{cssMode-D7TXw57I.js → cssMode-DPVckSOc.js} +1 -1
  30. package/dist/assets/definitions-B227e7f_.js +1 -0
  31. package/dist/assets/definitions-D0WOUJd7.js +1 -0
  32. package/dist/assets/{editor.api-DvkCO3ko.js → editor.api-Dkutg_Z1.js} +1 -1
  33. package/dist/assets/expressions-X-Z-GrgW.js +5 -0
  34. package/dist/assets/file-DAL6IfjV.js +1 -0
  35. package/dist/assets/filters-D3k1LRMa.js +1 -0
  36. package/dist/assets/{freemarker2-BA2lxsF1.js → freemarker2-CLiLKk3B.js} +1 -1
  37. package/dist/assets/generators-BhDxRAFC.js +1 -0
  38. package/dist/assets/geo-BsMImJIJ.js +1 -0
  39. package/dist/assets/geo-C4YngDob.js +2 -0
  40. package/dist/assets/{handlebars-BcDiyPd8.js → handlebars-CcD3aMeL.js} +1 -1
  41. package/dist/assets/{html-C7O4TyhX.js → html-DvZWGfqb.js} +1 -1
  42. package/dist/assets/{htmlMode-WaDGF3Mj.js → htmlMode-DUD3VaoN.js} +1 -1
  43. package/dist/assets/image-0SfH-snh.js +1 -0
  44. package/dist/assets/image-Ck-RdGU4.js +2 -0
  45. package/dist/assets/index-BWnrmIB9.js +137 -0
  46. package/dist/assets/index-DIaxEONI.css +1 -0
  47. package/dist/assets/io-BGmt2g0D.js +1 -0
  48. package/dist/assets/{javascript-CpIEK7tz.js → javascript-Cap2ahgh.js} +1 -1
  49. package/dist/assets/{jsonMode-B2FltW0f.js → jsonMode-B0uLjp9C.js} +1 -1
  50. package/dist/assets/{liquid-CN6FFHZE.js → liquid-CnxwhfLn.js} +1 -1
  51. package/dist/assets/{lspLanguageFeatures-QhV9XtLp.js → lspLanguageFeatures-Dn9hlKX6.js} +1 -1
  52. package/dist/assets/math-GeULS-sk.js +1 -0
  53. package/dist/assets/{mdx-DE89pafV.js → mdx-BV3jbrVa.js} +1 -1
  54. package/dist/assets/pop-KMF_qloi.js +1 -0
  55. package/dist/assets/preload-helper-Czpn1I53.js +1 -0
  56. package/dist/assets/{python-ByTllTB9.js → python-CGyFk4XK.js} +1 -1
  57. package/dist/assets/{razor-USYlRRCE.js → razor-CQfzX3-N.js} +1 -1
  58. package/dist/assets/remap-5SjXQU-q.js +1 -0
  59. package/dist/assets/renderMarkdown-ork0Oir7.js +67 -0
  60. package/dist/assets/studioImages-CAyH-0Pl.js +1 -0
  61. package/dist/assets/text-ZpX7qNkx.js +2 -0
  62. package/dist/assets/time-P_OiBhcf.js +1 -0
  63. package/dist/assets/{tsMode-BXpN8iUz.js → tsMode-BOeXzO9H.js} +1 -1
  64. package/dist/assets/{typescript-DInBjGQQ.js → typescript-J1SnE_Tu.js} +1 -1
  65. package/dist/assets/{workers-C7Ye5B7D.js → workers-CYwwTiVF.js} +1 -1
  66. package/dist/assets/{xml-U0Uocqyo.js → xml-DnPRNzK9.js} +1 -1
  67. package/dist/assets/{yaml-CBw_PVYf.js → yaml-BmmYBNa9.js} +1 -1
  68. package/dist/cli/DefinitionNode-P7AA4TAU.js +15 -0
  69. package/dist/cli/Graph-OB6EWSGL.js +2975 -0
  70. package/dist/cli/assert-GROGQNCQ.js +25 -0
  71. package/dist/cli/checks-3WB44XBL.js +189 -0
  72. package/dist/cli/chunk-2JEU3MGB.js +1125 -0
  73. package/dist/cli/chunk-2KXB47FP.js +115 -0
  74. package/dist/cli/chunk-3RXL5GHB.js +816 -0
  75. package/dist/cli/chunk-53LGFPWX.js +138 -0
  76. package/dist/cli/chunk-5ORUQ6ES.js +267 -0
  77. package/dist/cli/chunk-73S4JZXK.js +336 -0
  78. package/dist/cli/chunk-7QE2ZNSN.js +121 -0
  79. package/dist/cli/chunk-BP7UKF36.js +376 -0
  80. package/dist/cli/chunk-BPODRH52.js +201 -0
  81. package/dist/cli/chunk-BZ63XQ3I.js +1301 -0
  82. package/dist/cli/chunk-CKS2FQWR.js +89 -0
  83. package/dist/cli/chunk-CO22XHNG.js +593 -0
  84. package/dist/cli/chunk-CTPLRSWF.js +2197 -0
  85. package/dist/cli/chunk-DEQSEFFH.js +153 -0
  86. package/dist/cli/chunk-EU4ZFZ7Y.js +122 -0
  87. package/dist/cli/chunk-EZRWJ4G2.js +104 -0
  88. package/dist/cli/chunk-FWMSGKEH.js +71 -0
  89. package/dist/cli/chunk-G23A2BZZ.js +705 -0
  90. package/dist/cli/chunk-GRIN2DDP.js +1465 -0
  91. package/dist/cli/chunk-HFHWYL55.js +139 -0
  92. package/dist/cli/chunk-HSNOBIVT.js +263 -0
  93. package/dist/cli/chunk-ICH7N5PN.js +26 -0
  94. package/dist/cli/chunk-J7VK7MNF.js +418 -0
  95. package/dist/cli/chunk-JVOCK5W4.js +83 -0
  96. package/dist/cli/chunk-KBGUO27Q.js +15 -0
  97. package/dist/cli/chunk-KH4MUBKF.js +1551 -0
  98. package/dist/cli/chunk-L6B6FUQP.js +330 -0
  99. package/dist/cli/chunk-LTXWBXOM.js +438 -0
  100. package/dist/cli/chunk-MI5JLQYA.js +220 -0
  101. package/dist/cli/chunk-MP653A5O.js +3766 -0
  102. package/dist/cli/chunk-NJFMDXHR.js +320 -0
  103. package/dist/cli/chunk-OM6BEME6.js +267 -0
  104. package/dist/cli/chunk-OWWBYLHC.js +136 -0
  105. package/dist/cli/chunk-QQPA2ZHH.js +273 -0
  106. package/dist/cli/chunk-RUU3YWXU.js +99 -0
  107. package/dist/cli/chunk-RXDIMXN5.js +98 -0
  108. package/dist/cli/chunk-ST6LROUG.js +267 -0
  109. package/dist/cli/chunk-TJW67QSI.js +225 -0
  110. package/dist/cli/chunk-WD62JIP6.js +118 -0
  111. package/dist/cli/chunk-WGKPJZDY.js +364 -0
  112. package/dist/cli/chunk-X4WKAOQP.js +634 -0
  113. package/dist/cli/chunk-X566FWBV.js +156 -0
  114. package/dist/cli/chunk-YH637PDL.js +317 -0
  115. package/dist/cli/chunk-ZL4JTG5Y.js +37 -0
  116. package/dist/cli/chunk-ZSCUZE6I.js +112 -0
  117. package/dist/cli/cliCommands-JJN5Z3ZC.js +3470 -0
  118. package/dist/cli/commands-PHYFYA3F.js +1374 -0
  119. package/dist/cli/contactSheet-2PQVRUFW.js +11 -0
  120. package/dist/cli/definitions-BPOPW52L.js +32 -0
  121. package/dist/cli/doctorCommand-ORWQRNYP.js +106 -0
  122. package/dist/cli/edit-S5KEQ5RA.js +28 -0
  123. package/dist/cli/editorCommands-NFR32JOY.js +41 -0
  124. package/dist/cli/encode-ICPEPMH5.js +17 -0
  125. package/dist/cli/file-3HTQHPYQ.js +41 -0
  126. package/dist/cli/filters-6ODFWYK4.js +379 -0
  127. package/dist/cli/frames-LSAUBZ7P.js +16 -0
  128. package/dist/cli/generators-YU7GB4MD.js +210 -0
  129. package/dist/cli/geo-6ZPGKZRU.js +43 -0
  130. package/dist/cli/graphCommands-EOUOBOIJ.js +184 -0
  131. package/dist/cli/graphFile-YTGJB5F5.js +292 -0
  132. package/dist/cli/headlessCanvas-ENRHC7GS.js +16 -0
  133. package/dist/cli/helpCommand-PULH6ZYB.js +157 -0
  134. package/dist/cli/image-TXELZZK5.js +55 -0
  135. package/dist/cli/index.js +100 -30756
  136. package/dist/cli/math-RSYRQ3C6.js +16 -0
  137. package/dist/cli/mcp-JVZFQOEX.js +164 -0
  138. package/dist/cli/parity-X6DEH2DV.js +334 -0
  139. package/dist/cli/pop-B7SIIYKN.js +39 -0
  140. package/dist/cli/project-VYGIA7JX.js +12 -0
  141. package/dist/cli/projectDefinition-F3D7UIHJ.js +27 -0
  142. package/dist/cli/projectRuntime-2Q4JKEH3.js +55 -0
  143. package/dist/cli/registerStandardNodes-BXJCB6E4.js +744 -0
  144. package/dist/cli/render-PT4MWI6U.js +438 -0
  145. package/dist/cli/runCommand-HNPQVZMS.js +198 -0
  146. package/dist/cli/runner-5XPXVW7J.js +43 -0
  147. package/dist/cli/runtime/image.ts +302 -0
  148. package/dist/cli/runtime/io.ts +78 -0
  149. package/dist/cli/runtime/stage.ts +4 -1
  150. package/dist/cli/share-MHF6TNRK.js +166 -0
  151. package/dist/cli/stageCache-TY2BFZJR.js +15 -0
  152. package/dist/cli/stageRunner-KY2EJP7X.js +10 -0
  153. package/dist/cli/text-MB3EKR4H.js +126 -0
  154. package/dist/cli/verbs-PI2JH5FI.js +19 -0
  155. package/dist/cli/webBuild-WPPMGGW7.js +44 -0
  156. package/dist/contracts/capabilities.d.ts +18 -1
  157. package/dist/contracts/color.d.ts +8 -0
  158. package/dist/contracts/color.js +23 -0
  159. package/dist/contracts/definition.d.ts +10 -0
  160. package/dist/contracts/documentFormat.d.ts +18 -0
  161. package/dist/contracts/documentFormat.js +195 -0
  162. package/dist/contracts/editor.d.ts +17 -0
  163. package/dist/contracts/index.d.ts +1 -0
  164. package/dist/contracts/index.js +1 -0
  165. package/dist/contracts/schema.d.ts +1 -1
  166. package/dist/contracts/schema.js +1 -0
  167. package/dist/contracts/validate.js +11 -1
  168. package/dist/contracts/values.d.ts +24 -0
  169. package/dist/image.d.ts +156 -0
  170. package/dist/image.js +161 -0
  171. package/dist/index.html +8 -2
  172. package/dist/io.d.ts +29 -0
  173. package/dist/io.js +40 -0
  174. package/dist/player/app.js +3 -2
  175. package/dist/player/editor.js +35 -9
  176. package/dist/player/editorEngine.js +3 -2
  177. package/dist/runtime/builtins/assert/checks.d.ts +18 -0
  178. package/dist/runtime/builtins/assert/checks.js +207 -0
  179. package/dist/runtime/builtins/assert/definitions.d.ts +130 -0
  180. package/dist/runtime/builtins/assert/definitions.js +54 -0
  181. package/dist/runtime/builtins/assert/index.d.ts +152 -0
  182. package/dist/runtime/builtins/assert/index.js +36 -0
  183. package/dist/runtime/builtins/image/contactSheet.d.ts +64 -0
  184. package/dist/runtime/builtins/image/contactSheet.js +150 -0
  185. package/dist/runtime/builtins/image/definitions.d.ts +950 -0
  186. package/dist/runtime/builtins/image/definitions.js +343 -0
  187. package/dist/runtime/builtins/image/file.d.ts +8 -0
  188. package/dist/runtime/builtins/image/file.js +37 -0
  189. package/dist/runtime/builtins/image/filters.d.ts +8 -0
  190. package/dist/runtime/builtins/image/filters.js +394 -0
  191. package/dist/runtime/builtins/image/generators.d.ts +7 -0
  192. package/dist/runtime/builtins/image/generators.js +144 -0
  193. package/dist/runtime/builtins/image/index.d.ts +952 -0
  194. package/dist/runtime/builtins/image/index.js +44 -0
  195. package/dist/runtime/builtins/image/io.d.ts +100 -0
  196. package/dist/runtime/builtins/image/io.js +142 -0
  197. package/dist/runtime/builtins/image/planar.d.ts +55 -0
  198. package/dist/runtime/builtins/image/planar.js +274 -0
  199. package/dist/runtime/builtins/image/simplex.d.ts +22 -0
  200. package/dist/runtime/builtins/image/simplex.js +92 -0
  201. package/dist/runtime/builtins/image/text.d.ts +4 -0
  202. package/dist/runtime/builtins/image/text.js +141 -0
  203. package/dist/runtime/builtins/index.d.ts +3 -0
  204. package/dist/runtime/builtins/index.js +7 -0
  205. package/dist/runtime/gpu.d.ts +25 -0
  206. package/dist/runtime/gpu.js +40 -0
  207. package/dist/runtime/runtime.d.ts +2 -0
  208. package/dist/runtime/runtime.js +268 -44
  209. package/dist/runtime/types.d.ts +30 -0
  210. package/dist/stage.js +4 -1
  211. package/doc/CLI_REFERENCE.md +91 -2
  212. package/doc/EDITOR_EXPORT.md +37 -2
  213. package/doc/MCP.md +18 -1
  214. package/doc/NODE_REFERENCE.md +424 -10
  215. package/doc/PROJECT_AUTHORING.md +222 -7
  216. package/package.json +19 -4
  217. package/dist/assets/CodePanel-sY6sZa7B.js +0 -2
  218. package/dist/assets/CustomNodeDialog-BRKnqZhp.js +0 -1
  219. package/dist/assets/ExportDialog-C7dVGua4.js +0 -358
  220. package/dist/assets/ExportDialog-DOq0flMV.css +0 -1
  221. package/dist/assets/OpenGraphDialog-ZsgL-iwr.js +0 -1
  222. package/dist/assets/ProjectSettings-Cw17nNe3.js +0 -2
  223. package/dist/assets/definitions-DUww2Rt-.js +0 -1
  224. package/dist/assets/geo-C2RHOVHC.js +0 -1
  225. package/dist/assets/image-DR4oeyJB.js +0 -2062
  226. package/dist/assets/index-7vae6YFC.css +0 -1
  227. package/dist/assets/index-DyvDC6pS.js +0 -219
  228. package/dist/assets/math-DaDEgWKt.js +0 -1
  229. package/dist/assets/pop-pLH7L0PW.js +0 -1
@@ -0,0 +1,138 @@
1
+ // packages/runtime/src/builtins/image/contactSheet.ts
2
+ function contactSheetLayout(sizes, options = {}) {
3
+ const count = sizes.length;
4
+ if (!count) throw new Error("cascade.image.ContactSheet needs at least one image");
5
+ for (const [width, height] of sizes) {
6
+ if (!(width > 0 && height > 0)) throw new Error(`cascade.image.ContactSheet: an image is ${width}x${height}`);
7
+ }
8
+ const gap = Math.max(0, Math.round(options.gap ?? 16));
9
+ const columns = Math.min(count, options.columns && options.columns > 0 ? Math.round(options.columns) : Math.ceil(Math.sqrt(count)));
10
+ const rows = Math.ceil(count / columns);
11
+ const scaled = Boolean(options.cellWidth && options.cellWidth > 0);
12
+ const cellWidth = scaled ? Math.round(options.cellWidth) : Math.max(...sizes.map(([width]) => width));
13
+ const cellHeight = scaled ? Math.max(...sizes.map(([width, height]) => Math.round(height * cellWidth / width))) : Math.max(...sizes.map(([, height]) => height));
14
+ const labelHeight = Math.max(0, Math.round(options.labelHeight ?? 0));
15
+ const cells = sizes.map(([width, height], index) => {
16
+ const column = index % columns;
17
+ const row = Math.floor(index / columns);
18
+ const fit = Math.min(cellWidth / width, cellHeight / height);
19
+ const scale = scaled ? fit : Math.min(1, fit);
20
+ const drawnWidth = Math.max(1, Math.round(width * scale));
21
+ const drawnHeight = Math.max(1, Math.round(height * scale));
22
+ const left = gap + column * (cellWidth + gap);
23
+ const top = gap + row * (cellHeight + labelHeight + gap);
24
+ return {
25
+ x: left + Math.floor((cellWidth - drawnWidth) / 2),
26
+ y: top + Math.floor((cellHeight - drawnHeight) / 2),
27
+ width: drawnWidth,
28
+ height: drawnHeight,
29
+ labelX: left,
30
+ labelY: top + cellHeight
31
+ };
32
+ });
33
+ return {
34
+ width: gap + columns * (cellWidth + gap),
35
+ height: gap + rows * (cellHeight + labelHeight + gap),
36
+ columns,
37
+ rows,
38
+ cell: [cellWidth, cellHeight],
39
+ labelHeight,
40
+ cells
41
+ };
42
+ }
43
+ function host() {
44
+ const globals = globalThis;
45
+ const Canvas = globals.OffscreenCanvas;
46
+ const createBitmap = globals.createImageBitmap;
47
+ if (!Canvas || !createBitmap || !globals.Blob) {
48
+ throw new Error("cascade.image.ContactSheet needs OffscreenCanvas and createImageBitmap, which this host does not provide");
49
+ }
50
+ const BlobType = globals.Blob;
51
+ return {
52
+ surface: (width, height) => new Canvas(width, height),
53
+ decode: (bytes) => createBitmap(new BlobType([bytes]))
54
+ };
55
+ }
56
+ function css(colour, fallback) {
57
+ const [r = 0, g = 0, b = 0, a = 1] = colour ?? fallback;
58
+ return `rgba(${Math.round(r * 255)}, ${Math.round(g * 255)}, ${Math.round(b * 255)}, ${a})`;
59
+ }
60
+ async function composeContactSheet(images, options = {}) {
61
+ const canvas = host();
62
+ const bitmaps = [];
63
+ try {
64
+ for (const bytes of images) bitmaps.push(await canvas.decode(bytes));
65
+ const labels = options.labels ?? [];
66
+ const labelSize = Math.round(options.labelSize ?? 14);
67
+ const labelled = labels.some((label) => label.trim().length > 0);
68
+ const layout = contactSheetLayout(bitmaps.map((bitmap) => [bitmap.width, bitmap.height]), {
69
+ columns: options.columns,
70
+ gap: options.gap,
71
+ cellWidth: options.cellWidth,
72
+ labelHeight: labelled ? Math.round(labelSize * 1.8) : 0
73
+ });
74
+ const surface = canvas.surface(layout.width, layout.height);
75
+ const context = surface.getContext("2d");
76
+ if (!context) throw new Error("cascade.image.ContactSheet: no 2D context");
77
+ context.fillStyle = css(options.background, [1, 1, 1, 1]);
78
+ context.fillRect(0, 0, layout.width, layout.height);
79
+ context.imageSmoothingEnabled = true;
80
+ context.imageSmoothingQuality = "high";
81
+ layout.cells.forEach((cell, index) => context.drawImage(bitmaps[index], cell.x, cell.y, cell.width, cell.height));
82
+ if (labelled) {
83
+ context.fillStyle = css(options.labelColor, [0.1, 0.1, 0.1, 1]);
84
+ context.font = `${labelSize}px sans-serif`;
85
+ context.textBaseline = "top";
86
+ layout.cells.forEach((cell, index) => {
87
+ const label = labels[index];
88
+ if (label) context.fillText(label, cell.labelX, cell.labelY + Math.round((layout.labelHeight - labelSize) / 2), layout.cell[0]);
89
+ });
90
+ }
91
+ const blob = await surface.convertToBlob({ type: "image/png" });
92
+ return { bytes: new Uint8Array(await blob.arrayBuffer()), layout };
93
+ } finally {
94
+ for (const bitmap of bitmaps) bitmap.close?.();
95
+ }
96
+ }
97
+ function imageReference(value) {
98
+ if (typeof value === "string" && value) return { path: value };
99
+ const candidate = value?.path;
100
+ return typeof candidate === "string" && candidate ? { path: candidate } : void 0;
101
+ }
102
+ async function executeContactSheet(context) {
103
+ const { props } = context;
104
+ const references = [...context.inputs.images, ...context.inputs.list ?? []].map(imageReference).filter((reference) => reference !== void 0);
105
+ const bytes = [];
106
+ for (const reference of references) {
107
+ bytes.push(await context.capabilities.assets.read(reference, { signal: context.signal }));
108
+ }
109
+ const { bytes: sheet, layout } = await composeContactSheet(bytes, {
110
+ columns: props.columns,
111
+ gap: props.gap,
112
+ cellWidth: props.cellWidth,
113
+ labels: props.labels ? props.labels.split("\n") : [],
114
+ labelSize: props.labelSize,
115
+ background: props.background,
116
+ labelColor: props.labelColor
117
+ });
118
+ const asset = await context.capabilities.assets.write(
119
+ sheet,
120
+ { mediaType: "image/png", suggestedName: props.filename },
121
+ { signal: context.signal }
122
+ );
123
+ context.outputs.asset.set(asset);
124
+ context.outputs.image.set({
125
+ path: asset.path ?? props.filename,
126
+ size: [layout.width, layout.height],
127
+ channels: "rgba",
128
+ depth: "u8",
129
+ space: "srgb"
130
+ });
131
+ }
132
+
133
+ export {
134
+ contactSheetLayout,
135
+ composeContactSheet,
136
+ executeContactSheet
137
+ };
138
+ //# sourceMappingURL=chunk-53LGFPWX.js.map
@@ -0,0 +1,267 @@
1
+ // server/src/stageCache.ts
2
+ import { createHash as createHash2 } from "node:crypto";
3
+ import fs3 from "node:fs";
4
+ import path2 from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ // server/src/atomicWrite.ts
8
+ import fs from "node:fs";
9
+ import fsp from "node:fs/promises";
10
+ var temporaryFor = (file) => `${file}.${process.pid}.tmp`;
11
+ async function writeFileAtomic(file, data, options = {}) {
12
+ const temporary = temporaryFor(file);
13
+ await fsp.writeFile(temporary, data, options.mode === void 0 ? void 0 : { mode: options.mode });
14
+ await fsp.rename(temporary, file);
15
+ }
16
+ function writeFileAtomicSync(file, data) {
17
+ const temporary = temporaryFor(file);
18
+ fs.writeFileSync(temporary, data);
19
+ fs.renameSync(temporary, file);
20
+ }
21
+
22
+ // server/src/stageSources.ts
23
+ import { createHash } from "node:crypto";
24
+ import fs2 from "node:fs";
25
+ import path from "node:path";
26
+ var SKIPPED_DIRECTORIES = /* @__PURE__ */ new Set(["__pycache__", "node_modules", "venv", ".venv", ".git", ".cascade", ".cascade-cache"]);
27
+ var digests = /* @__PURE__ */ new Map();
28
+ function contentDigest(fullPath) {
29
+ let stat;
30
+ try {
31
+ stat = fs2.statSync(fullPath, { bigint: true });
32
+ } catch {
33
+ return null;
34
+ }
35
+ if (!stat.isFile()) return null;
36
+ const stamp = `${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
37
+ const known = digests.get(fullPath);
38
+ if (known?.stamp === stamp) return known.digest;
39
+ const hash = createHash("sha256");
40
+ const handle = fs2.openSync(fullPath, "r");
41
+ try {
42
+ const buffer = Buffer.allocUnsafe(1 << 20);
43
+ let read;
44
+ while ((read = fs2.readSync(handle, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, read));
45
+ } finally {
46
+ fs2.closeSync(handle);
47
+ }
48
+ const digest = hash.digest("hex");
49
+ digests.set(fullPath, { stamp, digest });
50
+ return digest;
51
+ }
52
+ var StageSources = class {
53
+ constructor(project, roots) {
54
+ this.project = project;
55
+ this.roots = [...new Set(roots)].sort();
56
+ }
57
+ /** The default roots for a stage configuration without `sources`. */
58
+ static defaultRoots(project, entrypoint, worker) {
59
+ const roots = [path.posix.dirname(entrypoint)];
60
+ if (worker) roots.push(path.posix.dirname(worker));
61
+ if (fs2.existsSync(project.resolve("src"))) roots.push("src");
62
+ return roots.map((root) => root === "" ? "." : root);
63
+ }
64
+ /** Every source file, project-relative and sorted. */
65
+ files() {
66
+ const found = /* @__PURE__ */ new Set();
67
+ for (const root of this.roots) {
68
+ let full;
69
+ try {
70
+ full = this.project.resolveMedia(root);
71
+ } catch {
72
+ continue;
73
+ }
74
+ let stat;
75
+ try {
76
+ stat = fs2.statSync(full);
77
+ } catch {
78
+ continue;
79
+ }
80
+ if (stat.isFile()) found.add(root);
81
+ else if (stat.isDirectory()) this.walk(full, root, found);
82
+ }
83
+ return [...found].sort();
84
+ }
85
+ walk(directory, relative, found) {
86
+ let entries;
87
+ try {
88
+ entries = fs2.readdirSync(directory, { withFileTypes: true });
89
+ } catch {
90
+ return;
91
+ }
92
+ for (const entry of entries) {
93
+ const child = relative === "." ? entry.name : path.posix.join(relative, entry.name);
94
+ if (entry.isDirectory()) {
95
+ if (SKIPPED_DIRECTORIES.has(entry.name) || entry.name.startsWith(".")) continue;
96
+ this.walk(path.join(directory, entry.name), child, found);
97
+ } else if (entry.isFile() && entry.name.endsWith(".py")) {
98
+ found.add(child);
99
+ }
100
+ }
101
+ }
102
+ digest(relative) {
103
+ return contentDigest(this.project.resolveMedia(relative)) ?? "missing";
104
+ }
105
+ /**
106
+ * The code hash, 16 hex characters. A file added, removed or edited changes
107
+ * it. Cheap to call per request: one directory walk and one stat per file,
108
+ * with a file read only when its stat changed.
109
+ */
110
+ codeHash() {
111
+ const hash = createHash("sha256");
112
+ for (const file of this.files()) hash.update(`${file}\0${this.digest(file)}
113
+ `);
114
+ return hash.digest("hex").slice(0, 16);
115
+ }
116
+ };
117
+
118
+ // server/src/stageCache.ts
119
+ var CACHE_FORMAT = 1;
120
+ var CACHE_DIRECTORY = ".cascade/cache/stages";
121
+ var version;
122
+ function cascadeVersion() {
123
+ if (version) return version;
124
+ let directory = path2.dirname(fileURLToPath(import.meta.url));
125
+ for (let depth = 0; depth < 6; depth++) {
126
+ try {
127
+ const manifest = JSON.parse(fs3.readFileSync(path2.join(directory, "package.json"), "utf8"));
128
+ if (manifest.name === "@field/cascade" && manifest.version) return version = manifest.version;
129
+ } catch {
130
+ }
131
+ directory = path2.dirname(directory);
132
+ }
133
+ return version = "unknown";
134
+ }
135
+ function projectFile(project, value) {
136
+ if (!value || value.length > 1024 || value.includes("\0") || /^[a-z][a-z0-9+.-]*:/i.test(value)) return null;
137
+ if (path2.isAbsolute(value) || !/[./]/.test(value)) return null;
138
+ try {
139
+ return project.resolveMedia(value.replace(/^\.\//, ""));
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+ function replaceFiles(project, value, at, skip, onFile) {
145
+ if (typeof value === "string") {
146
+ const full = projectFile(project, value);
147
+ const digest = full ? contentDigest(full) : null;
148
+ if (!digest) return value;
149
+ onFile(at, value, digest);
150
+ return `file:${digest}`;
151
+ }
152
+ if (Array.isArray(value)) return value.map((item, index) => replaceFiles(project, item, `${at}[${index}]`, skip, onFile));
153
+ if (value && typeof value === "object") {
154
+ const out = {};
155
+ for (const name of Object.keys(value).sort()) {
156
+ const child = value[name];
157
+ const where = at ? `${at}.${name}` : name;
158
+ out[name] = !at && skip.has(name) ? child : replaceFiles(project, child, where, skip, onFile);
159
+ }
160
+ return out;
161
+ }
162
+ return value;
163
+ }
164
+ function stageCacheKey(project, stage, args, code, outputs = []) {
165
+ const inputs = {};
166
+ const params = replaceFiles(project, args, "", new Set(outputs), (where, _relative, digest) => {
167
+ inputs[where] = digest.slice(0, 16);
168
+ });
169
+ const cascade = cascadeVersion();
170
+ const blob = JSON.stringify({ format: CACHE_FORMAT, stage, code, cascade, params });
171
+ return { key: createHash2("sha256").update(blob).digest("hex").slice(0, 32), code, cascade, inputs };
172
+ }
173
+ function entryPath(project, stage, key) {
174
+ return project.resolve(path2.posix.join(CACHE_DIRECTORY, stage, `${key}.json`));
175
+ }
176
+ function resultFiles(project, result) {
177
+ const files = {};
178
+ replaceFiles(project, result, "", /* @__PURE__ */ new Set(), (_where, relative, digest) => {
179
+ files[relative] = digest;
180
+ });
181
+ return files;
182
+ }
183
+ function readEntry(project, stage, key) {
184
+ let entry;
185
+ try {
186
+ entry = JSON.parse(fs3.readFileSync(entryPath(project, stage, key), "utf8"));
187
+ } catch {
188
+ return { why: "no entry" };
189
+ }
190
+ if (entry.format !== CACHE_FORMAT || entry.key !== key) return { why: "no entry" };
191
+ for (const [relative, digest] of Object.entries(entry.outputs ?? {})) {
192
+ const full = projectFile(project, relative);
193
+ if (!full || contentDigest(full) !== digest) return { why: `output changed: ${relative}` };
194
+ }
195
+ return { result: entry.result };
196
+ }
197
+ function writeEntry(project, stage, key, result) {
198
+ const file = entryPath(project, stage, key);
199
+ const entry = { format: CACHE_FORMAT, stage, key, result, outputs: resultFiles(project, result) };
200
+ fs3.mkdirSync(path2.dirname(file), { recursive: true });
201
+ writeFileAtomicSync(file, JSON.stringify(entry));
202
+ }
203
+ function cacheMode(policy, stage, caller, env) {
204
+ if (env.CASCADE_STAGE_CACHE === "off") return { use: false, bypass: true, why: "CASCADE_STAGE_CACHE=off" };
205
+ if (!policy.enabled) return { use: false, bypass: false, why: "not enabled" };
206
+ if (policy.exclude.includes(stage)) return { use: false, bypass: false, why: "excluded" };
207
+ if (policy.scope === "cook" && caller !== "cook") return { use: false, bypass: false, why: "not a cook call" };
208
+ return { use: true, bypass: false };
209
+ }
210
+ function stageSources(project, config) {
211
+ return new StageSources(project, config.sources ?? StageSources.defaultRoots(project, config.entrypoint, config.worker));
212
+ }
213
+ async function callStage(project, config, stage, args, invoke, options = {}) {
214
+ const started = performance.now();
215
+ const code = stageSources(project, config).codeHash();
216
+ const identity = stageCacheKey(project, stage, args, code, config.cache.outputs);
217
+ const mode = cacheMode(config.cache, stage, options.caller ?? "cook", options.env ?? process.env);
218
+ const finish = (result, trace) => {
219
+ const full = {
220
+ stage,
221
+ key: identity.key,
222
+ ...trace,
223
+ code: identity.code,
224
+ cascade: identity.cascade,
225
+ inputs: identity.inputs,
226
+ ms: Math.round(performance.now() - started)
227
+ };
228
+ options.onTrace?.(full);
229
+ return { result, trace: full };
230
+ };
231
+ let why = mode.why;
232
+ if (mode.use) {
233
+ const stored = readEntry(project, stage, identity.key);
234
+ if ("result" in stored) return finish(stored.result, { cache: "hit" });
235
+ why = stored.why;
236
+ }
237
+ const answer = await invoke({ key: identity.key, code, cache: mode.bypass ? "off" : "use" });
238
+ if (mode.use) {
239
+ try {
240
+ writeEntry(project, stage, identity.key, answer.result);
241
+ } catch {
242
+ }
243
+ }
244
+ return finish(answer.result, {
245
+ cache: mode.use ? "miss" : "off",
246
+ ...why ? { why } : {},
247
+ ...answer.projectCache ? { project: answer.projectCache } : {},
248
+ ...answer.restarted ? { restarted: true } : {}
249
+ });
250
+ }
251
+ function formatStageTrace(trace) {
252
+ const reason = trace.why ? ` (${trace.why})` : "";
253
+ const project = trace.project ? ` project:${trace.project}` : "";
254
+ const restarted = trace.restarted ? " worker restarted for new code" : "";
255
+ return `${trace.stage}: ${trace.cache}${reason}${project} key ${trace.key.slice(0, 12)} code ${trace.code} ${trace.ms} ms${restarted}`;
256
+ }
257
+
258
+ export {
259
+ writeFileAtomic,
260
+ writeFileAtomicSync,
261
+ cascadeVersion,
262
+ stageCacheKey,
263
+ stageSources,
264
+ callStage,
265
+ formatStageTrace
266
+ };
267
+ //# sourceMappingURL=chunk-5ORUQ6ES.js.map