@field/cascade 0.2.22 → 0.3.1

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 (233) hide show
  1. package/AGENTS.md +47 -7
  2. package/ARCHITECTURE.md +30 -2
  3. package/CHANGELOG.md +107 -0
  4. package/DESIGN.md +10 -5
  5. package/README.md +103 -7
  6. package/dist/assets/CodeEditor-D22fVi-4.js +623 -0
  7. package/dist/assets/CodePanel-DPNEYfcR.js +2 -0
  8. package/dist/assets/CustomNodeDialog-DeHCAvgb.js +1 -0
  9. package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
  10. package/dist/assets/ProjectSettings-DWpVtWEx.js +2 -0
  11. package/dist/assets/SettingsDialog-za00IS3L.js +1 -0
  12. package/dist/assets/VersionHistoryDialog-DTMGhI0_.js +3 -0
  13. package/dist/assets/{core-Bji4aZUU.js → core-JsyKMO1k.js} +29 -38
  14. package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-emdRheFK.js} +1 -1
  15. package/dist/assets/{editor.api-jMoW_43X.js → editor.api-xHX-fMSo.js} +1 -1
  16. package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-C3M8_Gs3.js} +1 -1
  17. package/dist/assets/geo-BAR6lQp5.js +1 -0
  18. package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-CBMurtFk.js} +1 -1
  19. package/dist/assets/{html-DyHm6JCp.js → html-xbLQwknw.js} +1 -1
  20. package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-2wKdDTon.js} +1 -1
  21. package/dist/assets/{image-BUcQC7PG.js → image-i1j6nWKe.js} +26 -40
  22. package/dist/assets/index-BDV5xGBZ.css +1 -0
  23. package/dist/assets/index-CMcH8AgP.js +212 -0
  24. package/dist/assets/index-client-BoJgsN9M.js +1 -0
  25. package/dist/assets/{javascript-C_bM5PCN.js → javascript-DJaOo_2I.js} +1 -1
  26. package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-Bheas8_0.js} +1 -1
  27. package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
  28. package/dist/assets/{liquid-CDkn3spA.js → liquid-BCj_BGbM.js} +1 -1
  29. package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BfIsx_xz.js} +1 -1
  30. package/dist/assets/{mdx-BvH_NjDN.js → mdx-DwiQDiVO.js} +1 -1
  31. package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
  32. package/dist/assets/{python-B7uii4fi.js → python-BjrB5nqj.js} +1 -1
  33. package/dist/assets/{razor-Z48fWdJc.js → razor-CfoBmGDs.js} +1 -1
  34. package/dist/assets/{tsMode-BDgacVG4.js → tsMode-BcEoMSxp.js} +1 -1
  35. package/dist/assets/{typescript-Derb6Oqa.js → typescript-DIg7TEmQ.js} +1 -1
  36. package/dist/assets/{workers-BtoMX_pm.js → workers-vXYuwtPw.js} +1 -1
  37. package/dist/assets/{xml-B1CaFSrG.js → xml-DexLQIQy.js} +1 -1
  38. package/dist/assets/{yaml-HmIeA0KH.js → yaml-DUnD_1dE.js} +1 -1
  39. package/dist/cli/index.js +16467 -10200
  40. package/dist/cli/runtime/gpu.ts +76 -0
  41. package/dist/cli/runtime/io.ts +93 -7
  42. package/dist/contracts/animation.d.ts +40 -0
  43. package/dist/contracts/animation.js +14 -0
  44. package/dist/contracts/capabilities.d.ts +69 -6
  45. package/dist/contracts/definition.d.ts +60 -2
  46. package/dist/contracts/document.d.ts +15 -0
  47. package/dist/contracts/index.d.ts +1 -0
  48. package/dist/contracts/index.js +1 -0
  49. package/dist/contracts/schema.js +8 -5
  50. package/dist/contracts/validate.d.ts +10 -1
  51. package/dist/contracts/validate.js +20 -3
  52. package/dist/gpu.d.ts +71 -0
  53. package/dist/gpu.js +53 -0
  54. package/dist/graphs/default.cascade +1 -1
  55. package/dist/index.html +5 -5
  56. package/dist/io.d.ts +196 -0
  57. package/dist/io.js +303 -0
  58. package/dist/net.d.ts +1 -0
  59. package/dist/net.js +28 -0
  60. package/dist/runtime/animation/channel.d.ts +108 -0
  61. package/dist/runtime/animation/channel.js +204 -0
  62. package/dist/runtime/animation/frameRange.d.ts +78 -0
  63. package/dist/runtime/animation/frameRange.js +76 -0
  64. package/dist/runtime/animation/index.d.ts +10 -0
  65. package/dist/runtime/animation/index.js +8 -0
  66. package/dist/runtime/builtins/core/random.d.ts +1 -1
  67. package/dist/runtime/builtins/core/remap.d.ts +1 -1
  68. package/dist/runtime/builtins/geo/copyToPoints.d.ts +1 -1
  69. package/dist/runtime/builtins/geo/copyToPoints.js +1 -1
  70. package/dist/runtime/builtins/geo/generators.d.ts +1 -1
  71. package/dist/runtime/builtins/geo/merge.d.ts +1 -1
  72. package/dist/runtime/builtins/geo/svgExport.d.ts +1 -1
  73. package/dist/runtime/builtins/geo/svgExport.js +1 -1
  74. package/dist/runtime/builtins/geo/transform.d.ts +1 -1
  75. package/dist/runtime/builtins/geo/transform.js +1 -1
  76. package/dist/runtime/definition/architecture.d.ts +1 -1
  77. package/dist/runtime/definition/extract.d.ts +1 -1
  78. package/dist/runtime/definition/extract.js +1 -1
  79. package/dist/runtime/error.d.ts +1 -1
  80. package/dist/runtime/expressions/evaluator.d.ts +53 -0
  81. package/dist/runtime/expressions/evaluator.js +193 -0
  82. package/dist/runtime/expressions/index.d.ts +15 -0
  83. package/dist/runtime/expressions/index.js +12 -0
  84. package/dist/runtime/expressions/math.d.ts +75 -0
  85. package/dist/runtime/expressions/math.js +140 -0
  86. package/dist/runtime/expressions/preprocess.d.ts +33 -0
  87. package/dist/runtime/expressions/preprocess.js +102 -0
  88. package/dist/runtime/expressions/scope.d.ts +27 -0
  89. package/dist/runtime/expressions/scope.js +10 -0
  90. package/dist/runtime/expressions/time.d.ts +54 -0
  91. package/dist/runtime/expressions/time.js +71 -0
  92. package/dist/runtime/expressions/types.d.ts +66 -0
  93. package/dist/runtime/expressions/types.js +9 -0
  94. package/dist/runtime/geometry/attributes.d.ts +2 -2
  95. package/dist/runtime/geometry/attributes.js +1 -1
  96. package/dist/runtime/geometry/builder.d.ts +1 -1
  97. package/dist/runtime/geometry/builder.js +1 -1
  98. package/dist/runtime/geometry/copy.d.ts +1 -1
  99. package/dist/runtime/geometry/copy.js +1 -1
  100. package/dist/runtime/geometry/generate.d.ts +1 -1
  101. package/dist/runtime/geometry/groups.d.ts +1 -1
  102. package/dist/runtime/geometry/groups.js +1 -1
  103. package/dist/runtime/geometry/matrix.d.ts +1 -1
  104. package/dist/runtime/geometry/merge.d.ts +1 -1
  105. package/dist/runtime/geometry/merge.js +1 -1
  106. package/dist/runtime/geometry/primitives.d.ts +1 -1
  107. package/dist/runtime/geometry/primitives.js +1 -1
  108. package/dist/runtime/geometry/promote.d.ts +1 -1
  109. package/dist/runtime/geometry/promote.js +1 -1
  110. package/dist/runtime/geometry/resample.d.ts +1 -1
  111. package/dist/runtime/geometry/resample.js +1 -1
  112. package/dist/runtime/geometry/svg.d.ts +1 -1
  113. package/dist/runtime/geometry/svg.js +1 -1
  114. package/dist/runtime/geometry/transform.d.ts +1 -1
  115. package/dist/runtime/geometry/transform.js +1 -1
  116. package/dist/runtime/index.d.ts +4 -1
  117. package/dist/runtime/index.js +4 -1
  118. package/dist/runtime/params/animator.d.ts +74 -0
  119. package/dist/runtime/params/animator.js +137 -0
  120. package/dist/runtime/params/index.d.ts +11 -0
  121. package/dist/runtime/params/index.js +9 -0
  122. package/dist/runtime/params/resolve.d.ts +73 -0
  123. package/dist/runtime/params/resolve.js +62 -0
  124. package/dist/runtime/runtime.d.ts +42 -0
  125. package/dist/runtime/runtime.js +351 -29
  126. package/dist/runtime/types.d.ts +32 -6
  127. package/dist/shell.d.ts +3 -3
  128. package/doc/NODE_REFERENCE.md +395 -0
  129. package/doc/PROJECT_AUTHORING.md +148 -6
  130. package/package.json +30 -3
  131. package/dist/assets/CodeEditor-DoaNEUaM.js +0 -641
  132. package/dist/assets/CodePanel-B0_2XJ-t.js +0 -2
  133. package/dist/assets/CustomNodeDialog-026lzecD.js +0 -1
  134. package/dist/assets/ProjectSettings-BzT164Yx.js +0 -2
  135. package/dist/assets/SettingsDialog-DmHb1V0r.js +0 -1
  136. package/dist/assets/VersionHistoryDialog-hH6VsXLX.js +0 -3
  137. package/dist/assets/geo-CXmCtzMM.js +0 -2
  138. package/dist/assets/index-BXYXSdgh.js +0 -224
  139. package/dist/assets/index-CrNke7SW.css +0 -1
  140. package/dist/assets/index-client-DN_FvjN8.js +0 -1
  141. package/dist/assets/quill-DjZ69wL_.js +0 -121
  142. package/dist/cli/index.js.map +0 -7
  143. package/dist/contracts/capabilities.d.ts.map +0 -1
  144. package/dist/contracts/capabilities.js.map +0 -1
  145. package/dist/contracts/color.d.ts.map +0 -1
  146. package/dist/contracts/color.js.map +0 -1
  147. package/dist/contracts/definition.d.ts.map +0 -1
  148. package/dist/contracts/definition.js.map +0 -1
  149. package/dist/contracts/document.d.ts.map +0 -1
  150. package/dist/contracts/document.js.map +0 -1
  151. package/dist/contracts/geometry.d.ts.map +0 -1
  152. package/dist/contracts/geometry.js.map +0 -1
  153. package/dist/contracts/index.d.ts.map +0 -1
  154. package/dist/contracts/index.js.map +0 -1
  155. package/dist/contracts/schema.d.ts.map +0 -1
  156. package/dist/contracts/schema.js.map +0 -1
  157. package/dist/contracts/validate.d.ts.map +0 -1
  158. package/dist/contracts/validate.js.map +0 -1
  159. package/dist/contracts/values.d.ts.map +0 -1
  160. package/dist/contracts/values.js.map +0 -1
  161. package/dist/graphs/default-orig.cascade +0 -128
  162. package/dist/runtime/browser.d.ts.map +0 -1
  163. package/dist/runtime/browser.js.map +0 -1
  164. package/dist/runtime/builtins/core/index.d.ts.map +0 -1
  165. package/dist/runtime/builtins/core/index.js.map +0 -1
  166. package/dist/runtime/builtins/core/random.d.ts.map +0 -1
  167. package/dist/runtime/builtins/core/random.js.map +0 -1
  168. package/dist/runtime/builtins/core/remap.d.ts.map +0 -1
  169. package/dist/runtime/builtins/core/remap.js.map +0 -1
  170. package/dist/runtime/builtins/core/routing.d.ts.map +0 -1
  171. package/dist/runtime/builtins/core/routing.js.map +0 -1
  172. package/dist/runtime/builtins/core/structural.d.ts.map +0 -1
  173. package/dist/runtime/builtins/core/structural.js.map +0 -1
  174. package/dist/runtime/builtins/geo/copyToPoints.d.ts.map +0 -1
  175. package/dist/runtime/builtins/geo/copyToPoints.js.map +0 -1
  176. package/dist/runtime/builtins/geo/definitions.d.ts.map +0 -1
  177. package/dist/runtime/builtins/geo/definitions.js.map +0 -1
  178. package/dist/runtime/builtins/geo/generators.d.ts.map +0 -1
  179. package/dist/runtime/builtins/geo/generators.js.map +0 -1
  180. package/dist/runtime/builtins/geo/index.d.ts.map +0 -1
  181. package/dist/runtime/builtins/geo/index.js.map +0 -1
  182. package/dist/runtime/builtins/geo/merge.d.ts.map +0 -1
  183. package/dist/runtime/builtins/geo/merge.js.map +0 -1
  184. package/dist/runtime/builtins/geo/namespace.d.ts.map +0 -1
  185. package/dist/runtime/builtins/geo/namespace.js.map +0 -1
  186. package/dist/runtime/builtins/geo/svgExport.d.ts.map +0 -1
  187. package/dist/runtime/builtins/geo/svgExport.js.map +0 -1
  188. package/dist/runtime/builtins/geo/transform.d.ts.map +0 -1
  189. package/dist/runtime/builtins/geo/transform.js.map +0 -1
  190. package/dist/runtime/builtins/index.d.ts.map +0 -1
  191. package/dist/runtime/builtins/index.js.map +0 -1
  192. package/dist/runtime/definition/architecture.d.ts.map +0 -1
  193. package/dist/runtime/definition/architecture.js.map +0 -1
  194. package/dist/runtime/definition/extract.d.ts.map +0 -1
  195. package/dist/runtime/definition/extract.js.map +0 -1
  196. package/dist/runtime/error.d.ts.map +0 -1
  197. package/dist/runtime/error.js.map +0 -1
  198. package/dist/runtime/geometry/attributes.d.ts.map +0 -1
  199. package/dist/runtime/geometry/attributes.js.map +0 -1
  200. package/dist/runtime/geometry/builder.d.ts.map +0 -1
  201. package/dist/runtime/geometry/builder.js.map +0 -1
  202. package/dist/runtime/geometry/copy.d.ts.map +0 -1
  203. package/dist/runtime/geometry/copy.js.map +0 -1
  204. package/dist/runtime/geometry/generate.d.ts.map +0 -1
  205. package/dist/runtime/geometry/generate.js.map +0 -1
  206. package/dist/runtime/geometry/groups.d.ts.map +0 -1
  207. package/dist/runtime/geometry/groups.js.map +0 -1
  208. package/dist/runtime/geometry/index.d.ts.map +0 -1
  209. package/dist/runtime/geometry/index.js.map +0 -1
  210. package/dist/runtime/geometry/matrix.d.ts.map +0 -1
  211. package/dist/runtime/geometry/matrix.js.map +0 -1
  212. package/dist/runtime/geometry/merge.d.ts.map +0 -1
  213. package/dist/runtime/geometry/merge.js.map +0 -1
  214. package/dist/runtime/geometry/primitives.d.ts.map +0 -1
  215. package/dist/runtime/geometry/primitives.js.map +0 -1
  216. package/dist/runtime/geometry/promote.d.ts.map +0 -1
  217. package/dist/runtime/geometry/promote.js.map +0 -1
  218. package/dist/runtime/geometry/resample.d.ts.map +0 -1
  219. package/dist/runtime/geometry/resample.js.map +0 -1
  220. package/dist/runtime/geometry/svg.d.ts.map +0 -1
  221. package/dist/runtime/geometry/svg.js.map +0 -1
  222. package/dist/runtime/geometry/transform.d.ts.map +0 -1
  223. package/dist/runtime/geometry/transform.js.map +0 -1
  224. package/dist/runtime/index.d.ts.map +0 -1
  225. package/dist/runtime/index.js.map +0 -1
  226. package/dist/runtime/node.d.ts.map +0 -1
  227. package/dist/runtime/node.js.map +0 -1
  228. package/dist/runtime/runtime.d.ts.map +0 -1
  229. package/dist/runtime/runtime.js.map +0 -1
  230. package/dist/runtime/types.d.ts.map +0 -1
  231. package/dist/runtime/types.js.map +0 -1
  232. package/dist/shell.js.map +0 -7
  233. package/dist/stage.js.map +0 -7
package/dist/gpu.d.ts ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * `cascade/gpu` — WebGPU's flag namespaces as plain values.
3
+ *
4
+ * Small, and it was load-bearing. TypeScript's DOM library declares the WebGPU
5
+ * *interfaces* but not the constant objects, so the only way to reach
6
+ * `GPUTextureUsage.RENDER_ATTACHMENT` was off `globalThis` — which
7
+ * `architecture/ambient-state` correctly forbids inside a node module, because
8
+ * an ambient read makes a node's inputs invisible to the static reader that
9
+ * lets `cascade check` say anything without cooking.
10
+ *
11
+ * So a sketch had to choose between a rule and a working shader, and it chose
12
+ * a project library file to hide the read in. Both stay honest now: the rule
13
+ * keeps its teeth and a node imports these instead. That is the whole reason
14
+ * this file exists, and it is why it had to land with the capability rather
15
+ * than after it — a rule whose only workaround is removed at the same moment
16
+ * is a rule; one enforced a release earlier is a nuisance.
17
+ *
18
+ * The values are fixed by the WebGPU specification, not by an implementation,
19
+ * which is what makes writing them down safe rather than a guess. They are
20
+ * checked against a real browser's own globals in `tests/gpu-usage-flags.test.ts`
21
+ * — the numbers below were verified against Chrome rather than recalled.
22
+ */
23
+ /** `GPUBufferUsage`. */
24
+ export declare const BufferUsage: {
25
+ readonly MAP_READ: 1;
26
+ readonly MAP_WRITE: 2;
27
+ readonly COPY_SRC: 4;
28
+ readonly COPY_DST: 8;
29
+ readonly INDEX: 16;
30
+ readonly VERTEX: 32;
31
+ readonly UNIFORM: 64;
32
+ readonly STORAGE: 128;
33
+ readonly INDIRECT: 256;
34
+ readonly QUERY_RESOLVE: 512;
35
+ };
36
+ /** `GPUTextureUsage`. */
37
+ export declare const TextureUsage: {
38
+ readonly COPY_SRC: 1;
39
+ readonly COPY_DST: 2;
40
+ readonly TEXTURE_BINDING: 4;
41
+ readonly STORAGE_BINDING: 8;
42
+ readonly RENDER_ATTACHMENT: 16;
43
+ /**
44
+ * Chrome 147 has this and it is newer than the rest — verified present
45
+ * 2026-09-08 alongside the others, but do not assume every implementation
46
+ * carries it. Included rather than omitted deliberately: leaving it out
47
+ * would send a node that wants it back to `globalThis`, which is the exact
48
+ * ambient read this file exists to remove, so an honest constant with a
49
+ * caveat beats an omission that forces a rule violation.
50
+ */
51
+ readonly TRANSIENT_ATTACHMENT: 32;
52
+ };
53
+ /** `GPUShaderStage`, for a bind group layout entry's `visibility`. */
54
+ export declare const ShaderStage: {
55
+ readonly VERTEX: 1;
56
+ readonly FRAGMENT: 2;
57
+ readonly COMPUTE: 4;
58
+ };
59
+ /** `GPUMapMode`, for `buffer.mapAsync`. */
60
+ export declare const MapMode: {
61
+ readonly READ: 1;
62
+ readonly WRITE: 2;
63
+ };
64
+ /** `GPUColorWrite`, for a colour target's `writeMask`. */
65
+ export declare const ColorWrite: {
66
+ readonly RED: 1;
67
+ readonly GREEN: 2;
68
+ readonly BLUE: 4;
69
+ readonly ALPHA: 8;
70
+ readonly ALL: 15;
71
+ };
package/dist/gpu.js ADDED
@@ -0,0 +1,53 @@
1
+ // server/src/runtime/gpu.ts
2
+ var BufferUsage = {
3
+ MAP_READ: 1,
4
+ MAP_WRITE: 2,
5
+ COPY_SRC: 4,
6
+ COPY_DST: 8,
7
+ INDEX: 16,
8
+ VERTEX: 32,
9
+ UNIFORM: 64,
10
+ STORAGE: 128,
11
+ INDIRECT: 256,
12
+ QUERY_RESOLVE: 512
13
+ };
14
+ var TextureUsage = {
15
+ COPY_SRC: 1,
16
+ COPY_DST: 2,
17
+ TEXTURE_BINDING: 4,
18
+ STORAGE_BINDING: 8,
19
+ RENDER_ATTACHMENT: 16,
20
+ /**
21
+ * Chrome 147 has this and it is newer than the rest — verified present
22
+ * 2026-09-08 alongside the others, but do not assume every implementation
23
+ * carries it. Included rather than omitted deliberately: leaving it out
24
+ * would send a node that wants it back to `globalThis`, which is the exact
25
+ * ambient read this file exists to remove, so an honest constant with a
26
+ * caveat beats an omission that forces a rule violation.
27
+ */
28
+ TRANSIENT_ATTACHMENT: 32
29
+ };
30
+ var ShaderStage = {
31
+ VERTEX: 1,
32
+ FRAGMENT: 2,
33
+ COMPUTE: 4
34
+ };
35
+ var MapMode = {
36
+ READ: 1,
37
+ WRITE: 2
38
+ };
39
+ var ColorWrite = {
40
+ RED: 1,
41
+ GREEN: 2,
42
+ BLUE: 4,
43
+ ALPHA: 8,
44
+ ALL: 15
45
+ };
46
+ export {
47
+ BufferUsage,
48
+ ColorWrite,
49
+ MapMode,
50
+ ShaderStage,
51
+ TextureUsage
52
+ };
53
+ //# sourceMappingURL=gpu.js.map
@@ -9,7 +9,7 @@
9
9
  "nodes": [
10
10
  {
11
11
  "id": "Color1",
12
- "module": "cascade.lens.Color",
12
+ "module": "cascade.image.Color",
13
13
  "position": [0, 0],
14
14
  "source": "stdlib",
15
15
  "props": {
package/dist/index.html CHANGED
@@ -4,11 +4,11 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Cascade</title>
7
- <script type="module" crossorigin src="/assets/index-BXYXSdgh.js"></script>
8
- <link rel="modulepreload" crossorigin href="/assets/legacy-DIu3XRwl.js">
9
- <link rel="modulepreload" crossorigin href="/assets/index-client-DN_FvjN8.js">
10
- <link rel="modulepreload" crossorigin href="/assets/nodeTypeUtils-CweA3NHd.js">
11
- <link rel="stylesheet" crossorigin href="/assets/index-CrNke7SW.css">
7
+ <script type="module" crossorigin src="/assets/index-CMcH8AgP.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/assets/legacy-CAj2_7QE.js">
9
+ <link rel="modulepreload" crossorigin href="/assets/index-client-BoJgsN9M.js">
10
+ <link rel="modulepreload" crossorigin href="/assets/nodeTypeUtils-BBA1JqHx.js">
11
+ <link rel="stylesheet" crossorigin href="/assets/index-BDV5xGBZ.css">
12
12
  </head>
13
13
  <body>
14
14
  <div id="app"></div>
package/dist/io.d.ts ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * `cascade/io` — the runtime a BROWSER-side node imports.
3
+ *
4
+ * The interop rests on one rule: an image port carries a project-relative PATH,
5
+ * never pixels. A Python node writes a file and emits its path; a WebGL node
6
+ * reads that path as a texture, renders, writes its result back, and emits a
7
+ * path of the same kind. Neither knows or cares which side produced its input,
8
+ * which is what makes the two interchangeable — and it lets the heavy stages
9
+ * stay in Python while the interactive ones run in the page.
10
+ *
11
+ * The counterpart on the server side is the project's own exec bridge
12
+ * (`runStage`), which is per-project because the stages are.
13
+ *
14
+ * This file is compiled into any node module that imports 'cascade/io'; it is
15
+ * not published to npm and has no dependencies.
16
+ */
17
+ export interface MediaOptions {
18
+ /** Ask for a resized copy. Never upscales past the file's own resolution. */
19
+ width?: number;
20
+ /** 'webp' | 'png' | 'jpeg' | 'avif'. Defaults to WebP, which is what you want
21
+ * for anything being looked at rather than processed. */
22
+ format?: string;
23
+ /** The original bytes, unresized and unconverted. Use this when the pixels
24
+ * matter — reading a render back for further processing, not previewing it. */
25
+ raw?: boolean;
26
+ quality?: number;
27
+ }
28
+ /** Build the URL that serves a project file to the page. */
29
+ export declare function mediaUrl(path: string, options?: MediaOptions): string;
30
+ /**
31
+ * The transport a host installs so `cascade/io` can reach project files.
32
+ *
33
+ * Same shape of decision as `cascade/stage`: the page posts to the server,
34
+ * because that is the only door a browser has, and a headless host installs a
35
+ * local transport before it runs the graph. Without one, every read here is a
36
+ * `fetch('/api/media/...')` against a relative URL with no origin to resolve it
37
+ * — which is exactly how a Canvas 2D node came to render in Studio and fail
38
+ * under `cascade run`.
39
+ *
40
+ * Paths stay project-relative on both sides. The bridge, not the node, decides
41
+ * what that resolves to.
42
+ */
43
+ export interface IoBridge {
44
+ /** Read a project file's bytes. */
45
+ read(path: string, options?: MediaOptions): Promise<Uint8Array>;
46
+ /** Write bytes into the project cache. Returns the stored project-relative path. */
47
+ write(path: string, data: Uint8Array): Promise<string>;
48
+ }
49
+ /** Install the local transport. Returns a disposer, so a test can take it down. */
50
+ export declare function installIoBridge(bridge: IoBridge | null): () => void;
51
+ /**
52
+ * A project file's bytes, over whichever transport this host has. Every read in
53
+ * this module goes through here so the two hosts differ in one place only.
54
+ */
55
+ export declare function loadBytes(path: string, options?: MediaOptions): Promise<ArrayBuffer>;
56
+ /**
57
+ * Load a project image as an <img>. Defaults to `raw` — a node is processing
58
+ * these pixels, not showing them, so a re-encoded copy would be the wrong thing
59
+ * to hand it. Pass a width explicitly to work at a smaller size on purpose.
60
+ */
61
+ export declare function loadImage(path: string, options?: MediaOptions): Promise<HTMLImageElement>;
62
+ /** Same, as an ImageBitmap — the right input for `texImage2D`. */
63
+ export declare function loadBitmap(path: string, options?: MediaOptions): Promise<ImageBitmap>;
64
+ /**
65
+ * A .npy field (a mask, a signal, a height or density field) rendered to
66
+ * greyscale, ready to upload as a texture. The numeric array itself stays on
67
+ * the server side for now; this is the visual form of it.
68
+ */
69
+ export declare function loadFieldBitmap(path: string, options?: MediaOptions): Promise<ImageBitmap>;
70
+ export interface SaveOptions {
71
+ /** 'png' (default), 'webp' or 'jpeg'. PNG is the default because the usual
72
+ * consumer of a node's output is another node, and a lossy intermediate
73
+ * compounds down a pipeline. Choose webp when the output is a preview. */
74
+ format?: 'png' | 'webp' | 'jpeg';
75
+ quality?: number;
76
+ }
77
+ /**
78
+ * Anything that can produce encoded pixels. The two browser canvases are the
79
+ * usual case; a headless host's canvas offers Skia's own encoder instead, and
80
+ * `saveImage` takes whichever it is handed rather than making a node care.
81
+ */
82
+ export interface ImageSource {
83
+ convertToBlob?(options?: {
84
+ type?: string;
85
+ quality?: number;
86
+ }): Promise<Blob>;
87
+ encode?(format: string, quality?: number): Promise<Uint8Array>;
88
+ toBuffer?(mime: string): Uint8Array;
89
+ }
90
+ /**
91
+ * Write pixels into the project and get back the path to hand downstream.
92
+ * Confined to the cache directory, like every other node output.
93
+ *
94
+ * const out = await saveImage(canvas, cachePath(node.id, '.png'));
95
+ * node.out('path').setValue(out);
96
+ */
97
+ export declare function saveImage(source: HTMLCanvasElement | OffscreenCanvas | Blob | ImageSource, path: string, options?: SaveOptions): Promise<string>;
98
+ /** Write any bytes into the project cache and return the path. */
99
+ export declare function saveBytes(data: Blob | ArrayBuffer | Uint8Array, path: string): Promise<string>;
100
+ /**
101
+ * The output path convention, shared with the Python side: scoped by node id so
102
+ * one node's scratch file never lands on a sibling's. The server puts the cache
103
+ * key into the filename when it writes, so different inputs keep separate files.
104
+ */
105
+ export declare function cachePath(nodeId: string, suffix: string): string;
106
+ /**
107
+ * Float pixel data, shared between Python and JavaScript with no conversion.
108
+ *
109
+ * This works because a `.npy` file holding little-endian float32 in C order IS
110
+ * a Float32Array with a header in front of it. numpy pads that header so the
111
+ * data begins on a 64-byte boundary — measured on this project's own files, it
112
+ * starts at byte 128 every time — which means a typed array can be constructed
113
+ * as a VIEW onto the fetched buffer rather than a copy of it. A 10 MB density
114
+ * field costs one fetch and no marshalling.
115
+ *
116
+ * From there it goes straight where you need it: into `texImage2D` as an R32F
117
+ * or RGBA32F texture, into a compute pass, or through `toImageData` for
118
+ * Canvas2D. And `saveFloats` writes the same layout back, so `np.load` on the
119
+ * Python side reads it as an ordinary array.
120
+ */
121
+ export interface FloatImage {
122
+ data: Float32Array;
123
+ /** [width, height] */
124
+ size: [number, number];
125
+ /** 1 for a mask or signal, 3 for RGB, 4 for RGBA. */
126
+ channels: number;
127
+ }
128
+ /**
129
+ * Read a float image as a zero-copy Float32Array view. Non-float and
130
+ * non-native-endian arrays are converted rather than refused, so a uint8 mask
131
+ * still loads — it just costs a pass.
132
+ */
133
+ export declare function loadFloats(path: string): Promise<FloatImage>;
134
+ /** Write float pixels back into the project. Python reads the result with a
135
+ * plain `np.load`. Returns the stored path. */
136
+ export declare function saveFloats(image: FloatImage, path: string): Promise<string>;
137
+ /**
138
+ * Float pixels -> ImageData for Canvas2D. Values are assumed to be 0..1 unless
139
+ * a range is given; pass the field's actual range to see it the way the viewer
140
+ * does rather than clipped.
141
+ */
142
+ export declare function toImageData(image: FloatImage, range?: [number, number]): ImageData;
143
+ /**
144
+ * Upload float pixels as a GPU texture, no conversion on the way. Needs the
145
+ * EXT_color_buffer_float extension to render INTO one; sampling works without.
146
+ */
147
+ export declare function uploadFloatTexture(gl: WebGL2RenderingContext, image: FloatImage): WebGLTexture;
148
+ /**
149
+ * A live GPU texture, passed between browser nodes.
150
+ *
151
+ * Separate from `image` on purpose, and the distinction is about cost rather
152
+ * than taste. An `image` is file-backed: durable, portable, readable by Python,
153
+ * and it costs a write and a read every time it is handed on. A `texture` never
154
+ * leaves the page — a chain of WebGL nodes passes one along with no round trip
155
+ * at all, which is what makes an interactive shader chain feel immediate.
156
+ *
157
+ * Because it is session-bound it cannot be cached, cannot be inspected after a
158
+ * reload, and cannot be read by a Python stage. So the conversions are explicit:
159
+ * `toImage` is a GPU readback and a file write, and it should be visible in the
160
+ * graph rather than happening silently on a connection.
161
+ */
162
+ export interface TextureRef {
163
+ gl: WebGL2RenderingContext;
164
+ texture: WebGLTexture;
165
+ /** [width, height] */
166
+ size: [number, number];
167
+ channels: number;
168
+ /** True when the texture holds float data rather than 8-bit. */
169
+ float: boolean;
170
+ }
171
+ /** Upload an image file to the GPU. The integer path for `uploadFloatTexture`. */
172
+ export declare function textureFromImage(gl: WebGL2RenderingContext, image: string | {
173
+ path: string;
174
+ }, options?: MediaOptions): Promise<TextureRef>;
175
+ /** Allocate an empty texture to render into. */
176
+ export declare function createTexture(gl: WebGL2RenderingContext, size: [number, number], options?: {
177
+ float?: boolean;
178
+ channels?: number;
179
+ }): TextureRef;
180
+ /**
181
+ * Read a texture back off the GPU and write it into the project as an image.
182
+ * The one place a texture becomes durable — deliberately a named step, because
183
+ * a readback stalls the pipeline and a write costs a file.
184
+ */
185
+ export declare function textureToImage(ref: TextureRef, path: string, options?: {
186
+ format?: 'png' | 'webp';
187
+ space?: 'srgb' | 'linear';
188
+ }): Promise<ImageRef>;
189
+ /** The value on an `image` port. Mirrors Cascade's core ImageRef. */
190
+ export interface ImageRef {
191
+ path: string;
192
+ size: [number, number];
193
+ channels: 'r' | 'a' | 'rg' | 'rgb' | 'rgba';
194
+ depth: 'u8' | 'u16' | 'f16' | 'f32';
195
+ space: 'srgb' | 'linear';
196
+ }
package/dist/io.js ADDED
@@ -0,0 +1,303 @@
1
+ // server/src/runtime/io.ts
2
+ function mediaUrl(path, options = {}) {
3
+ if (/^(https?:|data:|blob:)/.test(path)) return path;
4
+ const clean = path.replace(/^\.?\//, "");
5
+ const encoded = clean.split("/").map(encodeURIComponent).join("/");
6
+ const params = new URLSearchParams();
7
+ if (options.raw) params.set("raw", "1");
8
+ else {
9
+ if (options.width) params.set("w", String(options.width));
10
+ if (options.format) params.set("fmt", options.format);
11
+ if (options.quality) params.set("q", String(options.quality));
12
+ }
13
+ const query = params.toString();
14
+ return `/api/media/${encoded}${query ? `?${query}` : ""}`;
15
+ }
16
+ var IO_BRIDGE_KEY = "__cascadeIoBridge";
17
+ function installIoBridge(bridge) {
18
+ const globals = globalThis;
19
+ const previous = globals[IO_BRIDGE_KEY];
20
+ globals[IO_BRIDGE_KEY] = bridge ?? void 0;
21
+ return () => {
22
+ globals[IO_BRIDGE_KEY] = previous;
23
+ };
24
+ }
25
+ function localIo() {
26
+ const bridge = globalThis[IO_BRIDGE_KEY];
27
+ return bridge && typeof bridge.read === "function" && typeof bridge.write === "function" ? bridge : null;
28
+ }
29
+ async function loadBytes(path, options = { raw: true }) {
30
+ const bridge = localIo();
31
+ if (bridge) {
32
+ const bytes = await bridge.read(path, options);
33
+ const exact = bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength ? bytes.buffer : bytes.slice().buffer;
34
+ return exact;
35
+ }
36
+ const response = await fetch(mediaUrl(path, options));
37
+ if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
38
+ return await response.arrayBuffer();
39
+ }
40
+ async function loadImage(path, options = { raw: true }) {
41
+ const image = new Image();
42
+ const bridge = localIo();
43
+ if (bridge) {
44
+ image.src = new Uint8Array(await loadBytes(path, options));
45
+ } else {
46
+ image.crossOrigin = "anonymous";
47
+ image.src = mediaUrl(path, options);
48
+ }
49
+ await image.decode();
50
+ return image;
51
+ }
52
+ async function loadBitmap(path, options = { raw: true }) {
53
+ if (localIo()) return createImageBitmap(new Blob([await loadBytes(path, options)]));
54
+ const response = await fetch(mediaUrl(path, options));
55
+ if (!response.ok) throw new Error(`cascade/io: cannot load ${path} (${response.status})`);
56
+ return createImageBitmap(await response.blob());
57
+ }
58
+ async function loadFieldBitmap(path, options = {}) {
59
+ return loadBitmap(path, { ...options, raw: false });
60
+ }
61
+ async function saveImage(source, path, options = {}) {
62
+ const format = options.format ?? "png";
63
+ const type = `image/${format}`;
64
+ let blob;
65
+ if (source instanceof Blob) {
66
+ blob = source;
67
+ } else if (typeof source.convertToBlob === "function") {
68
+ blob = await source.convertToBlob({ type, quality: options.quality });
69
+ } else if (typeof source.encode === "function") {
70
+ blob = new Blob([await source.encode(format, options.quality)], { type });
71
+ } else if (typeof source.toBuffer === "function") {
72
+ blob = new Blob([source.toBuffer(type)], { type });
73
+ } else {
74
+ blob = await new Promise((resolve, reject) => {
75
+ source.toBlob(
76
+ (result) => result ? resolve(result) : reject(new Error("cascade/io: canvas produced no data")),
77
+ type,
78
+ options.quality
79
+ );
80
+ });
81
+ }
82
+ return saveBytes(blob, path);
83
+ }
84
+ async function saveBytes(data, path) {
85
+ const clean = path.replace(/^\.?\//, "");
86
+ const bridge = localIo();
87
+ if (bridge) {
88
+ const bytes = data instanceof Uint8Array ? data : new Uint8Array(data instanceof Blob ? await data.arrayBuffer() : data);
89
+ return await bridge.write(clean, bytes);
90
+ }
91
+ const response = await fetch(`/api/media/${clean.split("/").map(encodeURIComponent).join("/")}`, {
92
+ method: "PUT",
93
+ headers: { "Content-Type": "application/octet-stream" },
94
+ body: data
95
+ });
96
+ if (!response.ok) {
97
+ throw new Error(`cascade/io: cannot write ${path} \u2014 ${await response.text()}`);
98
+ }
99
+ const result = await response.json();
100
+ return result.path;
101
+ }
102
+ function cachePath(nodeId, suffix) {
103
+ const safe = nodeId.replace(/[^a-zA-Z0-9_-]/g, "_");
104
+ return `.cascade-cache/${safe}${suffix}`;
105
+ }
106
+ function parseNpyHeader(buffer) {
107
+ const bytes = new Uint8Array(buffer);
108
+ const magic = String.fromCharCode(...bytes.subarray(0, 6));
109
+ if (magic !== "\x93NUMPY") throw new Error("cascade/io: not a .npy file");
110
+ const view = new DataView(buffer);
111
+ const major = bytes[6];
112
+ const headerLength = major >= 2 ? view.getUint32(8, true) : view.getUint16(8, true);
113
+ const headerStart = major >= 2 ? 12 : 10;
114
+ const header = String.fromCharCode(...bytes.subarray(headerStart, headerStart + headerLength));
115
+ const dtype = header.match(/'descr':\s*'([^']+)'/)?.[1];
116
+ if (!dtype) throw new Error("cascade/io: .npy header has no dtype");
117
+ const shape = (header.match(/'shape':\s*\(([^)]*)\)/)?.[1] ?? "").split(",").map((part) => part.trim()).filter(Boolean).map(Number);
118
+ return {
119
+ dtype,
120
+ shape,
121
+ dataOffset: headerStart + headerLength,
122
+ fortran: /'fortran_order':\s*True/.test(header)
123
+ };
124
+ }
125
+ async function loadFloats(path) {
126
+ const buffer = await loadBytes(path, { raw: true });
127
+ const header = parseNpyHeader(buffer);
128
+ if (header.fortran) throw new Error("cascade/io: Fortran-ordered .npy is not supported");
129
+ if (header.shape.length < 2) throw new Error(`cascade/io: expected an image-shaped array, got ${header.shape.length}D`);
130
+ const [height, width] = header.shape;
131
+ const channels = header.shape.length > 2 ? header.shape.slice(2).reduce((a, b) => a * b, 1) : 1;
132
+ const count = width * height * channels;
133
+ let data;
134
+ if (header.dtype === "<f4" || header.dtype === "=f4") {
135
+ data = new Float32Array(buffer, header.dataOffset, count);
136
+ } else {
137
+ const source = readAs(buffer, header.dataOffset, header.dtype, count);
138
+ data = source instanceof Float32Array ? source : Float32Array.from(source);
139
+ }
140
+ return { data, size: [width, height], channels };
141
+ }
142
+ function readAs(buffer, offset, dtype, count) {
143
+ switch (dtype) {
144
+ case "<f8":
145
+ case "=f8":
146
+ return new Float64Array(buffer, offset, count);
147
+ case "|u1":
148
+ case "<u1":
149
+ case "=u1":
150
+ case "|b1":
151
+ return new Uint8Array(buffer, offset, count);
152
+ case "|i1":
153
+ return new Int8Array(buffer, offset, count);
154
+ case "<i2":
155
+ return new Int16Array(buffer, offset, count);
156
+ case "<u2":
157
+ return new Uint16Array(buffer, offset, count);
158
+ case "<i4":
159
+ return new Int32Array(buffer, offset, count);
160
+ case "<u4":
161
+ return new Uint32Array(buffer, offset, count);
162
+ default:
163
+ throw new Error(`cascade/io: .npy dtype ${dtype} is not supported`);
164
+ }
165
+ }
166
+ function encodeNpy(data, shape) {
167
+ const dict = `{'descr': '<f4', 'fortran_order': False, 'shape': (${shape.join(", ")}${shape.length === 1 ? "," : ""}), }`;
168
+ const unpadded = 10 + dict.length + 1;
169
+ const padding = (64 - unpadded % 64) % 64;
170
+ const header = dict + " ".repeat(padding) + "\n";
171
+ const out = new Uint8Array(10 + header.length + data.byteLength);
172
+ const view = new DataView(out.buffer);
173
+ out.set([147, 78, 85, 77, 80, 89, 1, 0], 0);
174
+ view.setUint16(8, header.length, true);
175
+ for (let i = 0; i < header.length; i++) out[10 + i] = header.charCodeAt(i);
176
+ out.set(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), 10 + header.length);
177
+ return out;
178
+ }
179
+ async function saveFloats(image, path) {
180
+ const [width, height] = image.size;
181
+ const shape = image.channels > 1 ? [height, width, image.channels] : [height, width];
182
+ return saveBytes(encodeNpy(image.data, shape), path);
183
+ }
184
+ function toImageData(image, range = [0, 1]) {
185
+ const [width, height] = image.size;
186
+ const [low, high] = range;
187
+ const span = high - low || 1;
188
+ const out = new Uint8ClampedArray(width * height * 4);
189
+ for (let i = 0; i < width * height; i++) {
190
+ const base = i * image.channels;
191
+ const r = (image.data[base] - low) / span * 255;
192
+ const g = image.channels > 1 ? (image.data[base + 1] - low) / span * 255 : r;
193
+ const b = image.channels > 2 ? (image.data[base + 2] - low) / span * 255 : r;
194
+ const a = image.channels > 3 ? image.data[base + 3] * 255 : 255;
195
+ out[i * 4] = r;
196
+ out[i * 4 + 1] = g;
197
+ out[i * 4 + 2] = b;
198
+ out[i * 4 + 3] = a;
199
+ }
200
+ return new ImageData(out, width, height);
201
+ }
202
+ function uploadFloatTexture(gl, image) {
203
+ const [width, height] = image.size;
204
+ const formats = {
205
+ 1: [gl.R32F, gl.RED],
206
+ 2: [gl.RG32F, gl.RG],
207
+ 3: [gl.RGB32F, gl.RGB],
208
+ 4: [gl.RGBA32F, gl.RGBA]
209
+ };
210
+ const pair = formats[image.channels];
211
+ if (!pair) throw new Error(`cascade/io: ${image.channels} channels has no texture format`);
212
+ const [internalFormat, format] = pair;
213
+ const texture = gl.createTexture();
214
+ gl.bindTexture(gl.TEXTURE_2D, texture);
215
+ gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
216
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
217
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
218
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
219
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
220
+ gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, gl.FLOAT, image.data);
221
+ return texture;
222
+ }
223
+ async function textureFromImage(gl, image, options = { raw: true }) {
224
+ const path = typeof image === "string" ? image : image.path;
225
+ if (path.endsWith(".npy")) {
226
+ const floats = await loadFloats(path);
227
+ return {
228
+ gl,
229
+ texture: uploadFloatTexture(gl, floats),
230
+ size: floats.size,
231
+ channels: floats.channels,
232
+ float: true
233
+ };
234
+ }
235
+ const bitmap = await loadBitmap(path, options);
236
+ const texture = gl.createTexture();
237
+ gl.bindTexture(gl.TEXTURE_2D, texture);
238
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
239
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
240
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
241
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
242
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bitmap);
243
+ const size = [bitmap.width, bitmap.height];
244
+ bitmap.close();
245
+ return { gl, texture, size, channels: 4, float: false };
246
+ }
247
+ function createTexture(gl, size, options = {}) {
248
+ const [width, height] = size;
249
+ const float = options.float ?? false;
250
+ const channels = options.channels ?? 4;
251
+ const texture = gl.createTexture();
252
+ gl.bindTexture(gl.TEXTURE_2D, texture);
253
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
254
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
255
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
256
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
257
+ gl.texStorage2D(gl.TEXTURE_2D, 1, float ? gl.RGBA32F : gl.RGBA8, width, height);
258
+ return { gl, texture, size, channels, float };
259
+ }
260
+ async function textureToImage(ref, path, options = {}) {
261
+ const { gl, size } = ref;
262
+ const [width, height] = size;
263
+ const framebuffer = gl.createFramebuffer();
264
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
265
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, ref.texture, 0);
266
+ try {
267
+ if (ref.float) {
268
+ const pixels2 = new Float32Array(width * height * 4);
269
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.FLOAT, pixels2);
270
+ const stored2 = await saveFloats({ data: pixels2, size, channels: 4 }, path.replace(/\.[^.]+$/, "") + ".npy");
271
+ return { path: stored2, size, channels: "rgba", depth: "f32", space: options.space ?? "linear" };
272
+ }
273
+ const pixels = new Uint8ClampedArray(width * height * 4);
274
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
275
+ const canvas = new OffscreenCanvas(width, height);
276
+ canvas.getContext("2d").putImageData(new ImageData(pixels, width, height), 0, 0);
277
+ const format = options.format ?? "png";
278
+ const stored = await saveImage(canvas, path, { format });
279
+ return { path: stored, size, channels: "rgba", depth: "u8", space: options.space ?? "srgb" };
280
+ } finally {
281
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
282
+ gl.deleteFramebuffer(framebuffer);
283
+ }
284
+ }
285
+ export {
286
+ cachePath,
287
+ createTexture,
288
+ installIoBridge,
289
+ loadBitmap,
290
+ loadBytes,
291
+ loadFieldBitmap,
292
+ loadFloats,
293
+ loadImage,
294
+ mediaUrl,
295
+ saveBytes,
296
+ saveFloats,
297
+ saveImage,
298
+ textureFromImage,
299
+ textureToImage,
300
+ toImageData,
301
+ uploadFloatTexture
302
+ };
303
+ //# sourceMappingURL=io.js.map
package/dist/net.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function authorizedFetch(credential: string, url: string, init?: RequestInit): Promise<Response>;
package/dist/net.js ADDED
@@ -0,0 +1,28 @@
1
+ // server/src/runtime/net.ts
2
+ var capability = null;
3
+ async function authorizedFetch(credential, url, init = {}) {
4
+ capability ??= fetch("/api/net/capability").then(async (response2) => {
5
+ if (!response2.ok) throw new Error(`Authorized fetch capability failed: HTTP ${response2.status}`);
6
+ return (await response2.json()).capability;
7
+ });
8
+ const headers = Object.fromEntries(new Headers(init.headers).entries());
9
+ const response = await fetch("/api/net", {
10
+ method: "POST",
11
+ headers: { "Content-Type": "application/json", "X-Cascade-Net-Capability": await capability },
12
+ body: JSON.stringify({
13
+ credential,
14
+ url,
15
+ method: init.method ?? "GET",
16
+ headers,
17
+ ...typeof init.body === "string" ? { body: init.body } : {}
18
+ }),
19
+ signal: init.signal
20
+ });
21
+ const result = await response.json();
22
+ if (!response.ok || !result.ok) throw new Error(result.error ?? `Authorized fetch failed: HTTP ${response.status}`);
23
+ return new Response(result.body, { status: result.status, statusText: result.statusText, headers: result.headers });
24
+ }
25
+ export {
26
+ authorizedFetch
27
+ };
28
+ //# sourceMappingURL=net.js.map