@field/cascade 0.2.22 → 0.3.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 (228) hide show
  1. package/AGENTS.md +47 -7
  2. package/ARCHITECTURE.md +30 -2
  3. package/CHANGELOG.md +89 -0
  4. package/DESIGN.md +10 -5
  5. package/README.md +103 -7
  6. package/dist/assets/CodeEditor-BADFbTJS.js +623 -0
  7. package/dist/assets/CodePanel-faK6PRfz.js +2 -0
  8. package/dist/assets/CustomNodeDialog-DhlKuviy.js +1 -0
  9. package/dist/assets/{ExportDialog-DWN4nhXI.js → ExportDialog-BJu2Bpvv.js} +2 -2
  10. package/dist/assets/ProjectSettings-D6a6kjqR.js +2 -0
  11. package/dist/assets/SettingsDialog-DJZdIvLC.js +1 -0
  12. package/dist/assets/VersionHistoryDialog-C1X3HOhc.js +3 -0
  13. package/dist/assets/{core-Bji4aZUU.js → core-CiuiuWZi.js} +29 -38
  14. package/dist/assets/{cssMode-B6Z_Q1Kh.js → cssMode-D-NwZSMJ.js} +1 -1
  15. package/dist/assets/{editor.api-jMoW_43X.js → editor.api-DT3WEvBd.js} +1 -1
  16. package/dist/assets/{freemarker2-DvvPV6m5.js → freemarker2-eKNYQaiz.js} +1 -1
  17. package/dist/assets/geo-DUvVbLnx.js +1 -0
  18. package/dist/assets/{handlebars-B8rfuSIa.js → handlebars-C2w1ggK2.js} +1 -1
  19. package/dist/assets/{html-DyHm6JCp.js → html-DQb41jyo.js} +1 -1
  20. package/dist/assets/{htmlMode-E9XLWE5u.js → htmlMode-IhNlh6Lv.js} +1 -1
  21. package/dist/assets/{image-BUcQC7PG.js → image-DMCXBY-_.js} +26 -40
  22. package/dist/assets/index-BDV5xGBZ.css +1 -0
  23. package/dist/assets/index-HTCN44kj.js +212 -0
  24. package/dist/assets/index-client-BoJgsN9M.js +1 -0
  25. package/dist/assets/{javascript-C_bM5PCN.js → javascript-BIT6jMLC.js} +1 -1
  26. package/dist/assets/{jsonMode-BdyVS7G4.js → jsonMode-CNfyB1ro.js} +1 -1
  27. package/dist/assets/{legacy-DIu3XRwl.js → legacy-CAj2_7QE.js} +2 -2
  28. package/dist/assets/{liquid-CDkn3spA.js → liquid-CmSmJmOG.js} +1 -1
  29. package/dist/assets/{lspLanguageFeatures-AB5DMAsr.js → lspLanguageFeatures-BqMm3uMP.js} +1 -1
  30. package/dist/assets/{mdx-BvH_NjDN.js → mdx-6r6YgOFw.js} +1 -1
  31. package/dist/assets/{nodeTypeUtils-CweA3NHd.js → nodeTypeUtils-BBA1JqHx.js} +1 -1
  32. package/dist/assets/{python-B7uii4fi.js → python-x1H0TQuI.js} +1 -1
  33. package/dist/assets/{razor-Z48fWdJc.js → razor-Dzl1vZFm.js} +1 -1
  34. package/dist/assets/{tsMode-BDgacVG4.js → tsMode-n6DMMe3a.js} +1 -1
  35. package/dist/assets/{typescript-Derb6Oqa.js → typescript-ziEz65xN.js} +1 -1
  36. package/dist/assets/{workers-BtoMX_pm.js → workers-D-qUdp-r.js} +1 -1
  37. package/dist/assets/{xml-B1CaFSrG.js → xml-DMZjt5lE.js} +1 -1
  38. package/dist/assets/{yaml-HmIeA0KH.js → yaml-DFe5qZSO.js} +1 -1
  39. package/dist/cli/index.js +16194 -10195
  40. package/dist/cli/runtime/io.ts +93 -7
  41. package/dist/contracts/animation.d.ts +40 -0
  42. package/dist/contracts/animation.js +14 -0
  43. package/dist/contracts/definition.d.ts +39 -1
  44. package/dist/contracts/document.d.ts +15 -0
  45. package/dist/contracts/index.d.ts +1 -0
  46. package/dist/contracts/index.js +1 -0
  47. package/dist/contracts/validate.d.ts +10 -1
  48. package/dist/contracts/validate.js +9 -1
  49. package/dist/graphs/default.cascade +1 -1
  50. package/dist/index.html +5 -5
  51. package/dist/io.d.ts +196 -0
  52. package/dist/io.js +303 -0
  53. package/dist/net.d.ts +1 -0
  54. package/dist/net.js +28 -0
  55. package/dist/runtime/animation/channel.d.ts +108 -0
  56. package/dist/runtime/animation/channel.js +204 -0
  57. package/dist/runtime/animation/frameRange.d.ts +78 -0
  58. package/dist/runtime/animation/frameRange.js +76 -0
  59. package/dist/runtime/animation/index.d.ts +10 -0
  60. package/dist/runtime/animation/index.js +8 -0
  61. package/dist/runtime/builtins/core/random.d.ts +1 -1
  62. package/dist/runtime/builtins/core/remap.d.ts +1 -1
  63. package/dist/runtime/builtins/geo/copyToPoints.d.ts +1 -1
  64. package/dist/runtime/builtins/geo/copyToPoints.js +1 -1
  65. package/dist/runtime/builtins/geo/generators.d.ts +1 -1
  66. package/dist/runtime/builtins/geo/merge.d.ts +1 -1
  67. package/dist/runtime/builtins/geo/svgExport.d.ts +1 -1
  68. package/dist/runtime/builtins/geo/svgExport.js +1 -1
  69. package/dist/runtime/builtins/geo/transform.d.ts +1 -1
  70. package/dist/runtime/builtins/geo/transform.js +1 -1
  71. package/dist/runtime/definition/architecture.d.ts +1 -1
  72. package/dist/runtime/definition/extract.d.ts +1 -1
  73. package/dist/runtime/definition/extract.js +1 -1
  74. package/dist/runtime/error.d.ts +1 -1
  75. package/dist/runtime/expressions/evaluator.d.ts +53 -0
  76. package/dist/runtime/expressions/evaluator.js +193 -0
  77. package/dist/runtime/expressions/index.d.ts +15 -0
  78. package/dist/runtime/expressions/index.js +12 -0
  79. package/dist/runtime/expressions/math.d.ts +75 -0
  80. package/dist/runtime/expressions/math.js +140 -0
  81. package/dist/runtime/expressions/preprocess.d.ts +33 -0
  82. package/dist/runtime/expressions/preprocess.js +102 -0
  83. package/dist/runtime/expressions/scope.d.ts +27 -0
  84. package/dist/runtime/expressions/scope.js +10 -0
  85. package/dist/runtime/expressions/time.d.ts +54 -0
  86. package/dist/runtime/expressions/time.js +71 -0
  87. package/dist/runtime/expressions/types.d.ts +66 -0
  88. package/dist/runtime/expressions/types.js +9 -0
  89. package/dist/runtime/geometry/attributes.d.ts +2 -2
  90. package/dist/runtime/geometry/attributes.js +1 -1
  91. package/dist/runtime/geometry/builder.d.ts +1 -1
  92. package/dist/runtime/geometry/builder.js +1 -1
  93. package/dist/runtime/geometry/copy.d.ts +1 -1
  94. package/dist/runtime/geometry/copy.js +1 -1
  95. package/dist/runtime/geometry/generate.d.ts +1 -1
  96. package/dist/runtime/geometry/groups.d.ts +1 -1
  97. package/dist/runtime/geometry/groups.js +1 -1
  98. package/dist/runtime/geometry/matrix.d.ts +1 -1
  99. package/dist/runtime/geometry/merge.d.ts +1 -1
  100. package/dist/runtime/geometry/merge.js +1 -1
  101. package/dist/runtime/geometry/primitives.d.ts +1 -1
  102. package/dist/runtime/geometry/primitives.js +1 -1
  103. package/dist/runtime/geometry/promote.d.ts +1 -1
  104. package/dist/runtime/geometry/promote.js +1 -1
  105. package/dist/runtime/geometry/resample.d.ts +1 -1
  106. package/dist/runtime/geometry/resample.js +1 -1
  107. package/dist/runtime/geometry/svg.d.ts +1 -1
  108. package/dist/runtime/geometry/svg.js +1 -1
  109. package/dist/runtime/geometry/transform.d.ts +1 -1
  110. package/dist/runtime/geometry/transform.js +1 -1
  111. package/dist/runtime/index.d.ts +4 -1
  112. package/dist/runtime/index.js +4 -1
  113. package/dist/runtime/params/animator.d.ts +74 -0
  114. package/dist/runtime/params/animator.js +137 -0
  115. package/dist/runtime/params/index.d.ts +11 -0
  116. package/dist/runtime/params/index.js +9 -0
  117. package/dist/runtime/params/resolve.d.ts +73 -0
  118. package/dist/runtime/params/resolve.js +62 -0
  119. package/dist/runtime/runtime.d.ts +22 -0
  120. package/dist/runtime/runtime.js +242 -14
  121. package/dist/runtime/types.d.ts +30 -4
  122. package/dist/shell.d.ts +3 -3
  123. package/doc/NODE_REFERENCE.md +395 -0
  124. package/doc/PROJECT_AUTHORING.md +148 -6
  125. package/package.json +26 -3
  126. package/dist/assets/CodeEditor-DoaNEUaM.js +0 -641
  127. package/dist/assets/CodePanel-B0_2XJ-t.js +0 -2
  128. package/dist/assets/CustomNodeDialog-026lzecD.js +0 -1
  129. package/dist/assets/ProjectSettings-BzT164Yx.js +0 -2
  130. package/dist/assets/SettingsDialog-DmHb1V0r.js +0 -1
  131. package/dist/assets/VersionHistoryDialog-hH6VsXLX.js +0 -3
  132. package/dist/assets/geo-CXmCtzMM.js +0 -2
  133. package/dist/assets/index-BXYXSdgh.js +0 -224
  134. package/dist/assets/index-CrNke7SW.css +0 -1
  135. package/dist/assets/index-client-DN_FvjN8.js +0 -1
  136. package/dist/assets/quill-DjZ69wL_.js +0 -121
  137. package/dist/cli/index.js.map +0 -7
  138. package/dist/contracts/capabilities.d.ts.map +0 -1
  139. package/dist/contracts/capabilities.js.map +0 -1
  140. package/dist/contracts/color.d.ts.map +0 -1
  141. package/dist/contracts/color.js.map +0 -1
  142. package/dist/contracts/definition.d.ts.map +0 -1
  143. package/dist/contracts/definition.js.map +0 -1
  144. package/dist/contracts/document.d.ts.map +0 -1
  145. package/dist/contracts/document.js.map +0 -1
  146. package/dist/contracts/geometry.d.ts.map +0 -1
  147. package/dist/contracts/geometry.js.map +0 -1
  148. package/dist/contracts/index.d.ts.map +0 -1
  149. package/dist/contracts/index.js.map +0 -1
  150. package/dist/contracts/schema.d.ts.map +0 -1
  151. package/dist/contracts/schema.js.map +0 -1
  152. package/dist/contracts/validate.d.ts.map +0 -1
  153. package/dist/contracts/validate.js.map +0 -1
  154. package/dist/contracts/values.d.ts.map +0 -1
  155. package/dist/contracts/values.js.map +0 -1
  156. package/dist/graphs/default-orig.cascade +0 -128
  157. package/dist/runtime/browser.d.ts.map +0 -1
  158. package/dist/runtime/browser.js.map +0 -1
  159. package/dist/runtime/builtins/core/index.d.ts.map +0 -1
  160. package/dist/runtime/builtins/core/index.js.map +0 -1
  161. package/dist/runtime/builtins/core/random.d.ts.map +0 -1
  162. package/dist/runtime/builtins/core/random.js.map +0 -1
  163. package/dist/runtime/builtins/core/remap.d.ts.map +0 -1
  164. package/dist/runtime/builtins/core/remap.js.map +0 -1
  165. package/dist/runtime/builtins/core/routing.d.ts.map +0 -1
  166. package/dist/runtime/builtins/core/routing.js.map +0 -1
  167. package/dist/runtime/builtins/core/structural.d.ts.map +0 -1
  168. package/dist/runtime/builtins/core/structural.js.map +0 -1
  169. package/dist/runtime/builtins/geo/copyToPoints.d.ts.map +0 -1
  170. package/dist/runtime/builtins/geo/copyToPoints.js.map +0 -1
  171. package/dist/runtime/builtins/geo/definitions.d.ts.map +0 -1
  172. package/dist/runtime/builtins/geo/definitions.js.map +0 -1
  173. package/dist/runtime/builtins/geo/generators.d.ts.map +0 -1
  174. package/dist/runtime/builtins/geo/generators.js.map +0 -1
  175. package/dist/runtime/builtins/geo/index.d.ts.map +0 -1
  176. package/dist/runtime/builtins/geo/index.js.map +0 -1
  177. package/dist/runtime/builtins/geo/merge.d.ts.map +0 -1
  178. package/dist/runtime/builtins/geo/merge.js.map +0 -1
  179. package/dist/runtime/builtins/geo/namespace.d.ts.map +0 -1
  180. package/dist/runtime/builtins/geo/namespace.js.map +0 -1
  181. package/dist/runtime/builtins/geo/svgExport.d.ts.map +0 -1
  182. package/dist/runtime/builtins/geo/svgExport.js.map +0 -1
  183. package/dist/runtime/builtins/geo/transform.d.ts.map +0 -1
  184. package/dist/runtime/builtins/geo/transform.js.map +0 -1
  185. package/dist/runtime/builtins/index.d.ts.map +0 -1
  186. package/dist/runtime/builtins/index.js.map +0 -1
  187. package/dist/runtime/definition/architecture.d.ts.map +0 -1
  188. package/dist/runtime/definition/architecture.js.map +0 -1
  189. package/dist/runtime/definition/extract.d.ts.map +0 -1
  190. package/dist/runtime/definition/extract.js.map +0 -1
  191. package/dist/runtime/error.d.ts.map +0 -1
  192. package/dist/runtime/error.js.map +0 -1
  193. package/dist/runtime/geometry/attributes.d.ts.map +0 -1
  194. package/dist/runtime/geometry/attributes.js.map +0 -1
  195. package/dist/runtime/geometry/builder.d.ts.map +0 -1
  196. package/dist/runtime/geometry/builder.js.map +0 -1
  197. package/dist/runtime/geometry/copy.d.ts.map +0 -1
  198. package/dist/runtime/geometry/copy.js.map +0 -1
  199. package/dist/runtime/geometry/generate.d.ts.map +0 -1
  200. package/dist/runtime/geometry/generate.js.map +0 -1
  201. package/dist/runtime/geometry/groups.d.ts.map +0 -1
  202. package/dist/runtime/geometry/groups.js.map +0 -1
  203. package/dist/runtime/geometry/index.d.ts.map +0 -1
  204. package/dist/runtime/geometry/index.js.map +0 -1
  205. package/dist/runtime/geometry/matrix.d.ts.map +0 -1
  206. package/dist/runtime/geometry/matrix.js.map +0 -1
  207. package/dist/runtime/geometry/merge.d.ts.map +0 -1
  208. package/dist/runtime/geometry/merge.js.map +0 -1
  209. package/dist/runtime/geometry/primitives.d.ts.map +0 -1
  210. package/dist/runtime/geometry/primitives.js.map +0 -1
  211. package/dist/runtime/geometry/promote.d.ts.map +0 -1
  212. package/dist/runtime/geometry/promote.js.map +0 -1
  213. package/dist/runtime/geometry/resample.d.ts.map +0 -1
  214. package/dist/runtime/geometry/resample.js.map +0 -1
  215. package/dist/runtime/geometry/svg.d.ts.map +0 -1
  216. package/dist/runtime/geometry/svg.js.map +0 -1
  217. package/dist/runtime/geometry/transform.d.ts.map +0 -1
  218. package/dist/runtime/geometry/transform.js.map +0 -1
  219. package/dist/runtime/index.d.ts.map +0 -1
  220. package/dist/runtime/index.js.map +0 -1
  221. package/dist/runtime/node.d.ts.map +0 -1
  222. package/dist/runtime/node.js.map +0 -1
  223. package/dist/runtime/runtime.d.ts.map +0 -1
  224. package/dist/runtime/runtime.js.map +0 -1
  225. package/dist/runtime/types.d.ts.map +0 -1
  226. package/dist/runtime/types.js.map +0 -1
  227. package/dist/shell.js.map +0 -7
  228. package/dist/stage.js.map +0 -7
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
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Keyframe channels, neutral runtime.
3
+ *
4
+ * A channel is the third way a parameter becomes a value, beside a literal and
5
+ * an expression. It is an ordered list of keys and a rule for what happens
6
+ * between them, and it is sampled — never pushed. The host asks for the value
7
+ * at a frame; nothing here holds a clock, a graph or a node, which is what
8
+ * keeps the whole thing deterministic and testable on its own.
9
+ *
10
+ * `sin($T)` and a drawn curve are the same shape: one written, one drawn.
11
+ *
12
+ * Every function here is pure. A channel is treated as immutable — `setKey`
13
+ * and `deleteKey` return a new channel rather than editing one in place — so a
14
+ * caller can hold a channel across a cook without it changing underneath.
15
+ */
16
+ /**
17
+ * What happens between a key and the next one. The mode belongs to the key on
18
+ * the LEFT of a segment: it describes how the value leaves that key.
19
+ *
20
+ * constant — hold the left value until the next key. Steps, not motion
21
+ * linear — straight line. Constant velocity, visible corners at the keys
22
+ * smooth — Hermite with automatic tangents. The default, because linear
23
+ * everywhere is how animation looks when nobody chose
24
+ */
25
+ export type Interpolation = "constant" | "linear" | "smooth";
26
+ /** Smooth, deliberately: an unset default of `linear` animates like a robot. */
27
+ export declare const DEFAULT_INTERPOLATION: Interpolation;
28
+ export declare function isInterpolation(value: unknown): value is Interpolation;
29
+ /** One key. `frame` may be fractional; `value` is a plain number. */
30
+ export interface Keyframe {
31
+ readonly frame: number;
32
+ readonly value: number;
33
+ readonly interpolation: Interpolation;
34
+ }
35
+ /** A key as a caller may hand it in — interpolation optional. */
36
+ export interface KeyframeInput {
37
+ readonly frame: number;
38
+ readonly value: number;
39
+ readonly interpolation?: Interpolation | string;
40
+ }
41
+ /**
42
+ * An animated parameter. `keys` is always sorted by frame and holds at most
43
+ * one key per frame — `createChannel` is the only way that invariant is
44
+ * established, and every other function here preserves it.
45
+ */
46
+ export interface Channel {
47
+ readonly keys: readonly Keyframe[];
48
+ }
49
+ /** The empty channel. Sampling it returns undefined, so a caller falls back. */
50
+ export declare const EMPTY_CHANNEL: Channel;
51
+ /**
52
+ * Build a channel from keys in any order, with anything unusable dropped.
53
+ *
54
+ * Unordered input is sorted rather than rejected — a file edited by hand and a
55
+ * UI that appends are both legitimate sources. A duplicate frame keeps the
56
+ * LAST key given, which is what "set this key again" means.
57
+ */
58
+ export declare function createChannel(keys?: readonly KeyframeInput[]): Channel;
59
+ export declare function isChannel(value: unknown): value is Channel;
60
+ /** A channel with no keys resolves to nothing, so the raw value still applies. */
61
+ export declare function isEmptyChannel(channel: Channel | null | undefined): boolean;
62
+ export declare function keyAt(channel: Channel, frame: number): Keyframe | undefined;
63
+ /**
64
+ * Set a key, replacing any key already on that frame.
65
+ *
66
+ * Re-keying an existing frame keeps that key's interpolation unless a new one
67
+ * is given: dragging a value should not silently change its easing.
68
+ */
69
+ export declare function setKey(channel: Channel | null | undefined, frame: number, value: number, interpolation?: Interpolation): Channel;
70
+ /** Remove the key on that frame. Removing the last key leaves an empty channel. */
71
+ export declare function deleteKey(channel: Channel | null | undefined, frame: number): Channel;
72
+ /** Change one key's outgoing interpolation without touching its value. */
73
+ export declare function setKeyInterpolation(channel: Channel | null | undefined, frame: number, interpolation: Interpolation): Channel;
74
+ /**
75
+ * The value at any frame, fractional included.
76
+ *
77
+ * Returns undefined for a channel with no keys — that is the signal to fall
78
+ * back to the parameter's raw value rather than to invent a zero.
79
+ *
80
+ * Outside the key range the end values are HELD, in both directions. No
81
+ * extrapolation: a curve that keeps climbing past its last key is never what
82
+ * anyone meant, and it is the failure that is hardest to see in a render.
83
+ */
84
+ export declare function sampleChannel(channel: Channel | null | undefined, frame: number): number | undefined;
85
+ /**
86
+ * The on-disk form. Interpolation is omitted when it is the default, so a
87
+ * plain channel reads as `{ "keys": [{ "frame": 1, "value": 0 }] }` and a diff
88
+ * shows easing changes rather than noise.
89
+ *
90
+ * Structurally identical to `CascadeSerializedChannel` in the contracts
91
+ * package, which is where the document shape is declared. It is restated here
92
+ * so this module keeps depending on nothing.
93
+ */
94
+ export interface SerializedKeyframe {
95
+ frame: number;
96
+ value: number;
97
+ interpolation?: Interpolation;
98
+ }
99
+ export interface SerializedChannel {
100
+ keys: SerializedKeyframe[];
101
+ }
102
+ export declare function serializeChannel(channel: Channel | null | undefined): SerializedChannel | undefined;
103
+ /**
104
+ * Read a channel back. Anything unrecognised yields the empty channel rather
105
+ * than an exception — a bad channel in one parameter must not fail a load.
106
+ */
107
+ export declare function deserializeChannel(data: unknown): Channel;
108
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Keyframe channels, neutral runtime.
3
+ *
4
+ * A channel is the third way a parameter becomes a value, beside a literal and
5
+ * an expression. It is an ordered list of keys and a rule for what happens
6
+ * between them, and it is sampled — never pushed. The host asks for the value
7
+ * at a frame; nothing here holds a clock, a graph or a node, which is what
8
+ * keeps the whole thing deterministic and testable on its own.
9
+ *
10
+ * `sin($T)` and a drawn curve are the same shape: one written, one drawn.
11
+ *
12
+ * Every function here is pure. A channel is treated as immutable — `setKey`
13
+ * and `deleteKey` return a new channel rather than editing one in place — so a
14
+ * caller can hold a channel across a cook without it changing underneath.
15
+ */
16
+ /** Smooth, deliberately: an unset default of `linear` animates like a robot. */
17
+ export const DEFAULT_INTERPOLATION = "smooth";
18
+ const INTERPOLATIONS = ["constant", "linear", "smooth"];
19
+ export function isInterpolation(value) {
20
+ return typeof value === "string" && INTERPOLATIONS.includes(value);
21
+ }
22
+ /** The empty channel. Sampling it returns undefined, so a caller falls back. */
23
+ export const EMPTY_CHANNEL = Object.freeze({ keys: Object.freeze([]) });
24
+ /**
25
+ * Build a channel from keys in any order, with anything unusable dropped.
26
+ *
27
+ * Unordered input is sorted rather than rejected — a file edited by hand and a
28
+ * UI that appends are both legitimate sources. A duplicate frame keeps the
29
+ * LAST key given, which is what "set this key again" means.
30
+ */
31
+ export function createChannel(keys = []) {
32
+ const byFrame = new Map();
33
+ for (const key of keys) {
34
+ if (!key)
35
+ continue;
36
+ const frame = Number(key.frame);
37
+ const value = Number(key.value);
38
+ if (!Number.isFinite(frame) || !Number.isFinite(value))
39
+ continue;
40
+ byFrame.set(frame, {
41
+ frame,
42
+ value,
43
+ interpolation: isInterpolation(key.interpolation) ? key.interpolation : DEFAULT_INTERPOLATION
44
+ });
45
+ }
46
+ const sorted = [...byFrame.values()].sort((a, b) => a.frame - b.frame);
47
+ return { keys: sorted };
48
+ }
49
+ export function isChannel(value) {
50
+ return !!value && typeof value === "object" && Array.isArray(value.keys);
51
+ }
52
+ /** A channel with no keys resolves to nothing, so the raw value still applies. */
53
+ export function isEmptyChannel(channel) {
54
+ return !channel || channel.keys.length === 0;
55
+ }
56
+ export function keyAt(channel, frame) {
57
+ return channel.keys.find(key => key.frame === frame);
58
+ }
59
+ /**
60
+ * Set a key, replacing any key already on that frame.
61
+ *
62
+ * Re-keying an existing frame keeps that key's interpolation unless a new one
63
+ * is given: dragging a value should not silently change its easing.
64
+ */
65
+ export function setKey(channel, frame, value, interpolation) {
66
+ const base = channel ?? EMPTY_CHANNEL;
67
+ if (!Number.isFinite(frame) || !Number.isFinite(value))
68
+ return createChannel(base.keys);
69
+ const existing = keyAt(base, frame);
70
+ const mode = interpolation ?? existing?.interpolation ?? DEFAULT_INTERPOLATION;
71
+ const kept = base.keys.filter(key => key.frame !== frame);
72
+ return createChannel([...kept, { frame, value, interpolation: mode }]);
73
+ }
74
+ /** Remove the key on that frame. Removing the last key leaves an empty channel. */
75
+ export function deleteKey(channel, frame) {
76
+ const base = channel ?? EMPTY_CHANNEL;
77
+ return createChannel(base.keys.filter(key => key.frame !== frame));
78
+ }
79
+ /** Change one key's outgoing interpolation without touching its value. */
80
+ export function setKeyInterpolation(channel, frame, interpolation) {
81
+ const base = channel ?? EMPTY_CHANNEL;
82
+ const existing = keyAt(base, frame);
83
+ if (!existing)
84
+ return createChannel(base.keys);
85
+ return setKey(base, frame, existing.value, interpolation);
86
+ }
87
+ /**
88
+ * The value at any frame, fractional included.
89
+ *
90
+ * Returns undefined for a channel with no keys — that is the signal to fall
91
+ * back to the parameter's raw value rather than to invent a zero.
92
+ *
93
+ * Outside the key range the end values are HELD, in both directions. No
94
+ * extrapolation: a curve that keeps climbing past its last key is never what
95
+ * anyone meant, and it is the failure that is hardest to see in a render.
96
+ */
97
+ export function sampleChannel(channel, frame) {
98
+ if (!channel || channel.keys.length === 0)
99
+ return undefined;
100
+ const keys = channel.keys;
101
+ if (!Number.isFinite(frame))
102
+ return keys[0].value;
103
+ if (keys.length === 1)
104
+ return keys[0].value;
105
+ if (frame <= keys[0].frame)
106
+ return keys[0].value;
107
+ if (frame >= keys[keys.length - 1].frame)
108
+ return keys[keys.length - 1].value;
109
+ const i = segmentIndex(keys, frame);
110
+ const k0 = keys[i];
111
+ const k1 = keys[i + 1];
112
+ const span = k1.frame - k0.frame;
113
+ if (span <= 0)
114
+ return k1.value;
115
+ const t = (frame - k0.frame) / span;
116
+ switch (k0.interpolation) {
117
+ case "constant":
118
+ return k0.value;
119
+ case "linear":
120
+ return k0.value + (k1.value - k0.value) * t;
121
+ case "smooth":
122
+ default:
123
+ return hermite(keys, i, t, span);
124
+ }
125
+ }
126
+ /** Index of the key at or before `frame`, binary searched. */
127
+ function segmentIndex(keys, frame) {
128
+ let lo = 0;
129
+ let hi = keys.length - 1;
130
+ while (lo < hi) {
131
+ const mid = (lo + hi + 1) >> 1;
132
+ if (keys[mid].frame <= frame)
133
+ lo = mid;
134
+ else
135
+ hi = mid - 1;
136
+ }
137
+ return Math.min(lo, keys.length - 2);
138
+ }
139
+ /**
140
+ * Cubic Hermite across segment `i`, with tangents derived from the neighbours.
141
+ *
142
+ * There are no tangent handles yet — that is the curve editor, and it is the
143
+ * largest single piece of UI in the plan. Until it exists the tangents are
144
+ * automatic, in the way both Houdini and After Effects pick them by default:
145
+ *
146
+ * - a key with a neighbour on each side takes the central difference, which
147
+ * is what makes velocity continuous through a key rather than kinked
148
+ * - the first and last key are FLAT, so a two-key smooth channel eases in
149
+ * and out. This is the behaviour people mean by "smooth"; a straight line
150
+ * between two keys would be indistinguishable from linear
151
+ * - a constant segment next door flattens the tangent it touches, so a hold
152
+ * does not make the neighbouring curve bulge to reach it
153
+ */
154
+ function hermite(keys, i, t, span) {
155
+ const k0 = keys[i];
156
+ const k1 = keys[i + 1];
157
+ const m0 = tangent(keys, i, "out");
158
+ const m1 = tangent(keys, i + 1, "in");
159
+ const t2 = t * t;
160
+ const t3 = t2 * t;
161
+ const h00 = 2 * t3 - 3 * t2 + 1;
162
+ const h10 = t3 - 2 * t2 + t;
163
+ const h01 = -2 * t3 + 3 * t2;
164
+ const h11 = t3 - t2;
165
+ return h00 * k0.value + h10 * span * m0 + h01 * k1.value + h11 * span * m1;
166
+ }
167
+ /** Value change per frame at key `i`, seen from the segment on `side`. */
168
+ function tangent(keys, i, side) {
169
+ const prev = keys[i - 1];
170
+ const next = keys[i + 1];
171
+ if (!prev || !next)
172
+ return 0;
173
+ // A hold on either side means the curve arrives, or leaves, level.
174
+ if (side === "out" && prev.interpolation === "constant")
175
+ return 0;
176
+ if (side === "in" && keys[i].interpolation === "constant")
177
+ return 0;
178
+ const span = next.frame - prev.frame;
179
+ if (span <= 0)
180
+ return 0;
181
+ return (next.value - prev.value) / span;
182
+ }
183
+ export function serializeChannel(channel) {
184
+ if (isEmptyChannel(channel))
185
+ return undefined;
186
+ return {
187
+ keys: channel.keys.map(key => (key.interpolation === DEFAULT_INTERPOLATION
188
+ ? { frame: key.frame, value: key.value }
189
+ : { frame: key.frame, value: key.value, interpolation: key.interpolation }))
190
+ };
191
+ }
192
+ /**
193
+ * Read a channel back. Anything unrecognised yields the empty channel rather
194
+ * than an exception — a bad channel in one parameter must not fail a load.
195
+ */
196
+ export function deserializeChannel(data) {
197
+ if (!data || typeof data !== "object")
198
+ return EMPTY_CHANNEL;
199
+ const keys = data.keys;
200
+ if (!Array.isArray(keys))
201
+ return EMPTY_CHANNEL;
202
+ return createChannel(keys);
203
+ }
204
+ //# sourceMappingURL=channel.js.map