capdag 1.426.27 → 1.439.69
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/README.md +56 -4
- package/cap-fab-renderer.js +216 -74
- package/capdag.js +4239 -1115
- package/formal/index.d.ts +1 -1
- package/formal/index.js +2 -2
- package/formal/program.wasm +0 -0
- package/machine-parser.js +1547 -234
- package/machine.pegjs +112 -17
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -28,10 +28,10 @@ imported, so importing it waits for that (top-level `await`), and a CommonJS
|
|
|
28
28
|
import { CapUrn, CapUrnBuilder } from "capdag";
|
|
29
29
|
|
|
30
30
|
const parsed = CapUrn.fromString(
|
|
31
|
-
'cap:disbind;in="media:ext=pdf";out="media:enc=utf-8;page"'
|
|
31
|
+
'cap:disbind;in="media:document;ext=pdf";out="media:enc=utf-8;page"'
|
|
32
32
|
);
|
|
33
33
|
const built = new CapUrnBuilder()
|
|
34
|
-
.inSpec("media:ext=pdf")
|
|
34
|
+
.inSpec("media:document;ext=pdf")
|
|
35
35
|
.outSpec("media:enc=utf-8;page")
|
|
36
36
|
.marker("disbind")
|
|
37
37
|
.build();
|
|
@@ -42,11 +42,63 @@ console.log(parsed.toString() === built.toString());
|
|
|
42
42
|
Treat URNs as opaque parsed values. Use the package's predicates for
|
|
43
43
|
equivalence, conformance, dispatch, and ranking instead of string surgery.
|
|
44
44
|
|
|
45
|
+
## Read and write Machine Notation
|
|
46
|
+
|
|
47
|
+
A machine is read against the definitions of the caps it names: they say what
|
|
48
|
+
each step takes and gives, by name and by type, and what a constant written in
|
|
49
|
+
the machine is held to.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
import { Cap, CapArg, CapOutput, ArgSource, CapUrn, CapDefinitions, parseMachine } from "capdag";
|
|
53
|
+
|
|
54
|
+
const extract = new Cap(
|
|
55
|
+
CapUrn.fromString('cap:in="media:document;ext=pdf";extract;out="media:enc=utf-8;ext=txt"'),
|
|
56
|
+
"Extract Text", ["extract"]
|
|
57
|
+
);
|
|
58
|
+
extract.addArg(new CapArg("media:document;ext=pdf", "document", true,
|
|
59
|
+
[new ArgSource({ stdin: "media:document;ext=pdf" })]));
|
|
60
|
+
extract.addOutput(new CapOutput("media:enc=utf-8;ext=txt", "text", "The text"));
|
|
61
|
+
|
|
62
|
+
const definitions = new CapDefinitions().addCaps([extract]);
|
|
63
|
+
const machine = parseMachine(
|
|
64
|
+
'[extract cap:in="media:document;ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
65
|
+
"[(document=pdf) -> extract -> text]",
|
|
66
|
+
definitions
|
|
67
|
+
);
|
|
68
|
+
console.log(machine.toMachineNotation());
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The definitions can come from the fabric registry instead. A client reads one
|
|
72
|
+
manifest of it — a pinned version, or the one the registry has promoted as its
|
|
73
|
+
latest — fetches each definition it needs, holds each cap to the definition
|
|
74
|
+
rules as it reads it, and then serves as the definitions a machine is parsed
|
|
75
|
+
against, fabric aliases included:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
import { FabricRegistryClient, parseMachine } from "capdag";
|
|
79
|
+
|
|
80
|
+
const fabric = await FabricRegistryClient.latest("https://fabric.capdag.com");
|
|
81
|
+
await fabric.getCap('cap:in="media:document;ext=pdf";extract;out="media:enc=utf-8;ext=txt"');
|
|
82
|
+
const machine = parseMachine(
|
|
83
|
+
'[extract cap:in="media:document;ext=pdf";extract;out="media:enc=utf-8;ext=txt"]' +
|
|
84
|
+
"[pdf -> extract -> text]",
|
|
85
|
+
fabric
|
|
86
|
+
);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`parseMachineWithAST` also returns every statement with where each part of it
|
|
90
|
+
stands in the text, and the name the text gave each node. A machine that names
|
|
91
|
+
other machines in step position is read with a `MachineSource`
|
|
92
|
+
(`MapMachineSource`, or `FileMachineSource` under Node.js). Anything with
|
|
93
|
+
`capDefinition(urn)` and `mediaDefinition(urn)` can stand in for
|
|
94
|
+
`CapDefinitions`.
|
|
95
|
+
|
|
45
96
|
## Find the relevant API
|
|
46
97
|
|
|
47
|
-
- `capdag.js` provides URNs, definitions, dispatch, and
|
|
98
|
+
- `capdag.js` provides URNs, definitions, dispatch, matching, and Machine
|
|
99
|
+
Notation: reading, resolving and writing machines.
|
|
48
100
|
- `machine-parser.js` is generated from `machine.pegjs`.
|
|
49
|
-
- `planner.js` provides
|
|
101
|
+
- `planner.js` provides the planner's request and result vocabulary.
|
|
50
102
|
- `cap-fab-renderer.js` (`capdag/cap-fab-renderer`) renders graphs; the page
|
|
51
103
|
provides cytoscape and cytoscape-elk.
|
|
52
104
|
- `formal/` is the generated model code, with its WebAssembly program.
|
package/cap-fab-renderer.js
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
// * cytoscape-elk extension (registers itself on `cytoscape`)
|
|
29
29
|
// * elkjs (via cytoscape-elk)
|
|
30
30
|
// * TaggedUrn (from tagged-urn browser build)
|
|
31
|
-
// * CapUrn, MediaUrn, Cap,
|
|
31
|
+
// * CapUrn, MediaUrn, Cap, CapFab (from capdag.js)
|
|
32
32
|
//
|
|
33
33
|
// The renderer owns its own theme observer (<html data-theme>) so hosts do
|
|
34
34
|
// nothing to drive theme sync. It owns its own tooltip element and its own
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
// uses a different name for the same concept, the fix is at the producer,
|
|
42
42
|
// not here.
|
|
43
43
|
|
|
44
|
-
import { CapUrn, CapFab, MediaUrn
|
|
44
|
+
import { Cap, CapUrn, CapFab, MediaUrn } from './capdag.js';
|
|
45
45
|
|
|
46
46
|
// =============================================================================
|
|
47
47
|
// Host dependencies — the page's: cytoscape and cytoscape-elk, loaded as
|
|
@@ -72,10 +72,27 @@ function cardinalityLabel(input_is_sequence, output_is_sequence) {
|
|
|
72
72
|
return `${lhs}\u2192${rhs}`;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// The main output of a cap as it appears in the /api/capabilities JSON: the
|
|
76
|
+
// one of its `outputs` whose media URN is the cap URN's `out=`, compared as
|
|
77
|
+
// media URNs. Null for a cap that declares none (its `out=` is media:void).
|
|
78
|
+
function mainOutputOf(cap) {
|
|
79
|
+
const outputs = cap.outputs || [];
|
|
80
|
+
if (!Array.isArray(outputs)) {
|
|
81
|
+
throw new Error('CapFabRenderer: a cap\'s outputs are a list');
|
|
82
|
+
}
|
|
83
|
+
if (outputs.length === 0) return null;
|
|
84
|
+
const outSpec = MediaUrn.fromString(CapUrn.fromString(cap.urn).getOutSpec());
|
|
85
|
+
const main = outputs.find(output =>
|
|
86
|
+
output && typeof output.media_urn === 'string' &&
|
|
87
|
+
MediaUrn.fromString(output.media_urn).isEquivalent(outSpec)
|
|
88
|
+
);
|
|
89
|
+
return main || null;
|
|
90
|
+
}
|
|
91
|
+
|
|
75
92
|
// Compute the cardinality marker for a cap as it appears in the
|
|
76
93
|
// /api/capabilities JSON. The main input arg is the one whose sources
|
|
77
|
-
// include a stdin source
|
|
78
|
-
// `CapOutput.is_sequence` names exactly.
|
|
94
|
+
// include a stdin source; the output is the cap's MAIN output. Matches core
|
|
95
|
+
// Rust `CapArg.is_sequence` and `CapOutput.is_sequence` names exactly.
|
|
79
96
|
function cardinalityFromCap(cap) {
|
|
80
97
|
if (!cap || typeof cap !== 'object') {
|
|
81
98
|
throw new Error('CapFabRenderer: cardinalityFromCap requires a cap object');
|
|
@@ -85,7 +102,8 @@ function cardinalityFromCap(cap) {
|
|
|
85
102
|
arg && arg.sources && arg.sources.some(src => src && src.stdin !== undefined)
|
|
86
103
|
);
|
|
87
104
|
const input_is_sequence = mainArg ? (mainArg.is_sequence === true) : false;
|
|
88
|
-
const
|
|
105
|
+
const mainOutput = mainOutputOf(cap);
|
|
106
|
+
const output_is_sequence = mainOutput !== null && mainOutput.is_sequence === true;
|
|
89
107
|
return cardinalityLabel(input_is_sequence, output_is_sequence);
|
|
90
108
|
}
|
|
91
109
|
|
|
@@ -440,8 +458,8 @@ function buildStylesheet() {
|
|
|
440
458
|
|
|
441
459
|
// Machine mode uses a dedicated palette exposed by the notation
|
|
442
460
|
// editor's CSS (`--graph-node-color`, `--graph-loop-color`,
|
|
443
|
-
// `--graph-edge-color`, `--graph-text`, `--graph-muted`).
|
|
444
|
-
//
|
|
461
|
+
// `--graph-edge-color`, `--graph-text`, `--graph-muted`). A step
|
|
462
|
+
// node of the resolved machine is outlined in the edge color.
|
|
445
463
|
const machineNodeColor = getCssVar('--graph-node-color');
|
|
446
464
|
const machineLoopColor = getCssVar('--graph-loop-color');
|
|
447
465
|
const machineEdgeColor = getCssVar('--graph-edge-color');
|
|
@@ -626,11 +644,12 @@ function buildStylesheet() {
|
|
|
626
644
|
// -------- Machine / editor-graph mode ------------------------------
|
|
627
645
|
// Shared between the resolved-machine view (`mode: 'machine'`,
|
|
628
646
|
// canonical `Machine::to_render_payload_json`) and the notation
|
|
629
|
-
// editor's live graph (`mode: 'editor-graph'`, ast-driven).
|
|
630
|
-
//
|
|
631
|
-
//
|
|
632
|
-
//
|
|
633
|
-
//
|
|
647
|
+
// editor's live graph (`mode: 'editor-graph'`, ast-driven). Data
|
|
648
|
+
// slots are `machine-node`s. The editor graph collapses each cap
|
|
649
|
+
// application into labeled edges between its data slots; the
|
|
650
|
+
// resolved machine draws each step as a `machine-step` node, and a
|
|
651
|
+
// constant it is given as a `machine-constant` node, with an edge
|
|
652
|
+
// per binding and per output taken.
|
|
634
653
|
//
|
|
635
654
|
// Reads the editor's dedicated palette so nodes, edges and
|
|
636
655
|
// loop markers match the text theme.
|
|
@@ -660,6 +679,34 @@ function buildStylesheet() {
|
|
|
660
679
|
'line-style': 'dashed',
|
|
661
680
|
},
|
|
662
681
|
},
|
|
682
|
+
{
|
|
683
|
+
selector: 'node.machine-step',
|
|
684
|
+
style: {
|
|
685
|
+
'shape': 'rectangle',
|
|
686
|
+
'background-color': nodeBg,
|
|
687
|
+
'border-color': machineEdgeColor || 'data(color)',
|
|
688
|
+
'color': machineText || nodeText,
|
|
689
|
+
'border-opacity': 1,
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
selector: 'node.machine-step-loop',
|
|
694
|
+
style: {
|
|
695
|
+
'border-color': machineLoopColor || nodeBorderActive,
|
|
696
|
+
'border-style': 'dashed',
|
|
697
|
+
},
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
selector: 'node.machine-constant',
|
|
701
|
+
style: {
|
|
702
|
+
'shape': 'tag',
|
|
703
|
+
'padding': '6px',
|
|
704
|
+
'border-width': '1px',
|
|
705
|
+
'background-color': nodeBg,
|
|
706
|
+
'border-color': machineMuted || nodeBorder,
|
|
707
|
+
'color': machineMuted || nodeText,
|
|
708
|
+
},
|
|
709
|
+
},
|
|
663
710
|
];
|
|
664
711
|
}
|
|
665
712
|
|
|
@@ -974,73 +1021,115 @@ function validateEditorGraphPayload(data) {
|
|
|
974
1021
|
});
|
|
975
1022
|
}
|
|
976
1023
|
|
|
977
|
-
// `validateResolvedMachinePayload` checks the canonical machine
|
|
978
|
-
//
|
|
1024
|
+
// `validateResolvedMachinePayload` checks the canonical machine render
|
|
1025
|
+
// payload every capdag library writes (`Machine.to_render_payload_json`).
|
|
979
1026
|
// Shape:
|
|
980
1027
|
// {
|
|
981
1028
|
// "strands": [
|
|
982
1029
|
// {
|
|
983
|
-
// "nodes": [{"id": "n0", "urn": "media:ext=pdf"}, ...],
|
|
1030
|
+
// "nodes": [{"id": "n0", "urn": "media:document;ext=pdf", "title": "PDF"}, ...],
|
|
984
1031
|
// "edges": [{
|
|
985
1032
|
// "alias": "edge_0",
|
|
986
1033
|
// "cap_urn": "...",
|
|
1034
|
+
// "title": "Extract Text",
|
|
987
1035
|
// "is_loop": false,
|
|
988
|
-
// "
|
|
989
|
-
// "
|
|
1036
|
+
// "retry": 0,
|
|
1037
|
+
// "on_error": "fail",
|
|
1038
|
+
// "included_as": [],
|
|
1039
|
+
// "assignment": [
|
|
1040
|
+
// {"cap_arg_media_urn": "media:document;ext=pdf", "arg_name": "input", "source_node": "n0"},
|
|
1041
|
+
// {"cap_arg_media_urn": "media:integer;window", "arg_name": "window", "constant": 20}
|
|
1042
|
+
// ],
|
|
1043
|
+
// "outputs": [{"output_media_urn": "media:...", "output_name": "output", "node": "n1"}]
|
|
990
1044
|
// }, ...],
|
|
991
1045
|
// "input_anchor_nodes": ["n0"],
|
|
992
|
-
// "output_anchor_nodes": ["
|
|
1046
|
+
// "output_anchor_nodes": ["n1"]
|
|
993
1047
|
// },
|
|
994
1048
|
// ...
|
|
995
1049
|
// ]
|
|
996
1050
|
// }
|
|
1051
|
+
//
|
|
1052
|
+
// A binding is fed by a node of its strand or by a constant, never both and
|
|
1053
|
+
// never neither. A step with no binding is a source, one with no output a
|
|
1054
|
+
// sink. Every node a binding, an output or an anchor names is a node of the
|
|
1055
|
+
// strand.
|
|
997
1056
|
function validateResolvedMachinePayload(data) {
|
|
998
1057
|
if (!data || typeof data !== 'object') {
|
|
999
1058
|
throw new Error('CapFabRenderer machine mode: data must be an object');
|
|
1000
1059
|
}
|
|
1001
1060
|
assertArray(data.strands, 'machine mode data.strands');
|
|
1002
1061
|
data.strands.forEach((strand, sIdx) => {
|
|
1062
|
+
const at = `machine mode data.strands[${sIdx}]`;
|
|
1003
1063
|
if (!strand || typeof strand !== 'object') {
|
|
1004
|
-
throw new Error(`CapFabRenderer
|
|
1064
|
+
throw new Error(`CapFabRenderer ${at} is not an object`);
|
|
1005
1065
|
}
|
|
1006
|
-
assertArray(strand.nodes,
|
|
1066
|
+
assertArray(strand.nodes, `${at}.nodes`);
|
|
1067
|
+
const nodeIds = new Set();
|
|
1007
1068
|
strand.nodes.forEach((n, nIdx) => {
|
|
1008
1069
|
if (!n || typeof n !== 'object') {
|
|
1009
|
-
throw new Error(`CapFabRenderer
|
|
1070
|
+
throw new Error(`CapFabRenderer ${at}.nodes[${nIdx}] is not an object`);
|
|
1010
1071
|
}
|
|
1011
|
-
assertString(n.id,
|
|
1012
|
-
assertString(n.urn,
|
|
1013
|
-
assertString(n.title,
|
|
1072
|
+
assertString(n.id, `${at}.nodes[${nIdx}].id`);
|
|
1073
|
+
assertString(n.urn, `${at}.nodes[${nIdx}].urn`);
|
|
1074
|
+
assertString(n.title, `${at}.nodes[${nIdx}].title`);
|
|
1075
|
+
nodeIds.add(n.id);
|
|
1014
1076
|
});
|
|
1015
|
-
|
|
1077
|
+
const assertNode = (id, path) => {
|
|
1078
|
+
assertString(id, path);
|
|
1079
|
+
if (!nodeIds.has(id)) {
|
|
1080
|
+
throw new Error(`CapFabRenderer ${path} names "${id}", which is no node of the strand`);
|
|
1081
|
+
}
|
|
1082
|
+
};
|
|
1083
|
+
assertArray(strand.edges, `${at}.edges`);
|
|
1016
1084
|
strand.edges.forEach((e, eIdx) => {
|
|
1085
|
+
const edgeAt = `${at}.edges[${eIdx}]`;
|
|
1017
1086
|
if (!e || typeof e !== 'object') {
|
|
1018
|
-
throw new Error(`CapFabRenderer
|
|
1087
|
+
throw new Error(`CapFabRenderer ${edgeAt} is not an object`);
|
|
1019
1088
|
}
|
|
1020
|
-
assertString(e.alias,
|
|
1021
|
-
assertString(e.cap_urn,
|
|
1022
|
-
assertString(e.title,
|
|
1089
|
+
assertString(e.alias, `${edgeAt}.alias`);
|
|
1090
|
+
assertString(e.cap_urn, `${edgeAt}.cap_urn`);
|
|
1091
|
+
assertString(e.title, `${edgeAt}.title`);
|
|
1023
1092
|
if (typeof e.is_loop !== 'boolean') {
|
|
1024
|
-
throw new Error(`CapFabRenderer
|
|
1093
|
+
throw new Error(`CapFabRenderer ${edgeAt}.is_loop must be boolean`);
|
|
1094
|
+
}
|
|
1095
|
+
if (!Number.isInteger(e.retry) || e.retry < 0) {
|
|
1096
|
+
throw new Error(`CapFabRenderer ${edgeAt}.retry must be a whole number of further attempts`);
|
|
1097
|
+
}
|
|
1098
|
+
if (e.on_error !== 'fail' && e.on_error !== 'absent') {
|
|
1099
|
+
throw new Error(`CapFabRenderer ${edgeAt}.on_error must be "fail" or "absent"`);
|
|
1025
1100
|
}
|
|
1026
|
-
assertArray(e.
|
|
1101
|
+
assertArray(e.included_as, `${edgeAt}.included_as`);
|
|
1102
|
+
e.included_as.forEach((step, iIdx) => assertString(step, `${edgeAt}.included_as[${iIdx}]`));
|
|
1103
|
+
assertArray(e.assignment, `${edgeAt}.assignment`);
|
|
1027
1104
|
e.assignment.forEach((b, bIdx) => {
|
|
1105
|
+
const bindingAt = `${edgeAt}.assignment[${bIdx}]`;
|
|
1028
1106
|
if (!b || typeof b !== 'object') {
|
|
1029
|
-
throw new Error(`CapFabRenderer
|
|
1107
|
+
throw new Error(`CapFabRenderer ${bindingAt} is not an object`);
|
|
1030
1108
|
}
|
|
1031
|
-
assertString(b.cap_arg_media_urn,
|
|
1032
|
-
assertString(b.
|
|
1109
|
+
assertString(b.cap_arg_media_urn, `${bindingAt}.cap_arg_media_urn`);
|
|
1110
|
+
assertString(b.arg_name, `${bindingAt}.arg_name`);
|
|
1111
|
+
const byNode = Object.prototype.hasOwnProperty.call(b, 'source_node');
|
|
1112
|
+
const byConstant = Object.prototype.hasOwnProperty.call(b, 'constant');
|
|
1113
|
+
if (byNode === byConstant) {
|
|
1114
|
+
throw new Error(`CapFabRenderer ${bindingAt} must have exactly one of source_node and constant`);
|
|
1115
|
+
}
|
|
1116
|
+
if (byNode) assertNode(b.source_node, `${bindingAt}.source_node`);
|
|
1117
|
+
});
|
|
1118
|
+
assertArray(e.outputs, `${edgeAt}.outputs`);
|
|
1119
|
+
e.outputs.forEach((o, oIdx) => {
|
|
1120
|
+
const outputAt = `${edgeAt}.outputs[${oIdx}]`;
|
|
1121
|
+
if (!o || typeof o !== 'object') {
|
|
1122
|
+
throw new Error(`CapFabRenderer ${outputAt} is not an object`);
|
|
1123
|
+
}
|
|
1124
|
+
assertString(o.output_media_urn, `${outputAt}.output_media_urn`);
|
|
1125
|
+
assertString(o.output_name, `${outputAt}.output_name`);
|
|
1126
|
+
assertNode(o.node, `${outputAt}.node`);
|
|
1033
1127
|
});
|
|
1034
|
-
assertString(e.target_node, `machine mode data.strands[${sIdx}].edges[${eIdx}].target_node`);
|
|
1035
|
-
});
|
|
1036
|
-
assertArray(strand.input_anchor_nodes, `machine mode data.strands[${sIdx}].input_anchor_nodes`);
|
|
1037
|
-
strand.input_anchor_nodes.forEach((id, iIdx) => {
|
|
1038
|
-
assertString(id, `machine mode data.strands[${sIdx}].input_anchor_nodes[${iIdx}]`);
|
|
1039
|
-
});
|
|
1040
|
-
assertArray(strand.output_anchor_nodes, `machine mode data.strands[${sIdx}].output_anchor_nodes`);
|
|
1041
|
-
strand.output_anchor_nodes.forEach((id, oIdx) => {
|
|
1042
|
-
assertString(id, `machine mode data.strands[${sIdx}].output_anchor_nodes[${oIdx}]`);
|
|
1043
1128
|
});
|
|
1129
|
+
assertArray(strand.input_anchor_nodes, `${at}.input_anchor_nodes`);
|
|
1130
|
+
strand.input_anchor_nodes.forEach((id, iIdx) => assertNode(id, `${at}.input_anchor_nodes[${iIdx}]`));
|
|
1131
|
+
assertArray(strand.output_anchor_nodes, `${at}.output_anchor_nodes`);
|
|
1132
|
+
strand.output_anchor_nodes.forEach((id, oIdx) => assertNode(id, `${at}.output_anchor_nodes[${oIdx}]`));
|
|
1044
1133
|
});
|
|
1045
1134
|
}
|
|
1046
1135
|
|
|
@@ -1173,10 +1262,11 @@ function buildBrowseGraphData(capabilities) {
|
|
|
1173
1262
|
const edgeId = `edge-${edges.length}`;
|
|
1174
1263
|
const title = capData.title || '';
|
|
1175
1264
|
|
|
1176
|
-
//
|
|
1177
|
-
//
|
|
1178
|
-
|
|
1179
|
-
|
|
1265
|
+
// The cap as the fabric defines it — its args and outputs with their
|
|
1266
|
+
// names — so a path through the graph is a machine its caps resolve.
|
|
1267
|
+
// Reading it validates the URN and the record: malformed registry data
|
|
1268
|
+
// fails hard.
|
|
1269
|
+
const cap = Cap.fromJSON(capData);
|
|
1180
1270
|
const capFabEdgeIndex = capFab.edges.length;
|
|
1181
1271
|
capFab.addCap(cap, 'registry');
|
|
1182
1272
|
|
|
@@ -2239,7 +2329,7 @@ function buildRunGraphData(data) {
|
|
|
2239
2329
|
// labels the anchor→entry edge on the first body.
|
|
2240
2330
|
//
|
|
2241
2331
|
// Without such a preceding sequence cap, the source itself is
|
|
2242
|
-
// already a list (e.g. `media:ext=pdf;list` source_media_urn) and the
|
|
2332
|
+
// already a list (e.g. `media:document;ext=pdf;list` source_media_urn) and the
|
|
2243
2333
|
// ForEach iterates it directly.
|
|
2244
2334
|
let seqProducerStepIdx = -1;
|
|
2245
2335
|
let seqProducerStep = null;
|
|
@@ -2707,17 +2797,21 @@ function editorGraphSignature(data) {
|
|
|
2707
2797
|
// so we can pass them straight through to cytoscape without name
|
|
2708
2798
|
// collisions.
|
|
2709
2799
|
//
|
|
2710
|
-
// Each
|
|
2711
|
-
//
|
|
2712
|
-
//
|
|
2713
|
-
//
|
|
2714
|
-
//
|
|
2800
|
+
// Each step is a node of its own, between the data it is given and the
|
|
2801
|
+
// data it makes: an edge into it per binding, labelled with the arg's name,
|
|
2802
|
+
// and an edge out of it per output it is taken from, labelled with the
|
|
2803
|
+
// output's name. A step may be given nothing (a source) or have nothing taken
|
|
2804
|
+
// (a sink), and a binding may be a constant, which is drawn as a small node of
|
|
2805
|
+
// its own: none of these has a single input-to-output edge to be drawn on. A
|
|
2806
|
+
// step that runs once per item draws its node and its edges in the loop style.
|
|
2715
2807
|
function buildResolvedMachineGraphData(data) {
|
|
2716
2808
|
validateResolvedMachinePayload(data);
|
|
2717
2809
|
|
|
2718
2810
|
const nodes = [];
|
|
2719
2811
|
const edges = [];
|
|
2720
2812
|
const seenNodeIds = new Set();
|
|
2813
|
+
let stepCounter = 0;
|
|
2814
|
+
let constantCounter = 0;
|
|
2721
2815
|
let edgeCounter = 0;
|
|
2722
2816
|
|
|
2723
2817
|
data.strands.forEach((strand, strandIdx) => {
|
|
@@ -2725,10 +2819,8 @@ function buildResolvedMachineGraphData(data) {
|
|
|
2725
2819
|
const outputAnchorSet = new Set(strand.output_anchor_nodes);
|
|
2726
2820
|
|
|
2727
2821
|
for (const node of strand.nodes) {
|
|
2728
|
-
//
|
|
2729
|
-
//
|
|
2730
|
-
// duplicate appears it indicates a malformed payload, so
|
|
2731
|
-
// fail hard rather than silently dropping.
|
|
2822
|
+
// Node ids are unique across the whole machine: every library names
|
|
2823
|
+
// them from one counter. A duplicate is a malformed payload.
|
|
2732
2824
|
if (seenNodeIds.has(node.id)) {
|
|
2733
2825
|
throw new Error(
|
|
2734
2826
|
`CapFabRenderer machine mode: duplicate node id "${node.id}" in strand ${strandIdx}`
|
|
@@ -2753,36 +2845,86 @@ function buildResolvedMachineGraphData(data) {
|
|
|
2753
2845
|
}
|
|
2754
2846
|
|
|
2755
2847
|
for (const edge of strand.edges) {
|
|
2756
|
-
const
|
|
2757
|
-
|
|
2758
|
-
const
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
: `${edge.alias} (${cardinality})`;
|
|
2762
|
-
|
|
2848
|
+
const stepId = `machine-step-${stepCounter}`;
|
|
2849
|
+
stepCounter++;
|
|
2850
|
+
const policy = [];
|
|
2851
|
+
if (edge.retry > 0) policy.push(`retry=${edge.retry}`);
|
|
2852
|
+
if (edge.on_error === 'absent') policy.push('on_error=absent');
|
|
2763
2853
|
const color = edge.is_loop
|
|
2764
2854
|
? 'var(--graph-loop-color)'
|
|
2765
|
-
: edgeHueColor(
|
|
2766
|
-
const
|
|
2767
|
-
|
|
2768
|
-
|
|
2855
|
+
: edgeHueColor(stepCounter - 1);
|
|
2856
|
+
const edgeClasses = edge.is_loop ? 'machine-edge machine-loop' : 'machine-edge';
|
|
2857
|
+
|
|
2858
|
+
nodes.push({
|
|
2859
|
+
group: 'nodes',
|
|
2860
|
+
data: {
|
|
2861
|
+
id: stepId,
|
|
2862
|
+
label: policy.length > 0 ? `${edge.title}\n{${policy.join(', ')}}` : edge.title,
|
|
2863
|
+
fullUrn: edge.cap_urn,
|
|
2864
|
+
alias: edge.alias,
|
|
2865
|
+
strandIndex: strandIdx,
|
|
2866
|
+
isLoop: edge.is_loop,
|
|
2867
|
+
retry: edge.retry,
|
|
2868
|
+
onError: edge.on_error,
|
|
2869
|
+
includedAs: edge.included_as.slice(),
|
|
2870
|
+
color,
|
|
2871
|
+
},
|
|
2872
|
+
classes: edge.is_loop ? 'machine-step machine-step-loop' : 'machine-step',
|
|
2873
|
+
});
|
|
2769
2874
|
|
|
2770
2875
|
for (const binding of edge.assignment) {
|
|
2876
|
+
let source = binding.source_node;
|
|
2877
|
+
if (source === undefined) {
|
|
2878
|
+
source = `machine-constant-${constantCounter}`;
|
|
2879
|
+
constantCounter++;
|
|
2880
|
+
nodes.push({
|
|
2881
|
+
group: 'nodes',
|
|
2882
|
+
data: {
|
|
2883
|
+
id: source,
|
|
2884
|
+
label: JSON.stringify(binding.constant),
|
|
2885
|
+
fullUrn: binding.cap_arg_media_urn,
|
|
2886
|
+
strandIndex: strandIdx,
|
|
2887
|
+
},
|
|
2888
|
+
classes: 'machine-constant',
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2771
2891
|
edges.push({
|
|
2772
2892
|
group: 'edges',
|
|
2773
2893
|
data: {
|
|
2774
2894
|
id: `machine-edge-${edgeCounter}`,
|
|
2775
|
-
source
|
|
2776
|
-
target:
|
|
2777
|
-
label,
|
|
2778
|
-
title: `${
|
|
2779
|
-
fullUrn:
|
|
2895
|
+
source,
|
|
2896
|
+
target: stepId,
|
|
2897
|
+
label: binding.arg_name,
|
|
2898
|
+
title: `${edge.title}: ${binding.arg_name} (${binding.cap_arg_media_urn})`,
|
|
2899
|
+
fullUrn: binding.cap_arg_media_urn,
|
|
2780
2900
|
color,
|
|
2781
2901
|
strandIndex: strandIdx,
|
|
2902
|
+
argName: binding.arg_name,
|
|
2782
2903
|
capArgMediaUrn: binding.cap_arg_media_urn,
|
|
2783
2904
|
isLoop: edge.is_loop,
|
|
2784
2905
|
},
|
|
2785
|
-
classes:
|
|
2906
|
+
classes: edgeClasses,
|
|
2907
|
+
});
|
|
2908
|
+
edgeCounter++;
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
for (const output of edge.outputs) {
|
|
2912
|
+
edges.push({
|
|
2913
|
+
group: 'edges',
|
|
2914
|
+
data: {
|
|
2915
|
+
id: `machine-edge-${edgeCounter}`,
|
|
2916
|
+
source: stepId,
|
|
2917
|
+
target: output.node,
|
|
2918
|
+
label: output.output_name,
|
|
2919
|
+
title: `${edge.title}: ${output.output_name} (${output.output_media_urn})`,
|
|
2920
|
+
fullUrn: output.output_media_urn,
|
|
2921
|
+
color,
|
|
2922
|
+
strandIndex: strandIdx,
|
|
2923
|
+
outputName: output.output_name,
|
|
2924
|
+
outputMediaUrn: output.output_media_urn,
|
|
2925
|
+
isLoop: edge.is_loop,
|
|
2926
|
+
},
|
|
2927
|
+
classes: edgeClasses,
|
|
2786
2928
|
});
|
|
2787
2929
|
edgeCounter++;
|
|
2788
2930
|
}
|