@bian-womp/spark-graph 0.1.8 → 0.1.9

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.
@@ -1,5 +1,14 @@
1
1
  import type { DataTypeDescriptor, NodeContext, NodeTypeDescriptor } from "../core/types";
2
2
  import type { NodeCategoryDescriptor } from "../core/categories";
3
+ export interface ResolvedCoercionSync {
4
+ kind: "sync";
5
+ convert: (value: unknown) => unknown;
6
+ }
7
+ export interface ResolvedCoercionAsync {
8
+ kind: "async";
9
+ convertAsync: (value: unknown, signal: AbortSignal) => Promise<unknown>;
10
+ }
11
+ export type ResolvedCoercion = ResolvedCoercionSync | ResolvedCoercionAsync;
3
12
  export declare class CategoryRegistry {
4
13
  private categories;
5
14
  register<Impl, State>(cat: NodeCategoryDescriptor<Impl, State>): this;
@@ -24,6 +33,7 @@ export declare class Registry {
24
33
  }>;
25
34
  private coercions;
26
35
  private asyncCoercions;
36
+ private resolvedCache;
27
37
  registerType(desc: DataTypeDescriptor, opts?: {
28
38
  withArray?: boolean;
29
39
  arrayId?: string;
@@ -34,8 +44,12 @@ export declare class Registry {
34
44
  serialize: (value: unknown) => unknown;
35
45
  deserialize: (data: unknown) => unknown;
36
46
  }): this;
37
- registerCoercion(fromTypeId: string, toTypeId: string, convert: (value: unknown) => unknown): this;
38
- registerAsyncCoercion(fromTypeId: string, toTypeId: string, convertAsync: (value: unknown, signal: AbortSignal) => Promise<unknown>): this;
47
+ registerCoercion(fromTypeId: string, toTypeId: string, convert: (value: unknown) => unknown, opts?: {
48
+ nonTransitive?: boolean;
49
+ }): this;
50
+ registerAsyncCoercion(fromTypeId: string, toTypeId: string, convertAsync: (value: unknown, signal: AbortSignal) => Promise<unknown>, opts?: {
51
+ nonTransitive?: boolean;
52
+ }): this;
39
53
  canCoerce(fromTypeId: string | undefined, toTypeId: string | undefined): boolean;
40
54
  getCoercion(fromTypeId: string, toTypeId: string): ((value: unknown) => unknown) | undefined;
41
55
  getAsyncCoercion(fromTypeId: string, toTypeId: string): ((value: unknown, signal: AbortSignal) => Promise<unknown>) | undefined;
@@ -43,7 +57,9 @@ export declare class Registry {
43
57
  from: string;
44
58
  to: string;
45
59
  async: boolean;
60
+ nonTransitive: boolean;
46
61
  }>;
62
+ resolveCoercion(fromTypeId: string, toTypeId: string): ResolvedCoercion | undefined;
47
63
  registerEnum(desc: {
48
64
  id: string;
49
65
  displayName?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"Registry.d.ts","sourceRoot":"","sources":["../../../../src/builder/Registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAClB,WAAW,EACX,kBAAkB,EACnB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAEjE,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,UAAU,CAAuD;IACzE,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,sBAAsB,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,IAAI;IAIrE,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,sBAAsB,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,SAAS;IAG7D,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;CAGzB;AAED,qBAAa,QAAQ;IACnB,QAAQ,CAAC,KAAK,uCAA8C;IAC5D,QAAQ,CAAC,KAAK,qOAAyC;IACvD,QAAQ,CAAC,KAAK;iBAGD,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC;sBAClC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;sBACnB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;OAEjC;IACJ,QAAQ,CAAC,UAAU,mBAA0B;IAC7C,QAAQ,CAAC,WAAW;mBAGL,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO;qBACzB,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO;OAEvC;IAEJ,OAAO,CAAC,SAAS,CAAkD;IACnE,OAAO,CAAC,cAAc,CAGlB;IAEJ,YAAY,CACV,IAAI,EAAE,kBAAkB,EACxB,IAAI,CAAC,EAAE;QACL,SAAS,CAAC,EAAE,OAAO,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,qBAAqB,CAAC,EAAE,OAAO,CAAC;KACjC,GACA,IAAI;IAyCP,YAAY,CACV,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAChC,KAAK,GAAG,OAAO,EACf,IAAI,GAAG,CACL,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,GAAG,EAAE,WAAW,CAAC,KAAK,CAAC,KAErB,IAAI,GACJ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACvB,OAAO,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC3C,IAAI,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,GAAG,IAAI;IAKpD,kBAAkB,CAChB,MAAM,EAAE,MAAM,EACd,CAAC,EAAE;QACD,SAAS,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;QACvC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC;KACzC,GACA,IAAI;IAiBP,gBAAgB,CACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,GACnC,IAAI;IAMP,qBAAqB,CACnB,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,GACtE,IAAI;IAKP,SAAS,CACP,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,GAC3B,OAAO;IAOV,WAAW,CACT,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,SAAS;IAK5C,gBAAgB,CACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,CAAC,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,SAAS;IAM1E,aAAa,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;IAcpE,YAAY,CAAC,IAAI,EAAE;QACjB,EAAE,EAAE,MAAM,CAAC;QACX,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,EAAE,KAAK,CAAC;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QACjD,IAAI,CAAC,EAAE;YACL,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,SAAS,CAAC,EAAE,OAAO,CAAC;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,qBAAqB,CAAC,EAAE,OAAO,CAAC;SACjC,CAAC;KACH,GAAG,IAAI;CA0DT"}
1
+ {"version":3,"file":"Registry.d.ts","sourceRoot":"","sources":["../../../../src/builder/Registry.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/examples/shared.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAGV,eAAe,EAChB,MAAM,eAAe,CAAC;AAGvB,wBAAgB,uBAAuB,IAAI,QAAQ,CAoYlD;AAED,wBAAgB,wBAAwB,IAAI,eAAe,CAc1D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,QA2BnD;AAqBD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,QA6BvD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,6BAUrE"}
1
+ {"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../../../src/examples/shared.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,KAAK,EAAoB,eAAe,EAAE,MAAM,eAAe,CAAC;AA2BvE,wBAAgB,uBAAuB,IAAI,QAAQ,CA+WlD;AAED,wBAAgB,wBAAwB,IAAI,eAAe,CA8B1D;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,QA2BnD;AAqBD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,QA6BvD;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,eAAe,6BAUrE"}
@@ -1,4 +1,5 @@
1
- export declare function createSimpleGraphDef(): import("..").GraphDefinition;
1
+ import { GraphDefinition } from "../core/types";
2
+ export declare function createSimpleGraphDef(): GraphDefinition;
2
3
  export declare function createSimpleGraphRegistry(): import("..").Registry;
3
4
  export declare function main(): Promise<void>;
4
5
  //# sourceMappingURL=simple.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"simple.d.ts","sourceRoot":"","sources":["../../../../src/examples/simple.ts"],"names":[],"mappings":"AAOA,wBAAgB,oBAAoB,iCAEnC;AAED,wBAAgB,yBAAyB,0BAExC;AAGD,wBAAsB,IAAI,kBAqBzB"}
1
+ {"version":3,"file":"simple.d.ts","sourceRoot":"","sources":["../../../../src/examples/simple.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAoChD,wBAAgB,oBAAoB,oBAEnC;AAED,wBAAgB,yBAAyB,0BAExC;AAGD,wBAAsB,IAAI,kBAsBzB"}
package/lib/esm/index.js CHANGED
@@ -22,9 +22,12 @@ class Registry {
22
22
  this.serializers = new Map();
23
23
  this.coercions = new Map();
24
24
  this.asyncCoercions = new Map();
25
+ this.resolvedCache = new Map();
25
26
  }
26
27
  registerType(desc, opts) {
27
28
  this.types.set(desc.id, desc);
29
+ // Any structural change invalidates resolution cache
30
+ this.resolvedCache.clear();
28
31
  if (!this.serializers.has(desc.id)) {
29
32
  this.registerSerializer(desc.id, {
30
33
  serialize: (v) => v,
@@ -81,46 +84,231 @@ class Registry {
81
84
  return this;
82
85
  }
83
86
  // Register a type coercion from one type id to another
84
- registerCoercion(fromTypeId, toTypeId, convert) {
85
- this.coercions.set(`${fromTypeId}->${toTypeId}`, convert);
87
+ registerCoercion(fromTypeId, toTypeId, convert, opts) {
88
+ this.coercions.set(`${fromTypeId}->${toTypeId}`, {
89
+ convert,
90
+ nonTransitive: !!opts?.nonTransitive,
91
+ });
92
+ // If both source and target have array variants, add derived array->array coercion
93
+ const fromArr = `${fromTypeId}[]`;
94
+ const toArr = `${toTypeId}[]`;
95
+ const arrKey = `${fromArr}->${toArr}`;
96
+ if (this.types.has(fromArr) && this.types.has(toArr)) {
97
+ if (!this.coercions.has(arrKey) && !this.asyncCoercions.has(arrKey)) {
98
+ this.coercions.set(arrKey, {
99
+ convert: (value) => {
100
+ if (Array.isArray(value))
101
+ return value.map((v) => convert(v));
102
+ // Best-effort: coerce single to array-of-single
103
+ return [convert(value)];
104
+ },
105
+ nonTransitive: !!opts?.nonTransitive,
106
+ });
107
+ }
108
+ }
109
+ this.resolvedCache.clear();
86
110
  return this;
87
111
  }
88
112
  // Register an async type coercion from one type id to another
89
- registerAsyncCoercion(fromTypeId, toTypeId, convertAsync) {
90
- this.asyncCoercions.set(`${fromTypeId}->${toTypeId}`, convertAsync);
113
+ registerAsyncCoercion(fromTypeId, toTypeId, convertAsync, opts) {
114
+ this.asyncCoercions.set(`${fromTypeId}->${toTypeId}`, {
115
+ convertAsync,
116
+ nonTransitive: !!opts?.nonTransitive,
117
+ });
118
+ // If both source and target have array variants, add derived array->array async coercion
119
+ const fromArr = `${fromTypeId}[]`;
120
+ const toArr = `${toTypeId}[]`;
121
+ const arrKey = `${fromArr}->${toArr}`;
122
+ if (this.types.has(fromArr) && this.types.has(toArr)) {
123
+ if (!this.coercions.has(arrKey) && !this.asyncCoercions.has(arrKey)) {
124
+ this.asyncCoercions.set(arrKey, {
125
+ convertAsync: async (value, signal) => {
126
+ if (Array.isArray(value)) {
127
+ const out = [];
128
+ for (let i = 0; i < value.length; i++) {
129
+ if (signal?.aborted)
130
+ throw signal.reason ?? new Error("aborted");
131
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
132
+ out.push(await convertAsync(value[i], signal));
133
+ }
134
+ return out;
135
+ }
136
+ return [await convertAsync(value, signal)];
137
+ },
138
+ nonTransitive: !!opts?.nonTransitive,
139
+ });
140
+ }
141
+ }
142
+ this.resolvedCache.clear();
91
143
  return this;
92
144
  }
93
145
  canCoerce(fromTypeId, toTypeId) {
94
146
  if (!fromTypeId || !toTypeId)
95
147
  return false;
96
- if (fromTypeId === toTypeId)
97
- return true;
98
- const key = `${fromTypeId}->${toTypeId}`;
99
- return this.coercions.has(key) || this.asyncCoercions.has(key);
148
+ return !!this.resolveCoercion(fromTypeId, toTypeId);
100
149
  }
101
150
  getCoercion(fromTypeId, toTypeId) {
102
- if (fromTypeId === toTypeId)
103
- return (v) => v;
104
- return this.coercions.get(`${fromTypeId}->${toTypeId}`);
151
+ const resolved = this.resolveCoercion(fromTypeId, toTypeId);
152
+ if (!resolved)
153
+ return undefined;
154
+ if (resolved.kind === "sync")
155
+ return resolved.convert;
156
+ return undefined;
105
157
  }
106
158
  getAsyncCoercion(fromTypeId, toTypeId) {
107
- if (fromTypeId === toTypeId)
159
+ const resolved = this.resolveCoercion(fromTypeId, toTypeId);
160
+ if (!resolved)
108
161
  return undefined;
109
- return this.asyncCoercions.get(`${fromTypeId}->${toTypeId}`);
162
+ if (resolved.kind === "async")
163
+ return resolved.convertAsync;
164
+ return undefined;
110
165
  }
111
166
  // Introspection for dynamic discovery
112
167
  listCoercions() {
113
168
  const out = [];
114
- for (const key of this.coercions.keys()) {
169
+ for (const [key, rec] of this.coercions.entries()) {
115
170
  const [from, to] = key.split("->");
116
- out.push({ from, to, async: false });
171
+ out.push({
172
+ from,
173
+ to,
174
+ async: false,
175
+ nonTransitive: rec.nonTransitive,
176
+ });
117
177
  }
118
- for (const key of this.asyncCoercions.keys()) {
178
+ for (const [key, rec] of this.asyncCoercions.entries()) {
119
179
  const [from, to] = key.split("->");
120
- out.push({ from, to, async: true });
180
+ out.push({ from, to, async: true, nonTransitive: rec.nonTransitive });
121
181
  }
122
182
  return out;
123
183
  }
184
+ resolveCoercion(fromTypeId, toTypeId) {
185
+ const cacheKey = `${fromTypeId}->${toTypeId}`;
186
+ const cached = this.resolvedCache.get(cacheKey);
187
+ if (cached)
188
+ return cached;
189
+ if (fromTypeId === toTypeId) {
190
+ const res = { kind: "sync", convert: (v) => v };
191
+ this.resolvedCache.set(cacheKey, res);
192
+ return res;
193
+ }
194
+ // Direct edges (regardless of nonTransitive)
195
+ const directSync = this.coercions.get(cacheKey);
196
+ if (directSync) {
197
+ const res = {
198
+ kind: "sync",
199
+ convert: directSync.convert,
200
+ };
201
+ this.resolvedCache.set(cacheKey, res);
202
+ return res;
203
+ }
204
+ const directAsync = this.asyncCoercions.get(cacheKey);
205
+ if (directAsync) {
206
+ const res = {
207
+ kind: "async",
208
+ convertAsync: directAsync.convertAsync,
209
+ };
210
+ this.resolvedCache.set(cacheKey, res);
211
+ return res;
212
+ }
213
+ // Build adjacency from transitive-allowed edges only
214
+ const getNeighbors = (from) => {
215
+ const out = [];
216
+ for (const [key, rec] of this.coercions.entries()) {
217
+ if (rec.nonTransitive)
218
+ continue;
219
+ const [src, dst] = key.split("->");
220
+ if (src === from)
221
+ out.push({ from: src, to: dst, kind: "sync", convert: rec.convert });
222
+ }
223
+ for (const [key, rec] of this.asyncCoercions.entries()) {
224
+ if (rec.nonTransitive)
225
+ continue;
226
+ const [src, dst] = key.split("->");
227
+ if (src === from)
228
+ out.push({
229
+ from: src,
230
+ to: dst,
231
+ kind: "async",
232
+ convertAsync: rec.convertAsync,
233
+ });
234
+ }
235
+ return out;
236
+ };
237
+ const betterOf = (a, b) => {
238
+ if (!a)
239
+ return true;
240
+ if (b.edges !== a.edges)
241
+ return b.edges < a.edges;
242
+ return b.async < a.async;
243
+ };
244
+ const best = new Map();
245
+ const queue = [];
246
+ const push = (e) => {
247
+ // simple insertion to keep queue roughly ordered by cost
248
+ let i = 0;
249
+ while (i < queue.length) {
250
+ const q = queue[i];
251
+ if (e.cost.edges < q.cost.edges ||
252
+ (e.cost.edges === q.cost.edges && e.cost.async < q.cost.async)) {
253
+ break;
254
+ }
255
+ i++;
256
+ }
257
+ queue.splice(i, 0, e);
258
+ };
259
+ push({ node: fromTypeId, cost: { edges: 0, async: 0 }, path: [] });
260
+ best.set(fromTypeId, { edges: 0, async: 0 });
261
+ while (queue.length > 0) {
262
+ const cur = queue.shift();
263
+ if (cur.node === toTypeId) {
264
+ // Compose
265
+ const hasAsync = cur.path.some((s) => s.kind === "async");
266
+ if (!hasAsync) {
267
+ const convert = (value) => {
268
+ let acc = value;
269
+ for (const step of cur.path) {
270
+ // all sync by construction
271
+ acc = step.convert(acc);
272
+ }
273
+ return acc;
274
+ };
275
+ const res = { kind: "sync", convert };
276
+ this.resolvedCache.set(cacheKey, res);
277
+ return res;
278
+ }
279
+ else {
280
+ const convertAsync = async (value, signal) => {
281
+ let acc = value;
282
+ for (const step of cur.path) {
283
+ if (step.kind === "sync") {
284
+ acc = step.convert(acc);
285
+ }
286
+ else {
287
+ acc = await step.convertAsync(acc, signal);
288
+ }
289
+ }
290
+ return acc;
291
+ };
292
+ const res = { kind: "async", convertAsync };
293
+ this.resolvedCache.set(cacheKey, res);
294
+ return res;
295
+ }
296
+ }
297
+ // expand neighbors
298
+ for (const step of getNeighbors(cur.node)) {
299
+ const nextCost = {
300
+ edges: cur.cost.edges + 1,
301
+ async: cur.cost.async + (step.kind === "async" ? 1 : 0),
302
+ };
303
+ const prev = best.get(step.to);
304
+ if (betterOf(prev, nextCost)) {
305
+ best.set(step.to, nextCost);
306
+ push({ node: step.to, cost: nextCost, path: [...cur.path, step] });
307
+ }
308
+ }
309
+ }
310
+ return undefined;
311
+ }
124
312
  // Enum support
125
313
  registerEnum(desc) {
126
314
  const { id, displayName, options, opts } = desc;
@@ -1329,79 +1517,89 @@ const CompositeCategory = (registry) => ({
1329
1517
  policy: { mode: "hybrid" },
1330
1518
  });
1331
1519
 
1520
+ // Helpers
1521
+ const asArray = (v) => Array.isArray(v) ? v : [Number(v)];
1522
+ const broadcast = (a, b) => {
1523
+ const aa = asArray(a);
1524
+ const bb = asArray(b);
1525
+ if (aa.length === bb.length)
1526
+ return [aa, bb];
1527
+ if (aa.length === 1)
1528
+ return [new Array(bb.length).fill(aa[0]), bb];
1529
+ if (bb.length === 1)
1530
+ return [aa, new Array(aa.length).fill(bb[0])];
1531
+ const len = Math.max(aa.length, bb.length);
1532
+ return [new Array(len).fill(aa[0] ?? 0), new Array(len).fill(bb[0] ?? 0)];
1533
+ };
1534
+ const clamp = (x, min, max) => Math.min(max, Math.max(min, x));
1535
+ const lerp = (a, b, t) => a + (b - a) * t;
1536
+ const lcg = (seed) => {
1537
+ let s = seed >>> 0 || 1;
1538
+ return () => (s = (s * 1664525 + 1013904223) >>> 0) / 0xffffffff;
1539
+ };
1332
1540
  function setupBasicGraphRegistry() {
1333
1541
  const registry = new Registry();
1334
1542
  registry.categories.register(ComputeCategory);
1335
- const floatType = {
1543
+ registry.registerType({
1336
1544
  id: "base.float",
1337
1545
  validate: (v) => typeof v === "number" && !Number.isNaN(v),
1338
- };
1339
- registry.registerType(floatType);
1340
- registry.registerSerializer("base.float", {
1341
- serialize: (v) => v,
1342
- deserialize: (d) => Number(d),
1343
- });
1344
- const boolType = {
1546
+ }, { withArray: true, arrayPickFirstDefined: true });
1547
+ registry.registerType({
1345
1548
  id: "base.bool",
1346
1549
  validate: (v) => typeof v === "boolean",
1347
- };
1348
- const stringType = {
1550
+ }, { withArray: true, arrayPickFirstDefined: true });
1551
+ registry.registerType({
1349
1552
  id: "base.string",
1350
1553
  validate: (v) => typeof v === "string",
1351
- };
1352
- const vec3Type = {
1554
+ }, { withArray: true, arrayPickFirstDefined: true });
1555
+ registry.registerType({
1353
1556
  id: "base.vec3",
1354
1557
  validate: (v) => Array.isArray(v) &&
1355
1558
  v.length === 3 &&
1356
1559
  v.every((x) => typeof x === "number"),
1357
- };
1358
- [boolType, stringType, vec3Type, floatType].forEach((t) => {
1359
- registry.registerType(t, { withArray: true, arrayPickFirstDefined: true });
1360
- registry.registerSerializer(t.id, {
1361
- serialize: (v) => v,
1362
- deserialize: (d) => d,
1363
- });
1560
+ }, { withArray: true, arrayPickFirstDefined: true });
1561
+ // float -> vec3 : map x to [x,0,0]
1562
+ registry.registerCoercion("base.float", "base.vec3", (v) => {
1563
+ return [Number(v) || 0, 0, 0];
1364
1564
  });
1365
- // Helpers
1366
- const asArray = (v) => Array.isArray(v) ? v : [Number(v)];
1367
- // float[] -> vec3[] : map x to [x,0,0]
1368
- registry.registerCoercion("base.float[]", "base.vec3[]", (v) => {
1369
- const arr = asArray(v);
1370
- return arr.map((x) => [Number(x) || 0, 0, 0]);
1565
+ // Async coercion variant for vec3 -> float (chunked + abortable)
1566
+ registry.registerAsyncCoercion("base.vec3", "base.float", async (value, signal) => {
1567
+ if (signal.aborted)
1568
+ throw new DOMException("Aborted", "AbortError");
1569
+ const v = value;
1570
+ await new Promise((r) => setTimeout(r, 1000));
1571
+ return Math.hypot(Number(v[0] ?? 0), Number(v[1] ?? 0), Number(v[2] ?? 0));
1371
1572
  });
1372
- // Async coercion variant for vec3[] -> float[] (chunked + abortable)
1373
- registry.registerAsyncCoercion("base.vec3[]", "base.float[]", async (value, signal) => {
1374
- const arr = Array.isArray(value)
1375
- ? value
1376
- : [];
1377
- const out = new Array(arr.length);
1378
- for (let i = 0; i < arr.length; i++) {
1379
- if (signal.aborted)
1380
- throw new DOMException("Aborted", "AbortError");
1381
- const v = arr[i] ?? [0, 0, 0];
1382
- await new Promise((r) => setTimeout(r, 1000));
1383
- out[i] = Math.hypot(Number(v[0] ?? 0), Number(v[1] ?? 0), Number(v[2] ?? 0));
1384
- }
1385
- return out;
1573
+ registry.registerCoercion("base.bool", "base.float", (v) => (v ? 1 : 0));
1574
+ registry.registerCoercion("base.float", "base.bool", (v) => !!v);
1575
+ registry.registerCoercion("base.float", "base.string", (v) => String(v));
1576
+ registry.registerCoercion("base.string", "base.float", (v) => Number(v));
1577
+ // Enums: Math Operation
1578
+ registry.registerEnum({
1579
+ id: "base.enum:math.operation",
1580
+ options: [
1581
+ { value: 0, label: "Add" },
1582
+ { value: 1, label: "Subtract" },
1583
+ { value: 2, label: "Multiply" },
1584
+ { value: 3, label: "Divide" },
1585
+ { value: 4, label: "Min" },
1586
+ { value: 5, label: "Max" },
1587
+ { value: 6, label: "Modulo" },
1588
+ { value: 7, label: "Power" },
1589
+ ],
1590
+ });
1591
+ // Enums: Compare Operation
1592
+ registry.registerEnum({
1593
+ id: "base.enum:compare.operation",
1594
+ options: [
1595
+ { value: 0, label: "LessThan" },
1596
+ { value: 1, label: "LessThanOrEqual" },
1597
+ { value: 2, label: "GreaterThan" },
1598
+ { value: 3, label: "GreaterThanOrEqual" },
1599
+ { value: 4, label: "Equal" },
1600
+ { value: 5, label: "NotEqual" },
1601
+ ],
1386
1602
  });
1387
- const broadcast = (a, b) => {
1388
- const aa = asArray(a);
1389
- const bb = asArray(b);
1390
- if (aa.length === bb.length)
1391
- return [aa, bb];
1392
- if (aa.length === 1)
1393
- return [new Array(bb.length).fill(aa[0]), bb];
1394
- if (bb.length === 1)
1395
- return [aa, new Array(aa.length).fill(bb[0])];
1396
- const len = Math.max(aa.length, bb.length);
1397
- return [new Array(len).fill(aa[0] ?? 0), new Array(len).fill(bb[0] ?? 0)];
1398
- };
1399
- const clamp = (x, min, max) => Math.min(max, Math.max(min, x));
1400
- const lerp = (a, b, t) => a + (b - a) * t;
1401
- const lcg = (seed) => {
1402
- let s = seed >>> 0 || 1;
1403
- return () => (s = (s * 1664525 + 1013904223) >>> 0) / 0xffffffff;
1404
- };
1405
1603
  // Number
1406
1604
  registry.registerNode({
1407
1605
  id: "base.number",
@@ -1428,32 +1626,6 @@ function setupBasicGraphRegistry() {
1428
1626
  outputs: { Text: "base.string" },
1429
1627
  impl: (ins) => ({ Text: String(ins.Value) }),
1430
1628
  });
1431
- // Enums: Math Operation
1432
- registry.registerEnum({
1433
- id: "base.enum:math.operation",
1434
- options: [
1435
- { value: 0, label: "Add" },
1436
- { value: 1, label: "Subtract" },
1437
- { value: 2, label: "Multiply" },
1438
- { value: 3, label: "Divide" },
1439
- { value: 4, label: "Min" },
1440
- { value: 5, label: "Max" },
1441
- { value: 6, label: "Modulo" },
1442
- { value: 7, label: "Power" },
1443
- ],
1444
- });
1445
- // Enums: Compare Operation
1446
- registry.registerEnum({
1447
- id: "base.enum:compare.operation",
1448
- options: [
1449
- { value: 0, label: "LessThan" },
1450
- { value: 1, label: "LessThanOrEqual" },
1451
- { value: 2, label: "GreaterThan" },
1452
- { value: 3, label: "GreaterThanOrEqual" },
1453
- { value: 4, label: "Equal" },
1454
- { value: 5, label: "NotEqual" },
1455
- ],
1456
- });
1457
1629
  // Clamp
1458
1630
  registry.registerNode({
1459
1631
  id: "base.clamp",
@@ -1665,21 +1837,6 @@ function setupBasicGraphRegistry() {
1665
1837
  });
1666
1838
  return registry;
1667
1839
  }
1668
- function makeBasicGraphDefinition() {
1669
- return {
1670
- nodes: [
1671
- { nodeId: "n1", typeId: "base.math" },
1672
- { nodeId: "n2", typeId: "base.math" },
1673
- ],
1674
- edges: [
1675
- {
1676
- id: "e1",
1677
- source: { nodeId: "n1", handle: "Result" },
1678
- target: { nodeId: "n2", handle: "A" },
1679
- },
1680
- ],
1681
- };
1682
- }
1683
1840
  function registerDelayNode(registry) {
1684
1841
  registry.registerNode({
1685
1842
  id: "async.delay",
@@ -1753,6 +1910,37 @@ function registerProgressNodes(registry) {
1753
1910
  });
1754
1911
  }
1755
1912
 
1913
+ function makeBasicGraphDefinition() {
1914
+ return {
1915
+ nodes: [
1916
+ { nodeId: "n1", typeId: "base.math" },
1917
+ { nodeId: "n2", typeId: "base.math" },
1918
+ // Transitivity demo nodes
1919
+ { nodeId: "n3", typeId: "base.compare" },
1920
+ { nodeId: "n4", typeId: "base.randomXYZs" },
1921
+ ],
1922
+ edges: [
1923
+ {
1924
+ id: "e1",
1925
+ source: { nodeId: "n1", handle: "Result" },
1926
+ target: { nodeId: "n2", handle: "A" },
1927
+ },
1928
+ // Feed n2 result to comparer A
1929
+ {
1930
+ id: "e2",
1931
+ source: { nodeId: "n2", handle: "Result" },
1932
+ target: { nodeId: "n3", handle: "A" },
1933
+ },
1934
+ // Transitive coercion edge: bool[] (n3.Result) -> vec3 (n4.Min)
1935
+ // Path: bool[] -> float[] -> vec3[] -> vec3
1936
+ {
1937
+ id: "e3",
1938
+ source: { nodeId: "n3", handle: "Result" },
1939
+ target: { nodeId: "n4", handle: "Min" },
1940
+ },
1941
+ ],
1942
+ };
1943
+ }
1756
1944
  function createSimpleGraphDef() {
1757
1945
  return makeBasicGraphDefinition();
1758
1946
  }