@bian-womp/spark-graph 0.1.1 → 0.1.3

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.
package/lib/cjs/index.cjs CHANGED
@@ -25,8 +25,44 @@ class Registry {
25
25
  this.asyncCoercions = new Map();
26
26
  this.enums = new Map();
27
27
  }
28
- registerType(desc) {
28
+ registerType(desc, opts) {
29
29
  this.types.set(desc.id, desc);
30
+ if (!this.serializers.has(desc.id)) {
31
+ this.registerSerializer(desc.id, {
32
+ serialize: (v) => v,
33
+ deserialize: (d) => d,
34
+ });
35
+ }
36
+ if (opts?.withArray) {
37
+ const arrayId = opts.arrayId ?? `${desc.id}[]`;
38
+ if (!this.types.has(arrayId)) {
39
+ const arrayDesc = {
40
+ id: arrayId,
41
+ validate: (v) => Array.isArray(v) &&
42
+ v.every((x) => {
43
+ try {
44
+ return desc.validate(x);
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }),
50
+ };
51
+ this.types.set(arrayId, arrayDesc);
52
+ }
53
+ this.registerCoercion(desc.id, arrayId, (value) => Array.isArray(value) ? value : [value]);
54
+ this.registerCoercion(arrayId, desc.id, (value) => {
55
+ if (!Array.isArray(value))
56
+ return value;
57
+ if (opts.arrayPickFirstDefined) {
58
+ for (const x of value)
59
+ if (x !== undefined)
60
+ return x;
61
+ return undefined;
62
+ }
63
+ return value[0];
64
+ });
65
+ }
30
66
  return this;
31
67
  }
32
68
  registerNode(desc) {
@@ -35,6 +71,15 @@ class Registry {
35
71
  }
36
72
  registerSerializer(typeId, s) {
37
73
  this.serializers.set(typeId, s);
74
+ // If an array variant of this type exists and doesn't have a serializer,
75
+ // auto-register an array serializer that maps each element using the base serializer.
76
+ const arrayId = `${typeId}[]`;
77
+ if (this.types.has(arrayId) && !this.serializers.has(arrayId)) {
78
+ this.serializers.set(arrayId, {
79
+ serialize: (value) => Array.isArray(value) ? value.map((el) => s.serialize(el)) : value,
80
+ deserialize: (data) => Array.isArray(data) ? data.map((el) => s.deserialize(el)) : data,
81
+ });
82
+ }
38
83
  return this;
39
84
  }
40
85
  // Register a type coercion from one type id to another
@@ -65,6 +110,19 @@ class Registry {
65
110
  return undefined;
66
111
  return this.asyncCoercions.get(`${fromTypeId}->${toTypeId}`);
67
112
  }
113
+ // Introspection for dynamic discovery
114
+ listCoercions() {
115
+ const out = [];
116
+ for (const key of this.coercions.keys()) {
117
+ const [from, to] = key.split("->");
118
+ out.push({ from, to, async: false });
119
+ }
120
+ for (const key of this.asyncCoercions.keys()) {
121
+ const [from, to] = key.split("->");
122
+ out.push({ from, to, async: true });
123
+ }
124
+ return out;
125
+ }
68
126
  // Enum support
69
127
  registerEnum(enumTypeId, options, labelType, valueType) {
70
128
  const valueToLabel = new Map();
@@ -1282,53 +1340,30 @@ function setupBasicGraphRegistry() {
1282
1340
  const registry = new Registry();
1283
1341
  registry.categories.register(ComputeCategory);
1284
1342
  const floatType = {
1285
- id: "float",
1343
+ id: "base.float",
1286
1344
  validate: (v) => typeof v === "number" && !Number.isNaN(v),
1287
1345
  };
1288
1346
  registry.registerType(floatType);
1289
- registry.registerSerializer("float", {
1347
+ registry.registerSerializer("base.float", {
1290
1348
  serialize: (v) => v,
1291
1349
  deserialize: (d) => Number(d),
1292
1350
  });
1293
1351
  const boolType = {
1294
- id: "bool",
1352
+ id: "base.bool",
1295
1353
  validate: (v) => typeof v === "boolean",
1296
1354
  };
1297
1355
  const stringType = {
1298
- id: "string",
1356
+ id: "base.string",
1299
1357
  validate: (v) => typeof v === "string",
1300
1358
  };
1301
1359
  const vec3Type = {
1302
- id: "vec3",
1360
+ id: "base.vec3",
1303
1361
  validate: (v) => Array.isArray(v) &&
1304
1362
  v.length === 3 &&
1305
1363
  v.every((x) => typeof x === "number"),
1306
1364
  };
1307
- const floatArrayType = {
1308
- id: "float[]",
1309
- validate: (v) => Array.isArray(v) && v.every((x) => typeof x === "number"),
1310
- };
1311
- const boolArrayType = {
1312
- id: "bool[]",
1313
- validate: (v) => Array.isArray(v) && v.every((x) => typeof x === "boolean"),
1314
- };
1315
- const vec3ArrayType = {
1316
- id: "vec3[]",
1317
- validate: (v) => Array.isArray(v) &&
1318
- v.every((x) => Array.isArray(x) &&
1319
- x.length === 3 &&
1320
- x.every((n) => typeof n === "number")),
1321
- };
1322
- [
1323
- boolType,
1324
- stringType,
1325
- vec3Type,
1326
- floatType,
1327
- floatArrayType,
1328
- boolArrayType,
1329
- vec3ArrayType,
1330
- ].forEach((t) => {
1331
- registry.registerType(t);
1365
+ [boolType, stringType, vec3Type, floatType].forEach((t) => {
1366
+ registry.registerType(t, { withArray: true, arrayPickFirstDefined: true });
1332
1367
  registry.registerSerializer(t.id, {
1333
1368
  serialize: (v) => v,
1334
1369
  deserialize: (d) => d,
@@ -1336,23 +1371,13 @@ function setupBasicGraphRegistry() {
1336
1371
  });
1337
1372
  // Helpers
1338
1373
  const asArray = (v) => Array.isArray(v) ? v : [Number(v)];
1339
- // Register core coercions: float <-> float[]
1340
- registry.registerCoercion("float", "float[]", (v) => Array.isArray(v) ? v : [Number(v)]);
1341
- registry.registerCoercion("float[]", "float", (v) => Array.isArray(v) ? Number(v[0] ?? 0) : Number(v));
1342
1374
  // float[] -> vec3[] : map x to [x,0,0]
1343
- registry.registerCoercion("float[]", "vec3[]", (v) => {
1375
+ registry.registerCoercion("base.float[]", "base.vec3[]", (v) => {
1344
1376
  const arr = asArray(v);
1345
1377
  return arr.map((x) => [Number(x) || 0, 0, 0]);
1346
1378
  });
1347
- // Example async coercion: simulate expensive conversion float[] -> vec3[] by computing magnitudes
1348
- registry.registerCoercion("float[]", "vec3[]", (v) => {
1349
- // synchronous fallback; async version can be provided on edge via convertAsync
1350
- if (!Array.isArray(v))
1351
- return [];
1352
- return v.map((t) => Math.hypot(Number(t?.[0] ?? 0), Number(t?.[1] ?? 0), Number(t?.[2] ?? 0)));
1353
- });
1354
1379
  // Async coercion variant for vec3[] -> float[] (chunked + abortable)
1355
- registry.registerAsyncCoercion("vec3[]", "float[]", async (value, signal) => {
1380
+ registry.registerAsyncCoercion("base.vec3[]", "base.float[]", async (value, signal) => {
1356
1381
  const arr = Array.isArray(value)
1357
1382
  ? value
1358
1383
  : [];
@@ -1386,32 +1411,32 @@ function setupBasicGraphRegistry() {
1386
1411
  };
1387
1412
  // Number
1388
1413
  registry.registerNode({
1389
- id: "number",
1414
+ id: "base.number",
1390
1415
  categoryId: "compute",
1391
- inputs: { Value: "float" },
1392
- outputs: { Result: "float" },
1416
+ inputs: { Value: "base.float" },
1417
+ outputs: { Result: "base.float" },
1393
1418
  impl: (ins) => ({ Result: Number(ins.Value) }),
1394
1419
  });
1395
1420
  // Integer
1396
1421
  registry.registerNode({
1397
- id: "integer",
1422
+ id: "base.int",
1398
1423
  categoryId: "compute",
1399
- inputs: { Value: "float" },
1400
- outputs: { Result: "float" },
1424
+ inputs: { Value: "base.float" },
1425
+ outputs: { Result: "base.float" },
1401
1426
  impl: (ins) => ({
1402
1427
  Result: Math.trunc(Number(ins.Value)),
1403
1428
  }),
1404
1429
  });
1405
1430
  // Number to String
1406
1431
  registry.registerNode({
1407
- id: "numberToString",
1432
+ id: "base.numberToString",
1408
1433
  categoryId: "compute",
1409
- inputs: { Value: "float" },
1410
- outputs: { Text: "string" },
1434
+ inputs: { Value: "base.float" },
1435
+ outputs: { Text: "base.string" },
1411
1436
  impl: (ins) => ({ Text: String(ins.Value) }),
1412
1437
  });
1413
1438
  // Enums: Math Operation
1414
- registry.registerEnum("enum:math.operation", [
1439
+ registry.registerEnum("base.enum:math.operation", [
1415
1440
  { value: 0, label: "Add" },
1416
1441
  { value: 1, label: "Subtract" },
1417
1442
  { value: 2, label: "Multiply" },
@@ -1420,22 +1445,22 @@ function setupBasicGraphRegistry() {
1420
1445
  { value: 5, label: "Max" },
1421
1446
  { value: 6, label: "Modulo" },
1422
1447
  { value: 7, label: "Power" },
1423
- ], "string", "float");
1448
+ ], "base.string", "base.float");
1424
1449
  // Enums: Compare Operation
1425
- registry.registerEnum("enum:compare.operation", [
1450
+ registry.registerEnum("base.enum:compare.operation", [
1426
1451
  { value: 0, label: "LessThan" },
1427
1452
  { value: 1, label: "LessThanOrEqual" },
1428
1453
  { value: 2, label: "GreaterThan" },
1429
1454
  { value: 3, label: "GreaterThanOrEqual" },
1430
1455
  { value: 4, label: "Equal" },
1431
1456
  { value: 5, label: "NotEqual" },
1432
- ], "string", "float");
1457
+ ], "base.string", "base.float");
1433
1458
  // Clamp
1434
1459
  registry.registerNode({
1435
- id: "clamp",
1460
+ id: "base.clamp",
1436
1461
  categoryId: "compute",
1437
- inputs: { Value: "float[]", Min: "float", Max: "float" },
1438
- outputs: { Value: "float[]" },
1462
+ inputs: { Value: "base.float[]", Min: "base.float", Max: "base.float" },
1463
+ outputs: { Value: "base.float[]" },
1439
1464
  impl: (ins) => {
1440
1465
  const vals = asArray(ins.Value);
1441
1466
  const min = Number(ins.Min ?? 0);
@@ -1445,10 +1470,14 @@ function setupBasicGraphRegistry() {
1445
1470
  });
1446
1471
  // Interpolate (lerp)
1447
1472
  registry.registerNode({
1448
- id: "interpolate",
1473
+ id: "base.interpolate",
1449
1474
  categoryId: "compute",
1450
- inputs: { ValueA: "float[]", ValueB: "float[]", Factor: "float" },
1451
- outputs: { Value: "float[]" },
1475
+ inputs: {
1476
+ ValueA: "base.float[]",
1477
+ ValueB: "base.float[]",
1478
+ Factor: "base.float",
1479
+ },
1480
+ outputs: { Value: "base.float[]" },
1452
1481
  impl: (ins) => {
1453
1482
  const [a, b] = broadcast(ins.ValueA, ins.ValueB);
1454
1483
  const t = Number(ins.Factor ?? 0);
@@ -1461,18 +1490,18 @@ function setupBasicGraphRegistry() {
1461
1490
  });
1462
1491
  // Map Range (linear)
1463
1492
  registry.registerNode({
1464
- id: "mapRange",
1493
+ id: "base.mapRange",
1465
1494
  categoryId: "compute",
1466
1495
  inputs: {
1467
- Mode: "string",
1468
- Clamp: "bool",
1469
- Value: "float[]",
1470
- FromMin: "float",
1471
- FromMax: "float",
1472
- ToMin: "float",
1473
- ToMax: "float",
1496
+ Mode: "base.string",
1497
+ Clamp: "base.bool",
1498
+ Value: "base.float[]",
1499
+ FromMin: "base.float",
1500
+ FromMax: "base.float",
1501
+ ToMin: "base.float",
1502
+ ToMax: "base.float",
1474
1503
  },
1475
- outputs: { Value: "float[]" },
1504
+ outputs: { Value: "base.float[]" },
1476
1505
  impl: (ins) => {
1477
1506
  const vals = asArray(ins.Value);
1478
1507
  const fromMin = Number(ins.FromMin ?? 0);
@@ -1492,10 +1521,14 @@ function setupBasicGraphRegistry() {
1492
1521
  });
1493
1522
  // Math (subset) - scalar version for simple examples
1494
1523
  registry.registerNode({
1495
- id: "math",
1524
+ id: "base.math",
1496
1525
  categoryId: "compute",
1497
- inputs: { Operation: "enum:math.operation", A: "float[]", B: "float[]" },
1498
- outputs: { Result: "float[]" },
1526
+ inputs: {
1527
+ Operation: "base.enum:math.operation",
1528
+ A: "base.float[]",
1529
+ B: "base.float[]",
1530
+ },
1531
+ outputs: { Result: "base.float[]" },
1499
1532
  impl: (ins) => {
1500
1533
  // Gracefully handle missing inputs by treating them as zeros
1501
1534
  const a = ins.A === undefined ? [] : asArray(ins.A);
@@ -1523,10 +1556,14 @@ function setupBasicGraphRegistry() {
1523
1556
  });
1524
1557
  // Compare
1525
1558
  registry.registerNode({
1526
- id: "compare",
1559
+ id: "base.compare",
1527
1560
  categoryId: "compute",
1528
- inputs: { Operation: "enum:compare.operation", A: "float[]", B: "float[]" },
1529
- outputs: { Result: "bool[]" },
1561
+ inputs: {
1562
+ Operation: "base.enum:compare.operation",
1563
+ A: "base.float[]",
1564
+ B: "base.float[]",
1565
+ },
1566
+ outputs: { Result: "base.bool[]" },
1530
1567
  impl: (ins) => {
1531
1568
  const [a, b] = broadcast(ins.A, ins.B);
1532
1569
  const op = Number(ins.Operation ?? 4) | 0; // default Equal
@@ -1544,10 +1581,10 @@ function setupBasicGraphRegistry() {
1544
1581
  });
1545
1582
  // Combine XYZ
1546
1583
  registry.registerNode({
1547
- id: "combineXYZ",
1584
+ id: "base.compareXYZ",
1548
1585
  categoryId: "compute",
1549
- inputs: { X: "float[]", Y: "float[]", Z: "float[]" },
1550
- outputs: { XYZ: "vec3[]" },
1586
+ inputs: { X: "base.float[]", Y: "base.float[]", Z: "base.float[]" },
1587
+ outputs: { XYZ: "base.vec3[]" },
1551
1588
  impl: (ins) => {
1552
1589
  const [x, y] = broadcast(ins.X, ins.Y);
1553
1590
  const [xx, z] = broadcast(x, ins.Z);
@@ -1560,10 +1597,10 @@ function setupBasicGraphRegistry() {
1560
1597
  });
1561
1598
  // Separate XYZ
1562
1599
  registry.registerNode({
1563
- id: "separateXYZ",
1600
+ id: "base.separateXYZ",
1564
1601
  categoryId: "compute",
1565
- inputs: { XYZ: "vec3[]" },
1566
- outputs: { X: "float[]", Y: "float[]", Z: "float[]" },
1602
+ inputs: { XYZ: "base.vec3[]" },
1603
+ outputs: { X: "base.float[]", Y: "base.float[]", Z: "base.float[]" },
1567
1604
  impl: (ins) => {
1568
1605
  const arr = ins.XYZ ?? [];
1569
1606
  const X = arr.map((v) => Number(v?.[0] ?? 0));
@@ -1574,10 +1611,10 @@ function setupBasicGraphRegistry() {
1574
1611
  });
1575
1612
  // Indices
1576
1613
  registry.registerNode({
1577
- id: "indices",
1614
+ id: "base.indices",
1578
1615
  categoryId: "compute",
1579
- inputs: { Domain: "float" },
1580
- outputs: { Indices: "float[]" },
1616
+ inputs: { Domain: "base.float" },
1617
+ outputs: { Indices: "base.float[]" },
1581
1618
  impl: (ins) => {
1582
1619
  const n = Math.trunc(ins.Domain);
1583
1620
  return { Indices: Array.from({ length: n }, (_, i) => i) };
@@ -1585,10 +1622,15 @@ function setupBasicGraphRegistry() {
1585
1622
  });
1586
1623
  // Random Numbers
1587
1624
  registry.registerNode({
1588
- id: "randomNumbers",
1625
+ id: "base.randomNumbers",
1589
1626
  categoryId: "compute",
1590
- inputs: { Domain: "float", Min: "float", Max: "float", Seed: "float" },
1591
- outputs: { Values: "float[]" },
1627
+ inputs: {
1628
+ Domain: "base.float",
1629
+ Min: "base.float",
1630
+ Max: "base.float",
1631
+ Seed: "base.float",
1632
+ },
1633
+ outputs: { Values: "base.float[]" },
1592
1634
  impl: (ins) => {
1593
1635
  const len = Math.trunc(ins.Domain);
1594
1636
  const min = Number(ins.Min ?? 0);
@@ -1600,10 +1642,15 @@ function setupBasicGraphRegistry() {
1600
1642
  });
1601
1643
  // Random Vectors
1602
1644
  registry.registerNode({
1603
- id: "randomVectors",
1645
+ id: "base.randomXYZs",
1604
1646
  categoryId: "compute",
1605
- inputs: { Domain: "float", Min: "vec3", Max: "vec3", Seed: "float" },
1606
- outputs: { Values: "vec3[]" },
1647
+ inputs: {
1648
+ Domain: "base.float",
1649
+ Min: "base.vec3",
1650
+ Max: "base.vec3",
1651
+ Seed: "base.float",
1652
+ },
1653
+ outputs: { Values: "base.vec3[]" },
1607
1654
  impl: (ins) => {
1608
1655
  const len = Math.trunc(ins.Domain);
1609
1656
  const min = ins.Min ?? [0, 0, 0];
@@ -1622,8 +1669,8 @@ function setupBasicGraphRegistry() {
1622
1669
  function makeBasicGraphDefinition() {
1623
1670
  return {
1624
1671
  nodes: [
1625
- { nodeId: "n1", typeId: "math" },
1626
- { nodeId: "n2", typeId: "math" },
1672
+ { nodeId: "n1", typeId: "base.math" },
1673
+ { nodeId: "n2", typeId: "base.math" },
1627
1674
  ],
1628
1675
  edges: [
1629
1676
  {
@@ -1636,10 +1683,10 @@ function makeBasicGraphDefinition() {
1636
1683
  }
1637
1684
  function registerDelayNode(registry) {
1638
1685
  registry.registerNode({
1639
- id: "delay",
1686
+ id: "async.delay",
1640
1687
  categoryId: "compute",
1641
- inputs: { x: "float", ms: "float" },
1642
- outputs: { out: "float" },
1688
+ inputs: { x: "base.float", ms: "base.float" },
1689
+ outputs: { out: "base.float" },
1643
1690
  impl: async (ins, ctx) => {
1644
1691
  const ms = Number(ins.ms ?? 200);
1645
1692
  const xRaw = ins.x;
@@ -1681,10 +1728,14 @@ function sleepWithAbort(ms, signal) {
1681
1728
  }
1682
1729
  function registerProgressNodes(registry) {
1683
1730
  registry.registerNode({
1684
- id: "progressWorker",
1731
+ id: "async.progress",
1685
1732
  categoryId: "compute",
1686
- inputs: { Steps: "float", DelayMs: "float", ShouldError: "bool" },
1687
- outputs: { Done: "string" },
1733
+ inputs: {
1734
+ Steps: "base.float",
1735
+ DelayMs: "base.float",
1736
+ ShouldError: "base.bool",
1737
+ },
1738
+ outputs: { Done: "base.string" },
1688
1739
  impl: async (ins, ctx) => {
1689
1740
  const steps = Math.max(1, Math.trunc(Number(ins.Steps ?? 10)));
1690
1741
  const delayMs = Math.max(0, Math.trunc(Number(ins.DelayMs ?? 50)));
@@ -1715,20 +1766,20 @@ function createAsyncGraphDef() {
1715
1766
  nodes: [
1716
1767
  {
1717
1768
  nodeId: "n1",
1718
- typeId: "math",
1769
+ typeId: "base.math",
1719
1770
  },
1720
1771
  {
1721
1772
  nodeId: "n2",
1722
- typeId: "delay",
1773
+ typeId: "async.delay",
1723
1774
  params: { policy: { asyncConcurrency: "queue", maxQueue: 4 } },
1724
1775
  },
1725
1776
  {
1726
1777
  nodeId: "n3",
1727
- typeId: "separateXYZ",
1778
+ typeId: "base.separateXYZ",
1728
1779
  },
1729
1780
  {
1730
1781
  nodeId: "n4",
1731
- typeId: "combineXYZ",
1782
+ typeId: "base.compareXYZ",
1732
1783
  },
1733
1784
  ],
1734
1785
  edges: [
@@ -1742,7 +1793,7 @@ function createAsyncGraphDef() {
1742
1793
  id: "e2",
1743
1794
  source: { nodeId: "n4", handle: "XYZ" },
1744
1795
  target: { nodeId: "n1", handle: "A" },
1745
- typeId: "vec3[]",
1796
+ typeId: "base.vec3[]",
1746
1797
  // convertAsync,
1747
1798
  },
1748
1799
  {
@@ -1773,9 +1824,9 @@ function createAsyncGraphRegistry() {
1773
1824
  function createProgressGraphDef() {
1774
1825
  const def = {
1775
1826
  nodes: [
1776
- { nodeId: "steps", typeId: "number" },
1777
- { nodeId: "delay", typeId: "number" },
1778
- { nodeId: "work", typeId: "progressWorker" },
1827
+ { nodeId: "steps", typeId: "base.number" },
1828
+ { nodeId: "async.delay", typeId: "base.number" },
1829
+ { nodeId: "work", typeId: "async.progress" },
1779
1830
  ],
1780
1831
  edges: [
1781
1832
  {
@@ -1785,7 +1836,7 @@ function createProgressGraphDef() {
1785
1836
  },
1786
1837
  {
1787
1838
  id: "e2",
1788
- source: { nodeId: "delay", handle: "Result" },
1839
+ source: { nodeId: "async.delay", handle: "Result" },
1789
1840
  target: { nodeId: "work", handle: "DelayMs" },
1790
1841
  },
1791
1842
  // not wiring ShouldError to show manual input driven error later
@@ -1806,11 +1857,11 @@ function createValidationGraphDef() {
1806
1857
  // - Multi inbound to same input
1807
1858
  const def = {
1808
1859
  nodes: [
1809
- { nodeId: "nA", typeId: "number" },
1810
- { nodeId: "nB", typeId: "number" },
1811
- { nodeId: "nC", typeId: "math" },
1812
- { nodeId: "s1", typeId: "numberToString" },
1813
- { nodeId: "cmp", typeId: "compare" },
1860
+ { nodeId: "nA", typeId: "base.number" },
1861
+ { nodeId: "nB", typeId: "base.number" },
1862
+ { nodeId: "nC", typeId: "base.math" },
1863
+ { nodeId: "s1", typeId: "base.numberToString" },
1864
+ { nodeId: "cmp", typeId: "base.compare" },
1814
1865
  // Global validation issue: unknown node type (no nodeId/edgeId in data)
1815
1866
  { nodeId: "bad", typeId: "unknownType" },
1816
1867
  ],