@bian-womp/spark-graph 0.2.16 → 0.2.18
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 +165 -189
- package/lib/cjs/index.cjs.map +1 -1
- package/lib/cjs/src/builder/GraphBuilder.d.ts.map +1 -1
- package/lib/cjs/src/core/types.d.ts +3 -4
- package/lib/cjs/src/core/types.d.ts.map +1 -1
- package/lib/cjs/src/misc/base.d.ts.map +1 -1
- package/lib/cjs/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/lib/esm/index.js +165 -189
- package/lib/esm/index.js.map +1 -1
- package/lib/esm/src/builder/GraphBuilder.d.ts.map +1 -1
- package/lib/esm/src/core/types.d.ts +3 -4
- package/lib/esm/src/core/types.d.ts.map +1 -1
- package/lib/esm/src/misc/base.d.ts.map +1 -1
- package/lib/esm/src/runtime/GraphRuntime.d.ts.map +1 -1
- package/package.json +2 -2
package/lib/cjs/index.cjs
CHANGED
|
@@ -477,23 +477,20 @@ class GraphRuntime {
|
|
|
477
477
|
}
|
|
478
478
|
// Instantiate edges
|
|
479
479
|
gr.edges = GraphRuntime.buildEdges(def, registry, gr.resolvedByNode);
|
|
480
|
-
// After nodes and edges exist, seed registry- and graph-level defaults
|
|
480
|
+
// After nodes and edges exist, seed registry-, dynamic- and graph-level defaults
|
|
481
481
|
for (const n of def.nodes) {
|
|
482
482
|
const node = gr.nodes.get(n.nodeId);
|
|
483
483
|
const desc = registry.nodes.get(n.typeId);
|
|
484
484
|
if (!node || !desc)
|
|
485
485
|
continue;
|
|
486
|
-
// Resolve registry-level defaults
|
|
487
|
-
const regDefaults =
|
|
488
|
-
|
|
489
|
-
params: n.params,
|
|
490
|
-
environment: gr.environment,
|
|
491
|
-
})
|
|
492
|
-
: desc.inputDefaults ?? {};
|
|
486
|
+
// Resolve registry-level defaults and dynamic (resolved) defaults
|
|
487
|
+
const regDefaults = desc.inputDefaults ?? {};
|
|
488
|
+
const dynDefaults = gr.resolvedByNode.get(n.nodeId)?.inputDefaults ?? {};
|
|
493
489
|
const graphDefaults = n.initialInputs ?? {};
|
|
494
|
-
// Apply precedence: graph-level overrides registry-level
|
|
490
|
+
// Apply precedence: graph-level overrides dynamic, which overrides registry-level
|
|
495
491
|
const merged = {
|
|
496
492
|
...regDefaults,
|
|
493
|
+
...dynDefaults,
|
|
497
494
|
...graphDefaults,
|
|
498
495
|
};
|
|
499
496
|
for (const [handle, value] of Object.entries(merged)) {
|
|
@@ -915,10 +912,12 @@ class GraphRuntime {
|
|
|
915
912
|
continue;
|
|
916
913
|
const overrideInputs = n.resolvedHandles?.inputs;
|
|
917
914
|
const overrideOutputs = n.resolvedHandles?.outputs;
|
|
915
|
+
const overrideDefaults = n.resolvedHandles?.inputDefaults;
|
|
918
916
|
// Merge base with overrides (allow partial resolvedHandles)
|
|
919
917
|
const inputs = { ...desc.inputs, ...overrideInputs };
|
|
920
918
|
const outputs = { ...desc.outputs, ...overrideOutputs };
|
|
921
|
-
|
|
919
|
+
const inputDefaults = { ...desc.inputDefaults, ...overrideDefaults };
|
|
920
|
+
out.set(n.nodeId, { inputs, outputs, inputDefaults });
|
|
922
921
|
}
|
|
923
922
|
return out;
|
|
924
923
|
}
|
|
@@ -1326,15 +1325,12 @@ class GraphRuntime {
|
|
|
1326
1325
|
if (defNode) {
|
|
1327
1326
|
const desc = registry.nodes.get(defNode.typeId);
|
|
1328
1327
|
if (desc) {
|
|
1329
|
-
const regDefaults =
|
|
1330
|
-
|
|
1331
|
-
params: defNode.params,
|
|
1332
|
-
environment: this.environment,
|
|
1333
|
-
})
|
|
1334
|
-
: desc.inputDefaults ?? {};
|
|
1328
|
+
const regDefaults = desc.inputDefaults ?? {};
|
|
1329
|
+
const dynDefaults = this.resolvedByNode.get(defNode.nodeId)?.inputDefaults ?? {};
|
|
1335
1330
|
const graphDefaults = defNode.initialInputs ?? {};
|
|
1336
1331
|
const merged = {
|
|
1337
1332
|
...regDefaults,
|
|
1333
|
+
...dynDefaults,
|
|
1338
1334
|
...graphDefaults,
|
|
1339
1335
|
};
|
|
1340
1336
|
for (const h of Array.from(prevSet)) {
|
|
@@ -1406,6 +1402,34 @@ class GraphRuntime {
|
|
|
1406
1402
|
}
|
|
1407
1403
|
}
|
|
1408
1404
|
}
|
|
1405
|
+
// Seed defaults for nodes (new or existing) where inputs are still undefined and not inbound
|
|
1406
|
+
for (const n of def.nodes) {
|
|
1407
|
+
const node = this.nodes.get(n.nodeId);
|
|
1408
|
+
const desc = registry.nodes.get(n.typeId);
|
|
1409
|
+
if (!node || !desc)
|
|
1410
|
+
continue;
|
|
1411
|
+
const regDefaults = desc.inputDefaults ?? {};
|
|
1412
|
+
const dynDefaults = this.resolvedByNode.get(n.nodeId)?.inputDefaults ?? {};
|
|
1413
|
+
const graphDefaults = n.initialInputs ?? {};
|
|
1414
|
+
const merged = {
|
|
1415
|
+
...regDefaults,
|
|
1416
|
+
...dynDefaults,
|
|
1417
|
+
...graphDefaults,
|
|
1418
|
+
};
|
|
1419
|
+
const inboundSet = nextInbound.get(n.nodeId) ?? new Set();
|
|
1420
|
+
for (const [handle, value] of Object.entries(merged)) {
|
|
1421
|
+
if (value === undefined)
|
|
1422
|
+
continue;
|
|
1423
|
+
if (inboundSet.has(handle))
|
|
1424
|
+
continue;
|
|
1425
|
+
if (node.inputs[handle] !== undefined)
|
|
1426
|
+
continue;
|
|
1427
|
+
node.inputs[handle] =
|
|
1428
|
+
typeof structuredClone === "function"
|
|
1429
|
+
? structuredClone(value)
|
|
1430
|
+
: JSON.parse(JSON.stringify(value));
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1409
1433
|
// Prune array bucket contributions for edges that no longer exist
|
|
1410
1434
|
const validPerTarget = new Map();
|
|
1411
1435
|
for (const ed of this.edges) {
|
|
@@ -1440,35 +1464,56 @@ class GraphBuilder {
|
|
|
1440
1464
|
const issues = [];
|
|
1441
1465
|
const nodeIds = new Set();
|
|
1442
1466
|
const edgeIds = new Set();
|
|
1467
|
+
// Precompute effective handle maps (registry statics merged with per-node resolvedHandles)
|
|
1468
|
+
const nodeById = new Map();
|
|
1469
|
+
const effByNodeId = new Map();
|
|
1470
|
+
const getEffectiveHandles = (n) => {
|
|
1471
|
+
if (!n)
|
|
1472
|
+
return { inputs: {}, outputs: {} };
|
|
1473
|
+
const desc = this.registry.nodes.get(n.typeId);
|
|
1474
|
+
const resolved = n.resolvedHandles || {};
|
|
1475
|
+
const inputs = { ...desc?.inputs, ...resolved.inputs };
|
|
1476
|
+
const outputs = { ...desc?.outputs, ...resolved.outputs };
|
|
1477
|
+
return { inputs, outputs };
|
|
1478
|
+
};
|
|
1479
|
+
const normOut = (decl) => Array.isArray(decl) ? decl : decl ? [decl] : [];
|
|
1480
|
+
const inferEdgeType = (srcDeclared, dstDeclared, explicit) => {
|
|
1481
|
+
if (explicit)
|
|
1482
|
+
return explicit;
|
|
1483
|
+
if (Array.isArray(srcDeclared) && dstDeclared)
|
|
1484
|
+
return dstDeclared;
|
|
1485
|
+
if (srcDeclared)
|
|
1486
|
+
return Array.isArray(srcDeclared) ? srcDeclared[0] : srcDeclared;
|
|
1487
|
+
return undefined;
|
|
1488
|
+
};
|
|
1489
|
+
const canFlow = (from, to) => {
|
|
1490
|
+
if (!to || !from)
|
|
1491
|
+
return true;
|
|
1492
|
+
const arr = Array.isArray(from) ? from : [from];
|
|
1493
|
+
return arr.every((s) => s === to || !!this.registry.canCoerce(s, to));
|
|
1494
|
+
};
|
|
1495
|
+
const pushIssue = (level, code, message, data) => {
|
|
1496
|
+
issues.push({ level, code, message, data });
|
|
1497
|
+
};
|
|
1443
1498
|
// nodes exist, ids unique, and categories registered
|
|
1444
1499
|
for (const n of def.nodes) {
|
|
1445
1500
|
if (nodeIds.has(n.nodeId)) {
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
code: "NODE_ID_DUP",
|
|
1449
|
-
message: `Duplicate nodeId ${n.nodeId}`,
|
|
1450
|
-
data: { nodeId: n.nodeId },
|
|
1501
|
+
pushIssue("error", "NODE_ID_DUP", `Duplicate nodeId ${n.nodeId}`, {
|
|
1502
|
+
nodeId: n.nodeId,
|
|
1451
1503
|
});
|
|
1452
1504
|
}
|
|
1453
1505
|
else {
|
|
1454
1506
|
nodeIds.add(n.nodeId);
|
|
1455
1507
|
}
|
|
1508
|
+
nodeById.set(n.nodeId, n);
|
|
1509
|
+
effByNodeId.set(n.nodeId, getEffectiveHandles(n));
|
|
1456
1510
|
const nodeType = this.registry.nodes.get(n.typeId);
|
|
1457
1511
|
if (!nodeType) {
|
|
1458
|
-
|
|
1459
|
-
level: "error",
|
|
1460
|
-
code: "NODE_TYPE_MISSING",
|
|
1461
|
-
message: `Unknown node type ${n.typeId}`,
|
|
1462
|
-
data: { typeId: n.typeId, nodeId: n.nodeId },
|
|
1463
|
-
});
|
|
1512
|
+
pushIssue("error", "NODE_TYPE_MISSING", `Unknown node type ${n.typeId}`, { typeId: n.typeId, nodeId: n.nodeId });
|
|
1464
1513
|
continue;
|
|
1465
1514
|
}
|
|
1466
1515
|
if (!this.registry.categories.has(nodeType.categoryId)) {
|
|
1467
|
-
|
|
1468
|
-
level: "error",
|
|
1469
|
-
code: "CATEGORY_MISSING",
|
|
1470
|
-
message: `Unknown category ${nodeType.categoryId} for node type ${n.typeId}`,
|
|
1471
|
-
});
|
|
1516
|
+
pushIssue("error", "CATEGORY_MISSING", `Unknown category ${nodeType.categoryId} for node type ${n.typeId}`);
|
|
1472
1517
|
}
|
|
1473
1518
|
}
|
|
1474
1519
|
// edges validation: nodes exist, handles exist, type exists
|
|
@@ -1477,186 +1522,96 @@ class GraphBuilder {
|
|
|
1477
1522
|
const inboundArrayOk = new Set();
|
|
1478
1523
|
for (const e of def.edges) {
|
|
1479
1524
|
if (edgeIds.has(e.id)) {
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
code: "EDGE_ID_DUP",
|
|
1483
|
-
message: `Duplicate edge id ${e.id}`,
|
|
1484
|
-
data: { edgeId: e.id },
|
|
1525
|
+
pushIssue("error", "EDGE_ID_DUP", `Duplicate edge id ${e.id}`, {
|
|
1526
|
+
edgeId: e.id,
|
|
1485
1527
|
});
|
|
1486
1528
|
}
|
|
1487
1529
|
else {
|
|
1488
1530
|
edgeIds.add(e.id);
|
|
1489
1531
|
}
|
|
1490
|
-
const srcNode =
|
|
1491
|
-
const dstNode =
|
|
1532
|
+
const srcNode = nodeById.get(e.source.nodeId);
|
|
1533
|
+
const dstNode = nodeById.get(e.target.nodeId);
|
|
1492
1534
|
if (!srcNode)
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
code: "EDGE_SOURCE_MISSING",
|
|
1496
|
-
message: `Edge ${e.id} source node missing`,
|
|
1497
|
-
data: { edgeId: e.id },
|
|
1535
|
+
pushIssue("error", "EDGE_SOURCE_MISSING", `Edge ${e.id} source node missing`, {
|
|
1536
|
+
edgeId: e.id,
|
|
1498
1537
|
});
|
|
1499
1538
|
if (!dstNode)
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
code: "EDGE_TARGET_MISSING",
|
|
1503
|
-
message: `Edge ${e.id} target node missing`,
|
|
1504
|
-
data: { edgeId: e.id },
|
|
1539
|
+
pushIssue("error", "EDGE_TARGET_MISSING", `Edge ${e.id} target node missing`, {
|
|
1540
|
+
edgeId: e.id,
|
|
1505
1541
|
});
|
|
1506
|
-
//
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
if (!effectiveTypeId) {
|
|
1521
|
-
if (Array.isArray(_srcDeclared) && _dstDeclared) {
|
|
1522
|
-
// When source is a union and target input type is known, adopt the input type
|
|
1523
|
-
// so validation checks are performed against the target, not an arbitrary variant.
|
|
1524
|
-
effectiveTypeId = _dstDeclared;
|
|
1525
|
-
}
|
|
1526
|
-
else if (_srcDeclared) {
|
|
1527
|
-
effectiveTypeId = Array.isArray(_srcDeclared)
|
|
1528
|
-
? _srcDeclared[0]
|
|
1529
|
-
: _srcDeclared;
|
|
1530
|
-
}
|
|
1531
|
-
}
|
|
1542
|
+
// Effective handle declarations
|
|
1543
|
+
const srcEff = effByNodeId.get(e.source.nodeId) || {
|
|
1544
|
+
outputs: {},
|
|
1545
|
+
};
|
|
1546
|
+
const dstEff = effByNodeId.get(e.target.nodeId) || {
|
|
1547
|
+
inputs: {}};
|
|
1548
|
+
const _srcDeclared = srcNode
|
|
1549
|
+
? srcEff.outputs[e.source.handle]
|
|
1550
|
+
: undefined;
|
|
1551
|
+
const _dstDeclared = dstNode
|
|
1552
|
+
? getInputTypeId(dstEff.inputs, e.target.handle)
|
|
1553
|
+
: undefined;
|
|
1554
|
+
// Effective edge type
|
|
1555
|
+
const effectiveTypeId = inferEdgeType(_srcDeclared, _dstDeclared, e.typeId);
|
|
1532
1556
|
const type = effectiveTypeId
|
|
1533
1557
|
? this.registry.types.get(effectiveTypeId)
|
|
1534
1558
|
: undefined;
|
|
1535
1559
|
if (!type) {
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
code: "TYPE_MISSING",
|
|
1539
|
-
message: `Edge ${e.id} type missing or unknown`,
|
|
1540
|
-
data: { edgeId: e.id },
|
|
1560
|
+
pushIssue("error", "TYPE_MISSING", `Edge ${e.id} type missing or unknown`, {
|
|
1561
|
+
edgeId: e.id,
|
|
1541
1562
|
});
|
|
1542
1563
|
}
|
|
1543
1564
|
if (srcNode) {
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
issues.push({
|
|
1547
|
-
level: "error",
|
|
1548
|
-
code: "OUTPUT_MISSING",
|
|
1549
|
-
message: `Edge ${e.id} source output ${e.source.handle} missing on ${srcNode.typeId}`,
|
|
1550
|
-
data: {
|
|
1551
|
-
edgeId: e.id,
|
|
1552
|
-
nodeId: srcNode.nodeId,
|
|
1553
|
-
output: e.source.handle,
|
|
1554
|
-
},
|
|
1555
|
-
});
|
|
1565
|
+
if (!(e.source.handle in srcEff.outputs)) {
|
|
1566
|
+
pushIssue("error", "OUTPUT_MISSING", `Edge ${e.id} source output ${e.source.handle} missing on ${srcNode.typeId}`, { edgeId: e.id, nodeId: srcNode.nodeId, output: e.source.handle });
|
|
1556
1567
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
issues.push({
|
|
1569
|
-
level: "error",
|
|
1570
|
-
code: "TYPE_MISMATCH_OUTPUT",
|
|
1571
|
-
message: `Edge ${e.id} type ${effectiveTypeId} mismatches source output ${srcNode.typeId}.${e.source.handle} (${s}) and no coercion exists`,
|
|
1572
|
-
data: {
|
|
1573
|
-
edgeId: e.id,
|
|
1574
|
-
nodeId: srcNode.nodeId,
|
|
1575
|
-
output: e.source.handle,
|
|
1576
|
-
declared: s,
|
|
1577
|
-
effectiveTypeId,
|
|
1578
|
-
},
|
|
1579
|
-
});
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1568
|
+
const declaredArr = normOut(srcEff.outputs[e.source.handle]);
|
|
1569
|
+
if (declaredArr.length > 0 &&
|
|
1570
|
+
effectiveTypeId &&
|
|
1571
|
+
!canFlow(declaredArr, effectiveTypeId)) {
|
|
1572
|
+
pushIssue("error", "TYPE_MISMATCH_OUTPUT", `Edge ${e.id} type ${effectiveTypeId} mismatches source output ${srcNode.typeId}.${e.source.handle} (${declaredArr.join("|")}) and no coercion exists`, {
|
|
1573
|
+
edgeId: e.id,
|
|
1574
|
+
nodeId: srcNode.nodeId,
|
|
1575
|
+
output: e.source.handle,
|
|
1576
|
+
declared: declaredArr.join("|"),
|
|
1577
|
+
effectiveTypeId,
|
|
1578
|
+
});
|
|
1583
1579
|
}
|
|
1584
1580
|
}
|
|
1585
1581
|
if (dstNode) {
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
data: {
|
|
1593
|
-
edgeId: e.id,
|
|
1594
|
-
nodeId: dstNode.nodeId,
|
|
1595
|
-
input: e.target.handle,
|
|
1596
|
-
},
|
|
1597
|
-
});
|
|
1582
|
+
if (!(e.target.handle in dstEff.inputs)) {
|
|
1583
|
+
pushIssue("error", "INPUT_MISSING", `Edge ${e.id} target input ${e.target.handle} missing on ${dstNode.typeId}`, { edgeId: e.id, nodeId: dstNode.nodeId, input: e.target.handle });
|
|
1584
|
+
}
|
|
1585
|
+
// Private inputs should not accept edges
|
|
1586
|
+
if (isInputPrivate(dstEff.inputs, e.target.handle)) {
|
|
1587
|
+
pushIssue("error", "INPUT_PRIVATE", `Edge ${e.id} targets private input ${dstNode.typeId}.${e.target.handle}`, { edgeId: e.id, nodeId: dstNode.nodeId, input: e.target.handle });
|
|
1598
1588
|
}
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
if (
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1589
|
+
const declaredIn = getInputTypeId(dstEff.inputs, e.target.handle);
|
|
1590
|
+
if (declaredIn && effectiveTypeId) {
|
|
1591
|
+
if (srcNode) {
|
|
1592
|
+
const srcDeclared = srcEff.outputs[e.source.handle];
|
|
1593
|
+
const srcArr = normOut(srcDeclared).length
|
|
1594
|
+
? normOut(srcDeclared)
|
|
1595
|
+
: [effectiveTypeId];
|
|
1596
|
+
if (!canFlow(srcArr, declaredIn)) {
|
|
1597
|
+
pushIssue("error", "TYPE_MISMATCH_INPUT", `Edge ${e.id} output type ${srcArr.join("|")} not convertible to target input ${dstNode.typeId}.${e.target.handle} (${declaredIn})`, {
|
|
1607
1598
|
edgeId: e.id,
|
|
1608
1599
|
nodeId: dstNode.nodeId,
|
|
1609
1600
|
input: e.target.handle,
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
}
|
|
1613
|
-
const declaredIn = getInputTypeId(dstType.inputs, e.target.handle);
|
|
1614
|
-
if (declaredIn && effectiveTypeId) {
|
|
1615
|
-
// If source is a union, ensure each variant can reach declaredIn
|
|
1616
|
-
if (srcNode) {
|
|
1617
|
-
const srcType = this.registry.nodes.get(srcNode.typeId);
|
|
1618
|
-
const srcDeclared = srcType?.outputs[e.source.handle];
|
|
1619
|
-
const srcArr = Array.isArray(srcDeclared)
|
|
1620
|
-
? srcDeclared
|
|
1621
|
-
: srcDeclared
|
|
1622
|
-
? [srcDeclared]
|
|
1623
|
-
: effectiveTypeId
|
|
1624
|
-
? [effectiveTypeId]
|
|
1625
|
-
: [];
|
|
1626
|
-
for (const s of srcArr) {
|
|
1627
|
-
if (s !== declaredIn &&
|
|
1628
|
-
!this.registry.canCoerce(s, declaredIn)) {
|
|
1629
|
-
issues.push({
|
|
1630
|
-
level: "error",
|
|
1631
|
-
code: "TYPE_MISMATCH_INPUT",
|
|
1632
|
-
message: `Edge ${e.id} output type ${s} not convertible to target input ${dstNode.typeId}.${e.target.handle} (${declaredIn})`,
|
|
1633
|
-
data: {
|
|
1634
|
-
edgeId: e.id,
|
|
1635
|
-
nodeId: dstNode.nodeId,
|
|
1636
|
-
input: e.target.handle,
|
|
1637
|
-
declared: declaredIn,
|
|
1638
|
-
effectiveTypeId: s,
|
|
1639
|
-
},
|
|
1640
|
-
});
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
else if (declaredIn !== effectiveTypeId &&
|
|
1645
|
-
!this.registry.canCoerce(effectiveTypeId, declaredIn)) {
|
|
1646
|
-
issues.push({
|
|
1647
|
-
level: "error",
|
|
1648
|
-
code: "TYPE_MISMATCH_INPUT",
|
|
1649
|
-
message: `Edge ${e.id} type ${effectiveTypeId} mismatches target input ${dstNode.typeId}.${e.target.handle} (${declaredIn}) and no coercion exists`,
|
|
1650
|
-
data: {
|
|
1651
|
-
edgeId: e.id,
|
|
1652
|
-
nodeId: dstNode.nodeId,
|
|
1653
|
-
input: e.target.handle,
|
|
1654
|
-
declared: declaredIn,
|
|
1655
|
-
effectiveTypeId,
|
|
1656
|
-
},
|
|
1601
|
+
declared: declaredIn,
|
|
1602
|
+
effectiveTypeId: srcArr.join("|"),
|
|
1657
1603
|
});
|
|
1658
1604
|
}
|
|
1659
1605
|
}
|
|
1606
|
+
else if (!canFlow(effectiveTypeId, declaredIn)) {
|
|
1607
|
+
pushIssue("error", "TYPE_MISMATCH_INPUT", `Edge ${e.id} type ${effectiveTypeId} mismatches target input ${dstNode.typeId}.${e.target.handle} (${declaredIn}) and no coercion exists`, {
|
|
1608
|
+
edgeId: e.id,
|
|
1609
|
+
nodeId: dstNode.nodeId,
|
|
1610
|
+
input: e.target.handle,
|
|
1611
|
+
declared: declaredIn,
|
|
1612
|
+
effectiveTypeId,
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1660
1615
|
}
|
|
1661
1616
|
}
|
|
1662
1617
|
// Track multiple inbound edges targeting the same input handle
|
|
@@ -1664,10 +1619,7 @@ class GraphBuilder {
|
|
|
1664
1619
|
inboundCounts.set(inboundKey, (inboundCounts.get(inboundKey) ?? 0) + 1);
|
|
1665
1620
|
// If the target input is declared as an array type, allow multi-inbound (runtime will append)
|
|
1666
1621
|
if (dstNode) {
|
|
1667
|
-
const
|
|
1668
|
-
const declaredIn = dstType
|
|
1669
|
-
? getInputTypeId(dstType.inputs, e.target.handle)
|
|
1670
|
-
: undefined;
|
|
1622
|
+
const declaredIn = getInputTypeId((effByNodeId.get(dstNode.nodeId) || { inputs: {} }).inputs, e.target.handle);
|
|
1671
1623
|
if (typeof declaredIn === "string" && declaredIn.endsWith("[]")) {
|
|
1672
1624
|
inboundArrayOk.add(inboundKey);
|
|
1673
1625
|
}
|
|
@@ -2689,6 +2641,30 @@ function setupBasicGraphRegistry() {
|
|
|
2689
2641
|
return { Items: Array.from({ length }, (_, i) => ins[`Item${i}`]) };
|
|
2690
2642
|
},
|
|
2691
2643
|
});
|
|
2644
|
+
// Decompose array into dynamic item outputs
|
|
2645
|
+
registry.registerNode({
|
|
2646
|
+
id: "base.array.decompose",
|
|
2647
|
+
categoryId: "compute",
|
|
2648
|
+
inputs: { Items: "base.object" },
|
|
2649
|
+
outputs: {},
|
|
2650
|
+
resolveHandles: ({ inputs }) => {
|
|
2651
|
+
const maxLen = 64;
|
|
2652
|
+
const arr = Array.isArray(inputs?.Items) ? inputs?.Items : [];
|
|
2653
|
+
const n = Math.max(0, Math.min(maxLen, arr.length));
|
|
2654
|
+
const dyn = {};
|
|
2655
|
+
for (let i = 0; i < n; i++)
|
|
2656
|
+
dyn[`Item${i}`] = "base.object";
|
|
2657
|
+
return { outputs: dyn };
|
|
2658
|
+
},
|
|
2659
|
+
impl: (ins) => {
|
|
2660
|
+
const arr = Array.isArray(ins.Items) ? ins.Items : [];
|
|
2661
|
+
const out = {};
|
|
2662
|
+
const n = Math.max(0, Math.min(64, arr.length));
|
|
2663
|
+
for (let i = 0; i < n; i++)
|
|
2664
|
+
out[`Item${i}`] = arr[i];
|
|
2665
|
+
return out;
|
|
2666
|
+
},
|
|
2667
|
+
});
|
|
2692
2668
|
// Select
|
|
2693
2669
|
registry.registerNode({
|
|
2694
2670
|
id: "base.select",
|