@bian-womp/spark-graph 0.3.88 → 0.3.89

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 (157) hide show
  1. package/lib/src/builder/GraphBuilder.d.ts +43 -0
  2. package/lib/src/builder/GraphBuilder.d.ts.map +1 -0
  3. package/lib/src/builder/GraphBuilder.js +279 -0
  4. package/lib/src/builder/GraphBuilder.js.map +1 -0
  5. package/lib/src/builder/Registry.d.ts +87 -0
  6. package/lib/src/builder/Registry.d.ts.map +1 -0
  7. package/lib/src/builder/Registry.js +390 -0
  8. package/lib/src/builder/Registry.js.map +1 -0
  9. package/lib/src/core/categories.d.ts +22 -0
  10. package/lib/src/core/categories.d.ts.map +1 -0
  11. package/lib/src/core/categories.js +2 -0
  12. package/lib/src/core/categories.js.map +1 -0
  13. package/lib/src/core/order.d.ts +7 -0
  14. package/lib/src/core/order.d.ts.map +1 -0
  15. package/lib/src/core/order.js +66 -0
  16. package/lib/src/core/order.js.map +1 -0
  17. package/lib/src/core/type-utils.d.ts +29 -0
  18. package/lib/src/core/type-utils.d.ts.map +1 -0
  19. package/lib/src/core/type-utils.js +92 -0
  20. package/lib/src/core/type-utils.js.map +1 -0
  21. package/lib/src/core/types.d.ts +95 -0
  22. package/lib/src/core/types.d.ts.map +1 -0
  23. package/lib/src/core/types.js +2 -0
  24. package/lib/src/core/types.js.map +1 -0
  25. package/lib/src/examples/arrays.d.ts +5 -0
  26. package/lib/src/examples/arrays.d.ts.map +1 -0
  27. package/lib/src/examples/arrays.js +49 -0
  28. package/lib/src/examples/arrays.js.map +1 -0
  29. package/lib/src/examples/async.d.ts +5 -0
  30. package/lib/src/examples/async.d.ts.map +1 -0
  31. package/lib/src/examples/async.js +91 -0
  32. package/lib/src/examples/async.js.map +1 -0
  33. package/lib/src/examples/progress.d.ts +5 -0
  34. package/lib/src/examples/progress.d.ts.map +1 -0
  35. package/lib/src/examples/progress.js +51 -0
  36. package/lib/src/examples/progress.js.map +1 -0
  37. package/lib/src/examples/run.d.ts +2 -0
  38. package/lib/src/examples/run.d.ts.map +1 -0
  39. package/lib/src/examples/run.js +32 -0
  40. package/lib/src/examples/run.js.map +1 -0
  41. package/lib/src/examples/runMode.d.ts +2 -0
  42. package/lib/src/examples/runMode.d.ts.map +1 -0
  43. package/lib/src/examples/runMode.js +223 -0
  44. package/lib/src/examples/runMode.js.map +1 -0
  45. package/lib/src/examples/shared.d.ts +5 -0
  46. package/lib/src/examples/shared.d.ts.map +1 -0
  47. package/lib/src/examples/shared.js +47 -0
  48. package/lib/src/examples/shared.js.map +1 -0
  49. package/lib/src/examples/simple.d.ts +5 -0
  50. package/lib/src/examples/simple.d.ts.map +1 -0
  51. package/lib/src/examples/simple.js +79 -0
  52. package/lib/src/examples/simple.js.map +1 -0
  53. package/lib/src/examples/snapshot.d.ts +4 -0
  54. package/lib/src/examples/snapshot.d.ts.map +1 -0
  55. package/lib/src/examples/snapshot.js +58 -0
  56. package/lib/src/examples/snapshot.js.map +1 -0
  57. package/lib/src/examples/validation.d.ts +5 -0
  58. package/lib/src/examples/validation.d.ts.map +1 -0
  59. package/lib/src/examples/validation.js +105 -0
  60. package/lib/src/examples/validation.js.map +1 -0
  61. package/lib/src/index.d.ts +27 -0
  62. package/lib/src/index.d.ts.map +1 -0
  63. package/lib/src/index.js +19 -0
  64. package/lib/src/index.js.map +1 -0
  65. package/lib/src/misc/base.d.ts +51 -0
  66. package/lib/src/misc/base.d.ts.map +1 -0
  67. package/lib/src/misc/base.js +1091 -0
  68. package/lib/src/misc/base.js.map +1 -0
  69. package/lib/src/misc/utils/LevelLogger.d.ts +150 -0
  70. package/lib/src/misc/utils/LevelLogger.d.ts.map +1 -0
  71. package/lib/src/misc/utils/LevelLogger.js +420 -0
  72. package/lib/src/misc/utils/LevelLogger.js.map +1 -0
  73. package/lib/src/misc/utils/LevelLogger.test.d.ts +2 -0
  74. package/lib/src/misc/utils/LevelLogger.test.d.ts.map +1 -0
  75. package/lib/src/misc/utils/LevelLogger.test.js +283 -0
  76. package/lib/src/misc/utils/LevelLogger.test.js.map +1 -0
  77. package/lib/src/misc/utils/json.d.ts +34 -0
  78. package/lib/src/misc/utils/json.d.ts.map +1 -0
  79. package/lib/src/misc/utils/json.js +471 -0
  80. package/lib/src/misc/utils/json.js.map +1 -0
  81. package/lib/src/misc/utils/merge.d.ts +51 -0
  82. package/lib/src/misc/utils/merge.d.ts.map +1 -0
  83. package/lib/src/misc/utils/merge.js +591 -0
  84. package/lib/src/misc/utils/merge.js.map +1 -0
  85. package/lib/src/misc/utils/test-logger-output.d.ts +7 -0
  86. package/lib/src/misc/utils/test-logger-output.d.ts.map +1 -0
  87. package/lib/src/misc/utils/test-logger-output.js +48 -0
  88. package/lib/src/misc/utils/test-logger-output.js.map +1 -0
  89. package/lib/src/plugins/composite.d.ts +22 -0
  90. package/lib/src/plugins/composite.d.ts.map +1 -0
  91. package/lib/src/plugins/composite.js +59 -0
  92. package/lib/src/plugins/composite.js.map +1 -0
  93. package/lib/src/plugins/compute.d.ts +5 -0
  94. package/lib/src/plugins/compute.d.ts.map +1 -0
  95. package/lib/src/plugins/compute.js +39 -0
  96. package/lib/src/plugins/compute.js.map +1 -0
  97. package/lib/src/runtime/Engine.d.ts +26 -0
  98. package/lib/src/runtime/Engine.d.ts.map +1 -0
  99. package/lib/src/runtime/Engine.js +2 -0
  100. package/lib/src/runtime/Engine.js.map +1 -0
  101. package/lib/src/runtime/GraphLifecycleApi.d.ts +46 -0
  102. package/lib/src/runtime/GraphLifecycleApi.d.ts.map +1 -0
  103. package/lib/src/runtime/GraphLifecycleApi.js +2 -0
  104. package/lib/src/runtime/GraphLifecycleApi.js.map +1 -0
  105. package/lib/src/runtime/GraphRuntime.d.ts +111 -0
  106. package/lib/src/runtime/GraphRuntime.d.ts.map +1 -0
  107. package/lib/src/runtime/GraphRuntime.js +791 -0
  108. package/lib/src/runtime/GraphRuntime.js.map +1 -0
  109. package/lib/src/runtime/LocalEngine.d.ts +41 -0
  110. package/lib/src/runtime/LocalEngine.d.ts.map +1 -0
  111. package/lib/src/runtime/LocalEngine.js +89 -0
  112. package/lib/src/runtime/LocalEngine.js.map +1 -0
  113. package/lib/src/runtime/components/EdgePropagator.d.ts +93 -0
  114. package/lib/src/runtime/components/EdgePropagator.d.ts.map +1 -0
  115. package/lib/src/runtime/components/EdgePropagator.js +378 -0
  116. package/lib/src/runtime/components/EdgePropagator.js.map +1 -0
  117. package/lib/src/runtime/components/EventEmitter.d.ts +12 -0
  118. package/lib/src/runtime/components/EventEmitter.d.ts.map +1 -0
  119. package/lib/src/runtime/components/EventEmitter.js +33 -0
  120. package/lib/src/runtime/components/EventEmitter.js.map +1 -0
  121. package/lib/src/runtime/components/Graph.d.ts +208 -0
  122. package/lib/src/runtime/components/Graph.d.ts.map +1 -0
  123. package/lib/src/runtime/components/Graph.js +452 -0
  124. package/lib/src/runtime/components/Graph.js.map +1 -0
  125. package/lib/src/runtime/components/HandleResolver.d.ts +36 -0
  126. package/lib/src/runtime/components/HandleResolver.d.ts.map +1 -0
  127. package/lib/src/runtime/components/HandleResolver.js +229 -0
  128. package/lib/src/runtime/components/HandleResolver.js.map +1 -0
  129. package/lib/src/runtime/components/NodeExecutor.d.ts +116 -0
  130. package/lib/src/runtime/components/NodeExecutor.d.ts.map +1 -0
  131. package/lib/src/runtime/components/NodeExecutor.js +648 -0
  132. package/lib/src/runtime/components/NodeExecutor.js.map +1 -0
  133. package/lib/src/runtime/components/RunContextManager.d.ts +90 -0
  134. package/lib/src/runtime/components/RunContextManager.d.ts.map +1 -0
  135. package/lib/src/runtime/components/RunContextManager.js +329 -0
  136. package/lib/src/runtime/components/RunContextManager.js.map +1 -0
  137. package/lib/src/runtime/components/RuntimeValidatorManager.d.ts +31 -0
  138. package/lib/src/runtime/components/RuntimeValidatorManager.d.ts.map +1 -0
  139. package/lib/src/runtime/components/RuntimeValidatorManager.js +56 -0
  140. package/lib/src/runtime/components/RuntimeValidatorManager.js.map +1 -0
  141. package/lib/src/runtime/components/graph-utils.d.ts +33 -0
  142. package/lib/src/runtime/components/graph-utils.d.ts.map +1 -0
  143. package/lib/src/runtime/components/graph-utils.js +300 -0
  144. package/lib/src/runtime/components/graph-utils.js.map +1 -0
  145. package/lib/src/runtime/components/interfaces.d.ts +59 -0
  146. package/lib/src/runtime/components/interfaces.d.ts.map +1 -0
  147. package/lib/src/runtime/components/interfaces.js +2 -0
  148. package/lib/src/runtime/components/interfaces.js.map +1 -0
  149. package/lib/src/runtime/components/types.d.ts +57 -0
  150. package/lib/src/runtime/components/types.d.ts.map +1 -0
  151. package/lib/src/runtime/components/types.js +2 -0
  152. package/lib/src/runtime/components/types.js.map +1 -0
  153. package/lib/src/runtime/utils.d.ts +21 -0
  154. package/lib/src/runtime/utils.d.ts.map +1 -0
  155. package/lib/src/runtime/utils.js +41 -0
  156. package/lib/src/runtime/utils.js.map +1 -0
  157. package/package.json +2 -2
@@ -0,0 +1,1091 @@
1
+ import { GraphBuilder } from "../builder/GraphBuilder";
2
+ import { Registry } from "../builder/Registry";
3
+ import { ComputeCategory } from "../plugins/compute";
4
+ // Helpers
5
+ const asArray = (v) => (Array.isArray(v) ? v : [Number(v)]);
6
+ const broadcast = (a, b) => {
7
+ const aa = asArray(a);
8
+ const bb = asArray(b);
9
+ if (aa.length === bb.length)
10
+ return [aa, bb];
11
+ if (aa.length === 1)
12
+ return [new Array(bb.length).fill(aa[0]), bb];
13
+ if (bb.length === 1)
14
+ return [aa, new Array(aa.length).fill(bb[0])];
15
+ const len = Math.max(aa.length, bb.length);
16
+ return [new Array(len).fill(aa[0] ?? 0), new Array(len).fill(bb[0] ?? 0)];
17
+ };
18
+ const asBoolArray = (v) => (Array.isArray(v) ? v.map((x) => Boolean(x)) : [Boolean(v)]);
19
+ const broadcastBool = (a, b) => {
20
+ const aa = asBoolArray(a);
21
+ const bb = asBoolArray(b);
22
+ if (aa.length === bb.length)
23
+ return [aa, bb];
24
+ if (aa.length === 1)
25
+ return [new Array(bb.length).fill(aa[0]), bb];
26
+ if (bb.length === 1)
27
+ return [aa, new Array(aa.length).fill(bb[0])];
28
+ const len = Math.max(aa.length, bb.length);
29
+ return [new Array(len).fill(aa[0] ?? false), new Array(len).fill(bb[0] ?? false)];
30
+ };
31
+ const clamp = (x, min, max) => Math.min(max, Math.max(min, x));
32
+ const lerp = (a, b, t) => a + (b - a) * t;
33
+ const lcg = (seed) => {
34
+ let s = seed >>> 0 || 1;
35
+ return () => (s = (s * 1664525 + 1013904223) >>> 0) / 0xffffffff;
36
+ };
37
+ // JSON Pointer helpers (RFC 6901 subset)
38
+ function jsonPointerGet(obj, pointer) {
39
+ if (!pointer || pointer === "/")
40
+ return obj;
41
+ if (!pointer.startsWith("/"))
42
+ return undefined;
43
+ const parts = pointer
44
+ .split("/")
45
+ .slice(1)
46
+ .map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
47
+ let cur = obj;
48
+ for (const key of parts) {
49
+ if (cur === undefined || cur === null)
50
+ return undefined;
51
+ cur = cur[key];
52
+ }
53
+ return cur;
54
+ }
55
+ function jsonPointerSet(obj, pointer, value) {
56
+ if (!pointer || pointer === "/")
57
+ return value;
58
+ if (!pointer.startsWith("/"))
59
+ return obj;
60
+ const parts = pointer
61
+ .split("/")
62
+ .slice(1)
63
+ .map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
64
+ const root = structuredClone(obj);
65
+ let cur = root;
66
+ for (let i = 0; i < parts.length; i++) {
67
+ const key = parts[i];
68
+ if (i === parts.length - 1) {
69
+ if (Array.isArray(cur) && key === "-")
70
+ cur.push(value);
71
+ else
72
+ cur[key] = value;
73
+ }
74
+ else {
75
+ const next = cur[key];
76
+ if (next === undefined || next === null) {
77
+ // create container heuristically
78
+ const nextKey = parts[i + 1];
79
+ cur[key] = typeof nextKey === "string" && /^[0-9]+$/.test(nextKey) ? [] : {};
80
+ }
81
+ cur = cur[key];
82
+ }
83
+ }
84
+ return root;
85
+ }
86
+ function jsonPointerRemove(obj, pointer) {
87
+ if (!pointer || pointer === "/")
88
+ return undefined;
89
+ if (!pointer.startsWith("/"))
90
+ return obj;
91
+ const parts = pointer
92
+ .split("/")
93
+ .slice(1)
94
+ .map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
95
+ const root = structuredClone(obj);
96
+ let cur = root;
97
+ for (let i = 0; i < parts.length - 1; i++) {
98
+ const key = parts[i];
99
+ if (cur === undefined || cur === null)
100
+ return root;
101
+ cur = cur[key];
102
+ }
103
+ const last = parts[parts.length - 1];
104
+ if (Array.isArray(cur)) {
105
+ const idx = last === "-" ? cur.length - 1 : Number(last);
106
+ if (Number.isFinite(idx))
107
+ cur.splice(idx, 1);
108
+ }
109
+ else if (cur && typeof cur === "object") {
110
+ delete cur[last];
111
+ }
112
+ return root;
113
+ }
114
+ function deepMerge(a, b) {
115
+ if (Array.isArray(a) && Array.isArray(b))
116
+ return [...a, ...b];
117
+ if (isPlainObject(a) && isPlainObject(b)) {
118
+ const out = { ...a };
119
+ for (const [k, v] of Object.entries(b)) {
120
+ out[k] = k in out ? deepMerge(out[k], v) : v;
121
+ }
122
+ return out;
123
+ }
124
+ return b;
125
+ }
126
+ // JSON helpers
127
+ const isPlainObject = (v) => {
128
+ if (v === null || typeof v !== "object")
129
+ return false;
130
+ const proto = Object.getPrototypeOf(v);
131
+ return proto === Object.prototype || proto === null;
132
+ };
133
+ const isJson = (v) => {
134
+ if (v === null)
135
+ return true;
136
+ const t = typeof v;
137
+ if (t === "string" || t === "number" || t === "boolean")
138
+ return true;
139
+ if (Array.isArray(v))
140
+ return v.every(isJson);
141
+ if (isPlainObject(v))
142
+ return Object.values(v).every(isJson);
143
+ return false;
144
+ };
145
+ // Export operation constants for use in examples and tests
146
+ export const BaseMathOperation = {
147
+ Add: 0,
148
+ Subtract: 1,
149
+ Multiply: 2,
150
+ Divide: 3,
151
+ Min: 4,
152
+ Max: 5,
153
+ Modulo: 6,
154
+ Power: 7,
155
+ Round: 8,
156
+ Floor: 9,
157
+ Ceil: 10,
158
+ Abs: 11,
159
+ Sum: 12,
160
+ Avg: 13,
161
+ MinAll: 14,
162
+ MaxAll: 15,
163
+ Sin: 16,
164
+ Cos: 17,
165
+ Tan: 18,
166
+ Asin: 19,
167
+ Acos: 20,
168
+ Atan: 21,
169
+ Sqrt: 22,
170
+ Exp: 23,
171
+ Log: 24,
172
+ };
173
+ export const BaseCompareOperation = {
174
+ LessThan: 0,
175
+ LessThanOrEqual: 1,
176
+ GreaterThan: 2,
177
+ GreaterThanOrEqual: 3,
178
+ Equal: 4,
179
+ NotEqual: 5,
180
+ };
181
+ export const BaseLogicOperation = {
182
+ Not: 0,
183
+ And: 1,
184
+ Or: 2,
185
+ Xor: 3,
186
+ };
187
+ export function setupBasicGraphRegistry(id) {
188
+ const registry = new Registry(id);
189
+ registry.categories.register(ComputeCategory);
190
+ registry.registerType({
191
+ id: "base.float",
192
+ validate: (v) => typeof v === "number" && !Number.isNaN(v),
193
+ bakeTarget: { nodeTypeId: "base.input.number", inputHandle: "Value" },
194
+ }, { withArray: true, arrayPickFirstDefined: true });
195
+ registry.registerType({
196
+ id: "base.bool",
197
+ validate: (v) => typeof v === "boolean",
198
+ bakeTarget: { nodeTypeId: "base.input.bool", inputHandle: "Value" },
199
+ }, { withArray: true, arrayPickFirstDefined: true });
200
+ registry.registerType({
201
+ id: "base.string",
202
+ validate: (v) => typeof v === "string",
203
+ bakeTarget: { nodeTypeId: "base.input.string", inputHandle: "Value" },
204
+ }, { withArray: true, arrayPickFirstDefined: true });
205
+ // Generic object value (JSON-compatible; object/array/primitive/null)
206
+ registry.registerType({
207
+ id: "base.object",
208
+ validate: (v) => isJson(v),
209
+ bakeTarget: { nodeTypeId: "base.input.object", inputHandle: "Value" },
210
+ }, { withArray: false });
211
+ registry.registerType({
212
+ id: "base.vec3",
213
+ validate: (v) => Array.isArray(v) && v.length === 3 && v.every((x) => typeof x === "number"),
214
+ bakeTarget: { nodeTypeId: "base.input.vec3", inputHandle: "Value" },
215
+ }, { withArray: true, arrayPickFirstDefined: true });
216
+ // float -> vec3 : map x to [x,0,0]
217
+ registry.registerCoercion("base.float", "base.vec3", (v) => {
218
+ return [Number(v) || 0, 0, 0];
219
+ });
220
+ registry.registerCoercion("base.vec3", "base.float", (value) => {
221
+ const v = value;
222
+ return Math.hypot(Number(v[0] ?? 0), Number(v[1] ?? 0), Number(v[2] ?? 0));
223
+ });
224
+ registry.registerCoercion("base.bool", "base.float", (v) => (v ? 1 : 0));
225
+ registry.registerCoercion("base.float", "base.bool", (v) => !!v);
226
+ registry.registerCoercion("base.float", "base.string", (v) => String(v));
227
+ registry.registerCoercion("base.string", "base.float", (v) => Number(v));
228
+ // Object <-> String
229
+ registry.registerCoercion("base.string", "base.object", (v) => {
230
+ return v;
231
+ });
232
+ registry.registerCoercion("base.object", "base.string", (v) => {
233
+ if (typeof v === "string")
234
+ return v;
235
+ return undefined;
236
+ });
237
+ registry.registerCoercion("base.bool", "base.object", (v) => {
238
+ return v;
239
+ });
240
+ registry.registerCoercion("base.object", "base.bool", (v) => {
241
+ if (typeof v === "boolean")
242
+ return v;
243
+ return undefined;
244
+ });
245
+ registry.registerCoercion("base.float", "base.object", (v) => {
246
+ return v;
247
+ });
248
+ registry.registerCoercion("base.object", "base.float", (v) => {
249
+ if (typeof v === "number")
250
+ return v;
251
+ return undefined;
252
+ });
253
+ registry.registerCoercion("base.vec3", "base.object", (v) => {
254
+ return v;
255
+ });
256
+ registry.registerCoercion("base.object", "base.vec3", (v) => {
257
+ try {
258
+ if (Array.isArray(v) && v.length === 3 && v.every((x) => typeof x === "number")) {
259
+ return v;
260
+ }
261
+ return undefined;
262
+ }
263
+ catch {
264
+ return undefined;
265
+ }
266
+ });
267
+ // Enums: Math Operation
268
+ registry.registerEnum({
269
+ id: "enum:base.math.operation",
270
+ options: Object.entries(BaseMathOperation).map(([label, value]) => ({
271
+ value,
272
+ label,
273
+ })),
274
+ });
275
+ // Enums: Compare Operation
276
+ registry.registerEnum({
277
+ id: "enum:base.compare.operation",
278
+ options: Object.entries(BaseCompareOperation).map(([label, value]) => ({
279
+ value,
280
+ label,
281
+ })),
282
+ });
283
+ // Enums: Logic Operation
284
+ registry.registerEnum({
285
+ id: "enum:base.logic.operation",
286
+ options: Object.entries(BaseLogicOperation).map(([label, value]) => ({
287
+ value,
288
+ label,
289
+ })),
290
+ });
291
+ // Number
292
+ registry.registerNode({
293
+ id: "base.input.number",
294
+ categoryId: "compute",
295
+ inputs: { Value: "base.float" },
296
+ outputs: { Result: "base.float" },
297
+ policy: { autoRun: true },
298
+ impl: (ins) => ({ Result: Number(ins.Value) }),
299
+ });
300
+ registry.registerNode({
301
+ id: "base.input.string",
302
+ categoryId: "compute",
303
+ inputs: { Value: "base.string" },
304
+ outputs: { Result: "base.string" },
305
+ policy: { autoRun: true },
306
+ impl: (ins) => ({ Result: String(ins.Value) }),
307
+ });
308
+ registry.registerNode({
309
+ id: "base.input.bool",
310
+ categoryId: "compute",
311
+ inputs: { Value: "base.bool" },
312
+ outputs: { Result: "base.bool" },
313
+ policy: { autoRun: true },
314
+ impl: (ins) => ({ Result: Boolean(ins.Value) }),
315
+ });
316
+ registry.registerNode({
317
+ id: "base.input.object",
318
+ categoryId: "compute",
319
+ inputs: { Value: "base.object" },
320
+ outputs: { Result: "base.object" },
321
+ policy: { autoRun: true },
322
+ impl: (ins) => ({ Result: ins.Value }),
323
+ });
324
+ registry.registerNode({
325
+ id: "base.input.vec3",
326
+ categoryId: "compute",
327
+ inputs: { Value: "base.vec3" },
328
+ outputs: { Result: "base.vec3" },
329
+ policy: { autoRun: true },
330
+ impl: (ins) => ({ Result: ins.Value }),
331
+ });
332
+ // JSON parser node: base.stringToObject
333
+ registry.registerNode({
334
+ id: "base.string.toObject",
335
+ categoryId: "compute",
336
+ inputs: { Text: "base.string" },
337
+ outputs: { Object: "base.object" },
338
+ impl: (ins) => {
339
+ const t = ins.Text ?? "";
340
+ try {
341
+ const obj = JSON.parse(t);
342
+ return { Object: obj };
343
+ }
344
+ catch {
345
+ return { Object: undefined };
346
+ }
347
+ },
348
+ });
349
+ registry.registerNode({
350
+ id: "base.object.toString",
351
+ categoryId: "compute",
352
+ inputs: { Object: "base.object" },
353
+ outputs: { Text: "base.string" },
354
+ impl: (ins) => {
355
+ return { Text: JSON.stringify(ins.Object) };
356
+ },
357
+ });
358
+ // Clamp
359
+ registry.registerNode({
360
+ id: "base.clamp",
361
+ categoryId: "compute",
362
+ inputs: { Value: "base.float[]", Min: "base.float", Max: "base.float" },
363
+ outputs: { Value: "base.float[]" },
364
+ impl: (ins) => {
365
+ const vals = asArray(ins.Value);
366
+ const min = Number(ins.Min ?? 0);
367
+ const max = Number(ins.Max ?? 1);
368
+ return { Value: vals.map((v) => clamp(Number(v), min, max)) };
369
+ },
370
+ });
371
+ // Interpolate (lerp)
372
+ registry.registerNode({
373
+ id: "base.interpolate",
374
+ categoryId: "compute",
375
+ inputs: {
376
+ ValueA: "base.float[]",
377
+ ValueB: "base.float[]",
378
+ Factor: "base.float",
379
+ },
380
+ outputs: { Value: "base.float[]" },
381
+ inputDefaults: {
382
+ Factor: 0.5,
383
+ },
384
+ impl: (ins) => {
385
+ const [a, b] = broadcast(ins.ValueA, ins.ValueB);
386
+ const t = Number(ins.Factor ?? 0);
387
+ const len = Math.max(a.length, b.length);
388
+ const out = new Array(len).fill(0).map((_, i) => lerp(Number(a[i] ?? 0), Number(b[i] ?? 0), t));
389
+ return { Value: out };
390
+ },
391
+ });
392
+ // Map Range (linear)
393
+ registry.registerNode({
394
+ id: "base.mapRange",
395
+ categoryId: "compute",
396
+ inputs: {
397
+ Mode: "base.string",
398
+ Clamp: "base.bool",
399
+ Value: "base.float[]",
400
+ FromMin: "base.float",
401
+ FromMax: "base.float",
402
+ ToMin: "base.float",
403
+ ToMax: "base.float",
404
+ },
405
+ outputs: { Value: "base.float[]" },
406
+ impl: (ins) => {
407
+ const vals = asArray(ins.Value);
408
+ const fromMin = Number(ins.FromMin ?? 0);
409
+ const fromMax = Number(ins.FromMax ?? 1);
410
+ const toMin = Number(ins.ToMin ?? 0);
411
+ const toMax = Number(ins.ToMax ?? 1);
412
+ const doClamp = Boolean(ins.Clamp);
413
+ const out = vals.map((v) => {
414
+ const t = (Number(v) - fromMin) / (fromMax - fromMin || 1);
415
+ const r = toMin + t * (toMax - toMin);
416
+ return doClamp ? clamp(r, Math.min(toMin, toMax), Math.max(toMin, toMax)) : r;
417
+ });
418
+ return { Value: out };
419
+ },
420
+ });
421
+ // Math (subset) - scalar version for simple examples
422
+ registry.registerNode({
423
+ id: "base.math",
424
+ categoryId: "compute",
425
+ inputs: {
426
+ Operation: "enum:base.math.operation",
427
+ A: "base.float[]",
428
+ B: "base.float[]",
429
+ },
430
+ outputs: { Result: "base.float[]" },
431
+ // Registry-level defaults: Add by default, A=[1], B=[1]
432
+ inputDefaults: { Operation: 0, A: [1], B: [1] },
433
+ impl: (ins) => {
434
+ const a = asArray(ins.A ?? []);
435
+ const b = asArray(ins.B ?? []);
436
+ const op = Number(ins.Operation ?? BaseMathOperation.Add);
437
+ const unaryByOp = {
438
+ [BaseMathOperation.Round]: (x) => Math.round(x),
439
+ [BaseMathOperation.Floor]: (x) => Math.floor(x),
440
+ [BaseMathOperation.Ceil]: (x) => Math.ceil(x),
441
+ [BaseMathOperation.Abs]: (x) => Math.abs(x),
442
+ [BaseMathOperation.Sin]: (x) => Math.sin(x),
443
+ [BaseMathOperation.Cos]: (x) => Math.cos(x),
444
+ [BaseMathOperation.Tan]: (x) => Math.tan(x),
445
+ [BaseMathOperation.Asin]: (x) => Math.asin(x),
446
+ [BaseMathOperation.Acos]: (x) => Math.acos(x),
447
+ [BaseMathOperation.Atan]: (x) => Math.atan(x),
448
+ [BaseMathOperation.Sqrt]: (x) => Math.sqrt(x),
449
+ [BaseMathOperation.Exp]: (x) => Math.exp(x),
450
+ [BaseMathOperation.Log]: (x) => Math.log(x),
451
+ };
452
+ if (unaryByOp[op])
453
+ return { Result: a.map((x) => unaryByOp[op](Number(x))) };
454
+ const aggregateByOp = {
455
+ [BaseMathOperation.Sum]: (arr) => arr.reduce((s, x) => s + Number(x || 0), 0),
456
+ [BaseMathOperation.Avg]: (arr) => {
457
+ const sum = arr.reduce((s, x) => s + Number(x || 0), 0);
458
+ return arr.length ? sum / arr.length : 0;
459
+ },
460
+ [BaseMathOperation.MinAll]: (arr) => (arr.length ? Math.min(...arr.map((x) => Number(x))) : 0),
461
+ [BaseMathOperation.MaxAll]: (arr) => (arr.length ? Math.max(...arr.map((x) => Number(x))) : 0),
462
+ };
463
+ if (aggregateByOp[op] !== undefined)
464
+ return { Result: [aggregateByOp[op](a)] };
465
+ const binaryByOp = {
466
+ [BaseMathOperation.Add]: (x, y) => x + y,
467
+ [BaseMathOperation.Subtract]: (x, y) => x - y,
468
+ [BaseMathOperation.Multiply]: (x, y) => x * y,
469
+ [BaseMathOperation.Divide]: (x, y) => x / (y || 1),
470
+ [BaseMathOperation.Min]: (x, y) => Math.min(x, y),
471
+ [BaseMathOperation.Max]: (x, y) => Math.max(x, y),
472
+ [BaseMathOperation.Modulo]: (x, y) => (y ? x % y : 0),
473
+ [BaseMathOperation.Power]: (x, y) => Math.pow(x, y),
474
+ };
475
+ const fn = binaryByOp[op] || binaryByOp[BaseMathOperation.Add];
476
+ const len = Math.max(a.length, b.length);
477
+ const out = new Array(len).fill(0).map((_, i) => {
478
+ const ax = a.length === 1 && len > 1 ? a[0] : (a[i] ?? 0);
479
+ const bx = b.length === 1 && len > 1 ? b[0] : (b[i] ?? 0);
480
+ return fn(Number(ax), Number(bx));
481
+ });
482
+ return { Result: out };
483
+ },
484
+ });
485
+ // Compare
486
+ registry.registerNode({
487
+ id: "base.compare",
488
+ categoryId: "compute",
489
+ inputs: {
490
+ Operation: "enum:base.compare.operation",
491
+ A: "base.float[]",
492
+ B: "base.float[]",
493
+ },
494
+ outputs: { Result: "base.bool[]" },
495
+ impl: (ins) => {
496
+ const [a, b] = broadcast(ins.A, ins.B);
497
+ const op = Number(ins.Operation ?? BaseCompareOperation.Equal);
498
+ const compareByOp = {
499
+ [BaseCompareOperation.LessThan]: (x, y) => x < y,
500
+ [BaseCompareOperation.LessThanOrEqual]: (x, y) => x <= y,
501
+ [BaseCompareOperation.GreaterThan]: (x, y) => x > y,
502
+ [BaseCompareOperation.GreaterThanOrEqual]: (x, y) => x >= y,
503
+ [BaseCompareOperation.Equal]: (x, y) => x === y,
504
+ [BaseCompareOperation.NotEqual]: (x, y) => x !== y,
505
+ };
506
+ const fn = compareByOp[op] || compareByOp[BaseCompareOperation.Equal];
507
+ return { Result: a.map((x, i) => fn(Number(x), Number(b[i] ?? 0))) };
508
+ },
509
+ });
510
+ // Logic
511
+ registry.registerNode({
512
+ id: "base.logic",
513
+ categoryId: "compute",
514
+ inputs: {
515
+ Operation: "enum:base.logic.operation",
516
+ A: "base.bool[]",
517
+ B: "base.bool[]",
518
+ },
519
+ outputs: { Result: "base.bool[]" },
520
+ inputDefaults: { Operation: BaseLogicOperation.Not },
521
+ impl: (ins) => {
522
+ const op = Number(ins.Operation ?? BaseLogicOperation.Not);
523
+ if (op === BaseLogicOperation.Not) {
524
+ const a = asBoolArray(ins.A ?? []);
525
+ return { Result: a.map((x) => !x) };
526
+ }
527
+ const [a, b] = broadcastBool(ins.A ?? [], ins.B ?? []);
528
+ const logicByOp = {
529
+ [BaseLogicOperation.And]: (x, y) => x && y,
530
+ [BaseLogicOperation.Or]: (x, y) => x || y,
531
+ [BaseLogicOperation.Xor]: (x, y) => Boolean(x ? !y : y),
532
+ };
533
+ const fn = logicByOp[op] || logicByOp[BaseLogicOperation.And];
534
+ return {
535
+ Result: a.map((x, i) => fn(Boolean(x), Boolean(b[i] ?? false))),
536
+ };
537
+ },
538
+ });
539
+ // Strings
540
+ registry.registerNode({
541
+ id: "base.string.length",
542
+ categoryId: "compute",
543
+ inputs: { Text: "base.string" },
544
+ outputs: { Length: "base.float" },
545
+ impl: (ins) => ({
546
+ Length: String(ins.Text || "").length,
547
+ }),
548
+ });
549
+ registry.registerNode({
550
+ id: "base.string.op",
551
+ categoryId: "compute",
552
+ inputs: { Op: "base.string", Text: "base.string" },
553
+ outputs: { Text: "base.string" },
554
+ impl: (ins) => {
555
+ const op = String(ins.Op || "trim").toLowerCase();
556
+ const t = String(ins.Text || "");
557
+ if (op === "lower")
558
+ return { Text: t.toLowerCase() };
559
+ if (op === "upper")
560
+ return { Text: t.toUpperCase() };
561
+ return { Text: t.trim() };
562
+ },
563
+ });
564
+ registry.registerNode({
565
+ id: "base.string.split",
566
+ categoryId: "compute",
567
+ inputs: { Text: "base.string", Sep: "base.string" },
568
+ outputs: { Parts: "base.string[]" },
569
+ impl: (ins) => {
570
+ const t = String(ins.Text || "");
571
+ const sep = String(ins.Sep || ",");
572
+ return { Parts: t.split(sep) };
573
+ },
574
+ });
575
+ registry.registerNode({
576
+ id: "base.string.join",
577
+ categoryId: "compute",
578
+ inputs: { Parts: "base.string[]", Sep: "base.string" },
579
+ outputs: { Text: "base.string" },
580
+ impl: (ins) => {
581
+ const parts = Array.isArray(ins.Parts) ? ins.Parts.map(String) : [];
582
+ const sep = String(ins.Sep || "");
583
+ return { Text: parts.join(sep) };
584
+ },
585
+ });
586
+ registry.registerNode({
587
+ id: "base.string.replace",
588
+ categoryId: "compute",
589
+ inputs: {
590
+ Text: "base.string",
591
+ Search: "base.string",
592
+ Replace: "base.string",
593
+ },
594
+ outputs: { Text: "base.string" },
595
+ impl: (ins) => {
596
+ const t = String(ins.Text || "");
597
+ const s = String(ins.Search || "");
598
+ const r = String(ins.Replace || "");
599
+ return { Text: t.split(s).join(r) };
600
+ },
601
+ });
602
+ // Arrays (carried as base.object)
603
+ registry.registerNode({
604
+ id: "base.array.length",
605
+ categoryId: "compute",
606
+ inputs: { Items: "base.object" },
607
+ outputs: { Length: "base.float" },
608
+ impl: (ins) => ({
609
+ Length: Array.isArray(ins.Items) ? ins.Items.length : 0,
610
+ }),
611
+ });
612
+ registry.registerNode({
613
+ id: "base.array.slice",
614
+ categoryId: "compute",
615
+ inputs: { Items: "base.object", Start: "base.float", End: "base.float" },
616
+ outputs: { Result: "base.object" },
617
+ impl: (ins) => {
618
+ const arr = Array.isArray(ins.Items) ? ins.Items : [];
619
+ const s = Math.trunc(Number(ins.Start ?? 0));
620
+ const e = Number.isFinite(Number(ins.End)) ? Math.trunc(Number(ins.End)) : undefined;
621
+ return { Result: arr.slice(s, e) };
622
+ },
623
+ });
624
+ // Compose array from dynamic item inputs
625
+ registry.registerNode({
626
+ id: "base.array.compose",
627
+ categoryId: "compute",
628
+ inputs: { Length: "base.float" },
629
+ outputs: { Items: "base.object" },
630
+ resolveHandles: ({ inputs }) => {
631
+ const maxLen = 64;
632
+ const raw = inputs?.Length ?? 0;
633
+ const n = Math.max(0, Math.min(maxLen, Math.trunc(Number(raw ?? 0))));
634
+ if (!Number.isFinite(n))
635
+ return { inputs: {} };
636
+ const dyn = {};
637
+ for (let i = 0; i < n; i++)
638
+ dyn[`Item${i}`] = { typeId: "base.object" };
639
+ return { inputs: dyn };
640
+ },
641
+ inputDefaults: { Length: 0 },
642
+ impl: (ins) => {
643
+ const length = Math.max(0, Math.trunc(Number(ins.Length ?? 0)));
644
+ if (!Number.isFinite(length))
645
+ return { Items: [] };
646
+ return { Items: Array.from({ length }, (_, i) => ins[`Item${i}`]) };
647
+ },
648
+ });
649
+ // Decompose array into dynamic item outputs
650
+ registry.registerNode({
651
+ id: "base.array.decompose",
652
+ categoryId: "compute",
653
+ inputs: { Items: "base.object" },
654
+ outputs: {},
655
+ resolveHandles: ({ inputs }) => {
656
+ const maxLen = 64;
657
+ const arr = Array.isArray(inputs?.Items) ? inputs?.Items : [];
658
+ const n = Math.max(0, Math.min(maxLen, arr.length));
659
+ const dyn = {};
660
+ for (let i = 0; i < n; i++)
661
+ dyn[`Item${i}`] = "base.object";
662
+ return { outputs: dyn };
663
+ },
664
+ impl: (ins) => {
665
+ const arr = Array.isArray(ins.Items) ? ins.Items : [];
666
+ const out = {};
667
+ const n = Math.max(0, Math.min(64, arr.length));
668
+ for (let i = 0; i < n; i++)
669
+ out[`Item${i}`] = arr[i];
670
+ return out;
671
+ },
672
+ });
673
+ // Select
674
+ registry.registerNode({
675
+ id: "base.select",
676
+ categoryId: "compute",
677
+ inputs: { Cond: "base.bool", Then: "base.object", Else: "base.object" },
678
+ outputs: { Result: "base.object" },
679
+ impl: (ins) => ({
680
+ Result: ins.Cond ? ins.Then : ins.Else,
681
+ }),
682
+ });
683
+ // Combine XYZ
684
+ registry.registerNode({
685
+ id: "base.compareXYZ",
686
+ categoryId: "compute",
687
+ inputs: { X: "base.float[]", Y: "base.float[]", Z: "base.float[]" },
688
+ outputs: { XYZ: "base.vec3[]" },
689
+ impl: (ins) => {
690
+ const [x, y] = broadcast(ins.X, ins.Y);
691
+ const [xx, z] = broadcast(x, ins.Z);
692
+ const len = Math.max(xx.length, z.length);
693
+ const out = new Array(len)
694
+ .fill(0)
695
+ .map((_, i) => [Number(xx[i] ?? 0), Number(y[i] ?? 0), Number(z[i] ?? 0)]);
696
+ return { XYZ: out };
697
+ },
698
+ });
699
+ // Separate XYZ
700
+ registry.registerNode({
701
+ id: "base.separateXYZ",
702
+ categoryId: "compute",
703
+ inputs: { XYZ: "base.vec3[]" },
704
+ outputs: { X: "base.float[]", Y: "base.float[]", Z: "base.float[]" },
705
+ impl: (ins) => {
706
+ const arr = ins.XYZ ?? [];
707
+ const X = arr.map((v) => Number(v?.[0] ?? 0));
708
+ const Y = arr.map((v) => Number(v?.[1] ?? 0));
709
+ const Z = arr.map((v) => Number(v?.[2] ?? 0));
710
+ return { X, Y, Z };
711
+ },
712
+ });
713
+ // Indices
714
+ registry.registerNode({
715
+ id: "base.indices",
716
+ categoryId: "compute",
717
+ inputs: { Domain: "base.float" },
718
+ outputs: { Indices: "base.float[]" },
719
+ impl: (ins) => {
720
+ const n = Math.trunc(ins.Domain);
721
+ return { Indices: Array.from({ length: n }, (_, i) => i) };
722
+ },
723
+ });
724
+ // Random Numbers
725
+ registry.registerNode({
726
+ id: "base.randomNumbers",
727
+ categoryId: "compute",
728
+ inputs: {
729
+ Domain: "base.float",
730
+ Min: "base.float",
731
+ Max: "base.float",
732
+ Seed: "base.float",
733
+ },
734
+ outputs: { Values: "base.float[]" },
735
+ impl: (ins) => {
736
+ const len = Math.trunc(ins.Domain);
737
+ const min = Number(ins.Min ?? 0);
738
+ const max = Number(ins.Max ?? 1);
739
+ const rng = lcg(Number(ins.Seed ?? 1));
740
+ const out = Array.from({ length: len }, () => min + rng() * (max - min));
741
+ return { Values: out };
742
+ },
743
+ });
744
+ // Random Vectors
745
+ registry.registerNode({
746
+ id: "base.randomXYZs",
747
+ categoryId: "compute",
748
+ inputs: {
749
+ Domain: "base.float",
750
+ Min: "base.vec3",
751
+ Max: "base.vec3",
752
+ Seed: "base.float",
753
+ },
754
+ outputs: { Values: "base.vec3[]" },
755
+ // Registry-level defaults for convenience
756
+ inputDefaults: { Domain: 10, Min: [0, 0, 0], Max: [1, 1, 1], Seed: 1 },
757
+ impl: (ins) => {
758
+ const len = Math.trunc(ins.Domain);
759
+ const min = ins.Min ?? [0, 0, 0];
760
+ const max = ins.Max ?? [1, 1, 1];
761
+ const rng = lcg(Number(ins.Seed ?? 1));
762
+ const out = Array.from({ length: len }, () => [
763
+ min[0] + rng() * (max[0] - min[0]),
764
+ min[1] + rng() * (max[1] - min[1]),
765
+ min[2] + rng() * (max[2] - min[2]),
766
+ ]);
767
+ return { Values: out };
768
+ },
769
+ });
770
+ // Timer
771
+ registry.registerNode({
772
+ id: "base.timer",
773
+ categoryId: "compute",
774
+ inputs: {
775
+ Enabled: "base.bool",
776
+ IntervalMs: "base.float",
777
+ Immediate: "base.bool",
778
+ },
779
+ outputs: { Now: "base.float", Count: "base.float" },
780
+ inputDefaults: { Enabled: true, IntervalMs: 1000, Immediate: true },
781
+ impl: (ins, ctx) => {
782
+ const enabled = Boolean(ins.Enabled);
783
+ const intervalMs = Math.max(1, Math.trunc(Number(ins.IntervalMs ?? 1000)));
784
+ const immediate = Boolean(ins.Immediate);
785
+ const stop = () => {
786
+ const id = ctx.state.timerId;
787
+ if (id !== undefined) {
788
+ clearInterval(id);
789
+ ctx.setState({ timerId: undefined });
790
+ }
791
+ };
792
+ if (!enabled) {
793
+ stop();
794
+ return;
795
+ }
796
+ // restart timer with new settings
797
+ stop();
798
+ let count = 0;
799
+ if (immediate) {
800
+ ctx.emit("Now", Date.now());
801
+ ctx.emit("Count", count);
802
+ count += 1;
803
+ }
804
+ const id = setInterval(() => {
805
+ ctx.emit("Now", Date.now());
806
+ ctx.emit("Count", count);
807
+ count += 1;
808
+ }, intervalMs);
809
+ ctx.setState({ timerId: id });
810
+ },
811
+ lifecycle: {
812
+ dispose: (ctx) => {
813
+ const id = ctx.state.timerId;
814
+ if (id !== undefined) {
815
+ clearInterval(id);
816
+ ctx.setState({ timerId: undefined });
817
+ }
818
+ },
819
+ },
820
+ });
821
+ // ------------------------- JSON/object utilities -------------------------
822
+ registry.registerNode({
823
+ id: "base.object.get",
824
+ categoryId: "compute",
825
+ inputs: { Object: "base.object", Pointers: "base.string[]" },
826
+ outputs: { Values: "base.object" },
827
+ impl: (ins) => {
828
+ const obj = ins.Object;
829
+ const pointers = (ins.Pointers || []).map(String);
830
+ const out = {};
831
+ for (const p of pointers)
832
+ out[p] = jsonPointerGet(obj, p);
833
+ return { Values: out };
834
+ },
835
+ });
836
+ registry.registerNode({
837
+ id: "base.object.set",
838
+ categoryId: "compute",
839
+ inputs: {
840
+ Object: "base.object",
841
+ Pointers: "base.string[]",
842
+ NewValues: "base.object",
843
+ },
844
+ outputs: { Result: "base.object" },
845
+ impl: (ins) => {
846
+ const pointers = (ins.Pointers || []).map(String);
847
+ const values = (ins.NewValues || []).map(String);
848
+ let cur = structuredClone(ins.Object);
849
+ for (let i = 0; i < pointers.length; i++) {
850
+ const p = pointers[i];
851
+ const raw = values[i];
852
+ let val = raw;
853
+ if (typeof raw === "string") {
854
+ try {
855
+ val = JSON.parse(raw);
856
+ }
857
+ catch {
858
+ /* keep as string */
859
+ }
860
+ }
861
+ cur = jsonPointerSet(cur, p, val);
862
+ }
863
+ return { Result: cur };
864
+ },
865
+ });
866
+ registry.registerNode({
867
+ id: "base.object.remove",
868
+ categoryId: "compute",
869
+ inputs: { Object: "base.object", Pointers: "base.string[]" },
870
+ outputs: { Result: "base.object" },
871
+ impl: (ins) => {
872
+ const pointers = (ins.Pointers || []).map(String);
873
+ let cur = structuredClone(ins.Object);
874
+ for (const p of pointers)
875
+ cur = jsonPointerRemove(cur, p);
876
+ return { Result: cur };
877
+ },
878
+ });
879
+ registry.registerNode({
880
+ id: "base.object.merge",
881
+ categoryId: "compute",
882
+ inputs: { A: "base.object", B: "base.object" },
883
+ outputs: { Result: "base.object" },
884
+ impl: (ins) => ({
885
+ Result: deepMerge(ins.A, ins.B),
886
+ }),
887
+ });
888
+ registry.registerNode({
889
+ id: "base.object.keys",
890
+ categoryId: "compute",
891
+ inputs: { Object: "base.object" },
892
+ outputs: { Keys: "base.string[]" },
893
+ impl: (ins) => {
894
+ const obj = ins.Object;
895
+ const keys = isPlainObject(obj) ? Object.keys(obj) : Array.isArray(obj) ? Object.keys(obj) : [];
896
+ return { Keys: keys };
897
+ },
898
+ });
899
+ registry.registerNode({
900
+ id: "base.object.values",
901
+ categoryId: "compute",
902
+ inputs: { Object: "base.object" },
903
+ outputs: { Values: "base.object" },
904
+ impl: (ins) => {
905
+ const obj = ins.Object;
906
+ const vals = isPlainObject(obj) ? Object.values(obj) : Array.isArray(obj) ? Object.values(obj) : [];
907
+ return { Values: vals };
908
+ },
909
+ });
910
+ registry.registerNode({
911
+ id: "base.object.patch",
912
+ categoryId: "compute",
913
+ inputs: { Object: "base.object", Ops: "base.object" },
914
+ outputs: { Result: "base.object" },
915
+ impl: (ins) => {
916
+ const root = structuredClone(ins.Object);
917
+ const opsRaw = ins.Ops;
918
+ const ops = Array.isArray(opsRaw) ? opsRaw : opsRaw ? [opsRaw] : [];
919
+ let cur = root;
920
+ for (const op of ops) {
921
+ if (!op || typeof op !== "object")
922
+ continue;
923
+ const kind = String(op.op || "");
924
+ const path = String(op.path || "");
925
+ if (kind === "add" || kind === "replace") {
926
+ cur = jsonPointerSet(cur, path, op.value);
927
+ }
928
+ else if (kind === "remove") {
929
+ cur = jsonPointerRemove(cur, path);
930
+ }
931
+ else if (kind === "test") {
932
+ const got = jsonPointerGet(cur, path);
933
+ const expected = op.value;
934
+ const ok = JSON.stringify(got) === JSON.stringify(expected);
935
+ if (!ok)
936
+ throw new Error(`objectPatch test failed at ${path}`);
937
+ }
938
+ }
939
+ return { Result: cur };
940
+ },
941
+ });
942
+ registry.registerNode({
943
+ id: "base.array.concat",
944
+ categoryId: "compute",
945
+ inputs: { A: "base.object", B: "base.object" },
946
+ outputs: { Result: "base.object" },
947
+ impl: (ins) => {
948
+ const a = Array.isArray(ins.A) ? ins.A : [];
949
+ const b = Array.isArray(ins.B) ? ins.B : [];
950
+ return { Result: [...a, ...b] };
951
+ },
952
+ });
953
+ registry.registerNode({
954
+ id: "base.array.flatten",
955
+ categoryId: "compute",
956
+ inputs: { Objects: "base.object" },
957
+ outputs: { Result: "base.object" },
958
+ impl: (ins) => {
959
+ const arr = Array.isArray(ins.Objects) ? ins.Objects : [];
960
+ const out = [];
961
+ for (const v of arr) {
962
+ if (Array.isArray(v))
963
+ out.push(...v);
964
+ else
965
+ out.push(v);
966
+ }
967
+ return { Result: out };
968
+ },
969
+ });
970
+ registry.registerNode({
971
+ id: "base.array.sortBy",
972
+ categoryId: "compute",
973
+ inputs: { Objects: "base.object", Pointers: "base.string[]" },
974
+ outputs: { Result: "base.object" },
975
+ impl: (ins) => {
976
+ const arr = Array.isArray(ins.Objects) ? ins.Objects : [];
977
+ const pointers = (ins.Pointers || []).map(String);
978
+ const out = [...arr].sort((a, b) => {
979
+ for (const p of pointers) {
980
+ const av = jsonPointerGet(a, p);
981
+ const bv = jsonPointerGet(b, p);
982
+ if (av === bv)
983
+ continue;
984
+ if (av === undefined)
985
+ return 1;
986
+ if (bv === undefined)
987
+ return -1;
988
+ if (av < bv)
989
+ return -1;
990
+ if (av > bv)
991
+ return 1;
992
+ }
993
+ return 0;
994
+ });
995
+ return { Result: out };
996
+ },
997
+ });
998
+ return registry;
999
+ }
1000
+ export function registerDelayNode(registry) {
1001
+ registry.registerNode({
1002
+ id: "async.delay",
1003
+ categoryId: "compute",
1004
+ inputs: { Value: "base.float", DelayMs: "base.float" },
1005
+ outputs: { Output: "base.float" },
1006
+ impl: async (ins, ctx) => {
1007
+ const ms = Number(ins.DelayMs ?? 200);
1008
+ const valueRaw = ins.Value;
1009
+ if (valueRaw === undefined || valueRaw === null || Number.isNaN(Number(valueRaw))) {
1010
+ return; // wait until x is present to avoid NaN emissions
1011
+ }
1012
+ await new Promise((resolve, reject) => {
1013
+ const id = setTimeout(resolve, ms);
1014
+ const onAbort = () => {
1015
+ clearTimeout(id);
1016
+ reject(new DOMException("Aborted", "AbortError"));
1017
+ };
1018
+ if (ctx.abortSignal.aborted)
1019
+ return onAbort();
1020
+ ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
1021
+ });
1022
+ return { Output: Number(valueRaw) };
1023
+ },
1024
+ });
1025
+ }
1026
+ function sleepWithAbort(ms, signal) {
1027
+ return new Promise((resolve, reject) => {
1028
+ const id = setTimeout(() => {
1029
+ cleanup();
1030
+ resolve();
1031
+ }, ms);
1032
+ const onAbort = () => {
1033
+ clearTimeout(id);
1034
+ cleanup();
1035
+ reject(new DOMException("Aborted", "AbortError"));
1036
+ };
1037
+ const cleanup = () => {
1038
+ signal.removeEventListener("abort", onAbort);
1039
+ };
1040
+ if (signal.aborted)
1041
+ return onAbort();
1042
+ signal.addEventListener("abort", onAbort);
1043
+ });
1044
+ }
1045
+ export function registerProgressNodes(registry) {
1046
+ registry.registerNode({
1047
+ id: "async.progress",
1048
+ categoryId: "compute",
1049
+ inputs: {
1050
+ Steps: "base.float",
1051
+ DelayMs: "base.float",
1052
+ ShouldError: "base.bool",
1053
+ },
1054
+ outputs: { Done: "base.string" },
1055
+ impl: async (ins, ctx) => {
1056
+ const steps = Math.max(1, Math.trunc(Number(ins.Steps ?? 10)));
1057
+ const delayMs = Math.max(0, Math.trunc(Number(ins.DelayMs ?? 50)));
1058
+ const shouldError = Boolean(ins.ShouldError);
1059
+ for (let i = 0; i < steps; i++) {
1060
+ ctx.reportProgress?.(i / steps);
1061
+ await sleepWithAbort(delayMs, ctx.abortSignal);
1062
+ if (shouldError && i >= Math.floor(steps * 0.7)) {
1063
+ ctx.reportProgress?.(i / steps);
1064
+ throw new Error("progressWorker: simulated failure at 70% progress");
1065
+ }
1066
+ }
1067
+ ctx.reportProgress?.(1);
1068
+ return { Done: `Completed ${steps} steps` };
1069
+ },
1070
+ });
1071
+ }
1072
+ export function createRuntime(registry, def, opts) {
1073
+ const builder = new GraphBuilder(registry);
1074
+ const report = builder.validate(def);
1075
+ if (!report.ok)
1076
+ throw new Error("Validation failed: " + report.issues.map((i) => `${i.code}:${i.message}`).join(", "));
1077
+ return builder.build(def, opts);
1078
+ }
1079
+ export function generateId(prefix, used = new Set()) {
1080
+ let id;
1081
+ let attempts = 0;
1082
+ do {
1083
+ id = `${prefix}${Math.random().toString(36).slice(2, 8)}`;
1084
+ attempts++;
1085
+ if (attempts > 1000) {
1086
+ id = `${prefix}${Date.now().toString(36)}${Math.random().toString(36).slice(2, 4)}`;
1087
+ }
1088
+ } while (used.has(id));
1089
+ return id;
1090
+ }
1091
+ //# sourceMappingURL=base.js.map