@synergenius/flow-weaver 0.21.14 → 0.21.16
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/dist/annotation-generator.js +15 -2
- package/dist/api/generate-in-place.js +23 -4
- package/dist/cli/flow-weaver.mjs +123 -10
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/parser.js +28 -6
- package/dist/sugar-optimizer.d.ts +12 -7
- package/dist/sugar-optimizer.js +96 -10
- package/package.json +1 -1
|
@@ -225,7 +225,18 @@ export class AnnotationGenerator {
|
|
|
225
225
|
}
|
|
226
226
|
});
|
|
227
227
|
// Filter stale macros (e.g. paths whose connections were deleted)
|
|
228
|
-
const existingMacros = filterStaleMacros(workflow.macros || [], workflow.connections, workflow.instances);
|
|
228
|
+
const existingMacros = filterStaleMacros(workflow.macros || [], workflow.connections, workflow.instances, workflow.nodeTypes, workflow.startPorts, workflow.exitPorts);
|
|
229
|
+
// Compute dropped coerce instance IDs — their connections must be excluded
|
|
230
|
+
const survivingCoerceIds = new Set();
|
|
231
|
+
for (const macro of existingMacros) {
|
|
232
|
+
if (macro.type === 'coerce')
|
|
233
|
+
survivingCoerceIds.add(macro.instanceId);
|
|
234
|
+
}
|
|
235
|
+
const droppedCoerceIds = new Set();
|
|
236
|
+
for (const id of coerceInstanceIds) {
|
|
237
|
+
if (!survivingCoerceIds.has(id))
|
|
238
|
+
droppedCoerceIds.add(id);
|
|
239
|
+
}
|
|
229
240
|
// Auto-detect @path sugar patterns from connections
|
|
230
241
|
const detected = detectSugarPatterns(workflow.connections, workflow.instances, existingMacros, workflow.nodeTypes, workflow.startPorts, workflow.exitPorts);
|
|
231
242
|
// Merge detected macros with existing ones
|
|
@@ -275,11 +286,13 @@ export class AnnotationGenerator {
|
|
|
275
286
|
if (workflow.ui?.exitNode?.x !== undefined && workflow.ui?.exitNode?.y !== undefined) {
|
|
276
287
|
lines.push(` * @position Exit ${Math.round(workflow.ui.exitNode.x)} ${Math.round(workflow.ui.exitNode.y)}`);
|
|
277
288
|
}
|
|
278
|
-
// Add connections — skip connections covered by macros
|
|
289
|
+
// Add connections — skip connections covered by macros and dropped coerce connections
|
|
279
290
|
if (!workflow.options?.autoConnect) {
|
|
280
291
|
workflow.connections.forEach((conn) => {
|
|
281
292
|
if (allMacros.length > 0 && isConnectionCoveredByMacroStatic(conn, allMacros))
|
|
282
293
|
return;
|
|
294
|
+
if (droppedCoerceIds.has(conn.from.node) || droppedCoerceIds.has(conn.to.node))
|
|
295
|
+
return;
|
|
283
296
|
const fromScope = conn.from.scope ? `:${conn.from.scope}` : '';
|
|
284
297
|
const toScope = conn.to.scope ? `:${conn.to.scope}` : '';
|
|
285
298
|
lines.push(` * @connect ${conn.from.node}.${conn.from.port}${fromScope} -> ${conn.to.node}.${conn.to.port}${toScope}`);
|
|
@@ -1035,10 +1035,11 @@ function isConnectionCoveredByMacro(conn, macros) {
|
|
|
1035
1035
|
*/
|
|
1036
1036
|
function generateWorkflowJSDoc(ast, options = {}) {
|
|
1037
1037
|
const lines = [];
|
|
1038
|
-
// Build macro coverage sets for filtering (@map
|
|
1038
|
+
// Build macro coverage sets for filtering (@map and @coerce)
|
|
1039
1039
|
const macroInstanceIds = new Set();
|
|
1040
1040
|
const macroChildIds = new Set();
|
|
1041
1041
|
const macroScopeNames = new Set();
|
|
1042
|
+
const allCoerceInstanceIds = new Set();
|
|
1042
1043
|
if (ast.macros && ast.macros.length > 0) {
|
|
1043
1044
|
for (const macro of ast.macros) {
|
|
1044
1045
|
if (macro.type === 'map') {
|
|
@@ -1046,6 +1047,9 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
1046
1047
|
macroChildIds.add(macro.childId);
|
|
1047
1048
|
macroScopeNames.add(`${macro.instanceId}.iterate`);
|
|
1048
1049
|
}
|
|
1050
|
+
else if (macro.type === 'coerce') {
|
|
1051
|
+
allCoerceInstanceIds.add(macro.instanceId);
|
|
1052
|
+
}
|
|
1049
1053
|
}
|
|
1050
1054
|
}
|
|
1051
1055
|
lines.push('/**');
|
|
@@ -1127,11 +1131,13 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
1127
1131
|
// Auto-position: compute default positions for nodes without explicit positions.
|
|
1128
1132
|
// Must happen before instance tags are generated so [position:] can be emitted.
|
|
1129
1133
|
const autoPositions = computeAutoPositions(ast);
|
|
1130
|
-
// Add node instances — skip synthetic MAP_ITERATOR instances, strip parent from macro children.
|
|
1134
|
+
// Add node instances — skip synthetic MAP_ITERATOR/COERCION instances, strip parent from macro children.
|
|
1131
1135
|
// Merge auto-computed positions into instance config (without mutating the AST).
|
|
1132
1136
|
for (const instance of ast.instances) {
|
|
1133
1137
|
if (macroInstanceIds.has(instance.id))
|
|
1134
1138
|
continue;
|
|
1139
|
+
if (allCoerceInstanceIds.has(instance.id))
|
|
1140
|
+
continue;
|
|
1135
1141
|
// Merge auto-position into config if not already set
|
|
1136
1142
|
let inst = instance;
|
|
1137
1143
|
if (inst.config?.x === undefined || inst.config?.y === undefined) {
|
|
@@ -1157,7 +1163,18 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
1157
1163
|
}
|
|
1158
1164
|
}
|
|
1159
1165
|
// Filter stale macros (e.g. paths whose connections were deleted)
|
|
1160
|
-
const existingMacros = filterStaleMacros(ast.macros || [], ast.connections, ast.instances);
|
|
1166
|
+
const existingMacros = filterStaleMacros(ast.macros || [], ast.connections, ast.instances, ast.nodeTypes, ast.startPorts, ast.exitPorts);
|
|
1167
|
+
// Compute dropped coerce instance IDs — their synthetic instances and connections must be excluded
|
|
1168
|
+
const survivingCoerceIds = new Set();
|
|
1169
|
+
for (const macro of existingMacros) {
|
|
1170
|
+
if (macro.type === 'coerce')
|
|
1171
|
+
survivingCoerceIds.add(macro.instanceId);
|
|
1172
|
+
}
|
|
1173
|
+
const droppedCoerceIds = new Set();
|
|
1174
|
+
for (const id of allCoerceInstanceIds) {
|
|
1175
|
+
if (!survivingCoerceIds.has(id))
|
|
1176
|
+
droppedCoerceIds.add(id);
|
|
1177
|
+
}
|
|
1161
1178
|
// Auto-detect @path sugar patterns from connections
|
|
1162
1179
|
const detected = detectSugarPatterns(ast.connections, ast.instances, existingMacros, ast.nodeTypes, ast.startPorts, ast.exitPorts);
|
|
1163
1180
|
// Merge detected macros with existing ones
|
|
@@ -1205,11 +1222,13 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
1205
1222
|
lines.push(` * @position Exit ${Math.round(exitX)} ${Math.round(exitY)}`);
|
|
1206
1223
|
}
|
|
1207
1224
|
// Add connections (with scope suffix when present)
|
|
1208
|
-
// Skip connections covered by
|
|
1225
|
+
// Skip connections covered by macros, autoConnect-generated connections, and dropped coerce connections
|
|
1209
1226
|
if (!ast.options?.autoConnect) {
|
|
1210
1227
|
for (const conn of ast.connections) {
|
|
1211
1228
|
if (allMacros.length > 0 && isConnectionCoveredByMacro(conn, allMacros))
|
|
1212
1229
|
continue;
|
|
1230
|
+
if (droppedCoerceIds.has(conn.from.node) || droppedCoerceIds.has(conn.to.node))
|
|
1231
|
+
continue;
|
|
1213
1232
|
const fromScope = conn.from.scope ? `:${conn.from.scope}` : '';
|
|
1214
1233
|
const toScope = conn.to.scope ? `:${conn.to.scope}` : '';
|
|
1215
1234
|
lines.push(` * @connect ${conn.from.node}.${conn.from.port}${fromScope} -> ${conn.to.node}.${conn.to.port}${toScope}`);
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -9671,7 +9671,7 @@ var VERSION;
|
|
|
9671
9671
|
var init_generated_version = __esm({
|
|
9672
9672
|
"src/generated-version.ts"() {
|
|
9673
9673
|
"use strict";
|
|
9674
|
-
VERSION = "0.21.
|
|
9674
|
+
VERSION = "0.21.16";
|
|
9675
9675
|
}
|
|
9676
9676
|
});
|
|
9677
9677
|
|
|
@@ -18090,7 +18090,7 @@ var init_port_tag_utils = __esm({
|
|
|
18090
18090
|
});
|
|
18091
18091
|
|
|
18092
18092
|
// src/sugar-optimizer.ts
|
|
18093
|
-
function validatePathMacro(path50, connections, instances) {
|
|
18093
|
+
function validatePathMacro(path50, connections, instances, nodeTypes, startPorts, exitPorts) {
|
|
18094
18094
|
const instanceIds = new Set(instances.map((inst) => inst.id));
|
|
18095
18095
|
for (const step of path50.steps) {
|
|
18096
18096
|
if (step.node === "Start" || step.node === "Exit") continue;
|
|
@@ -18126,21 +18126,85 @@ function validatePathMacro(path50, connections, instances) {
|
|
|
18126
18126
|
return false;
|
|
18127
18127
|
}
|
|
18128
18128
|
}
|
|
18129
|
+
if (nodeTypes && startPorts && exitPorts) {
|
|
18130
|
+
const instanceMap = new Map(instances.map((inst) => [inst.id, inst]));
|
|
18131
|
+
const nodeTypeMap = /* @__PURE__ */ new Map();
|
|
18132
|
+
for (const nt of nodeTypes) {
|
|
18133
|
+
nodeTypeMap.set(nt.name, nt);
|
|
18134
|
+
if (nt.functionName !== nt.name) {
|
|
18135
|
+
nodeTypeMap.set(nt.functionName, nt);
|
|
18136
|
+
}
|
|
18137
|
+
}
|
|
18138
|
+
const getNodeType2 = (nodeId) => {
|
|
18139
|
+
const inst = instanceMap.get(nodeId);
|
|
18140
|
+
if (!inst) return void 0;
|
|
18141
|
+
return nodeTypeMap.get(inst.nodeType);
|
|
18142
|
+
};
|
|
18143
|
+
const getOutputPorts = (nodeId) => {
|
|
18144
|
+
if (nodeId === "Start") return startPorts;
|
|
18145
|
+
const nt = getNodeType2(nodeId);
|
|
18146
|
+
return nt?.outputs || {};
|
|
18147
|
+
};
|
|
18148
|
+
const getInputPorts = (nodeId) => {
|
|
18149
|
+
if (nodeId === "Exit") return exitPorts;
|
|
18150
|
+
const nt = getNodeType2(nodeId);
|
|
18151
|
+
return nt?.inputs || {};
|
|
18152
|
+
};
|
|
18153
|
+
const { steps } = path50;
|
|
18154
|
+
for (let i = 0; i < steps.length - 1; i++) {
|
|
18155
|
+
const nextId = steps[i + 1].node;
|
|
18156
|
+
if (nextId === "Exit") continue;
|
|
18157
|
+
const nextInputs = getInputPorts(nextId);
|
|
18158
|
+
for (const [inputName] of Object.entries(nextInputs)) {
|
|
18159
|
+
if (isControlFlowPort(inputName)) continue;
|
|
18160
|
+
for (let j = i; j >= 0; j--) {
|
|
18161
|
+
const ancestorId = steps[j].node;
|
|
18162
|
+
const ancestorOutputs = getOutputPorts(ancestorId);
|
|
18163
|
+
if (inputName in ancestorOutputs && !isControlFlowPort(inputName)) {
|
|
18164
|
+
const key = `${ancestorId}.${inputName}->${nextId}.${inputName}`;
|
|
18165
|
+
if (!connKeys.has(key)) {
|
|
18166
|
+
return false;
|
|
18167
|
+
}
|
|
18168
|
+
break;
|
|
18169
|
+
}
|
|
18170
|
+
}
|
|
18171
|
+
}
|
|
18172
|
+
}
|
|
18173
|
+
}
|
|
18129
18174
|
return true;
|
|
18130
18175
|
}
|
|
18131
|
-
function filterStaleMacros(macros, connections, instances) {
|
|
18176
|
+
function filterStaleMacros(macros, connections, instances, nodeTypes, startPorts, exitPorts) {
|
|
18132
18177
|
const instanceIds = new Set(instances.map((i) => i.id));
|
|
18133
18178
|
instanceIds.add("Start");
|
|
18134
18179
|
instanceIds.add("Exit");
|
|
18180
|
+
const connKeys = /* @__PURE__ */ new Set();
|
|
18181
|
+
for (const conn of connections) {
|
|
18182
|
+
if (!conn.from.scope && !conn.to.scope) {
|
|
18183
|
+
connKeys.add(`${conn.from.node}.${conn.from.port}->${conn.to.node}.${conn.to.port}`);
|
|
18184
|
+
}
|
|
18185
|
+
}
|
|
18135
18186
|
return macros.filter((macro) => {
|
|
18136
|
-
if (macro.type === "path") return validatePathMacro(macro, connections, instances);
|
|
18187
|
+
if (macro.type === "path") return validatePathMacro(macro, connections, instances, nodeTypes, startPorts, exitPorts);
|
|
18137
18188
|
if (macro.type === "fanOut") {
|
|
18138
18189
|
if (!instanceIds.has(macro.source.node)) return false;
|
|
18139
|
-
return macro.targets.every((t) =>
|
|
18190
|
+
return macro.targets.every((t) => {
|
|
18191
|
+
if (!instanceIds.has(t.node)) return false;
|
|
18192
|
+
const targetPort = t.port ?? macro.source.port;
|
|
18193
|
+
return connKeys.has(`${macro.source.node}.${macro.source.port}->${t.node}.${targetPort}`);
|
|
18194
|
+
});
|
|
18140
18195
|
}
|
|
18141
18196
|
if (macro.type === "fanIn") {
|
|
18142
18197
|
if (!instanceIds.has(macro.target.node)) return false;
|
|
18143
|
-
return macro.sources.every((s) =>
|
|
18198
|
+
return macro.sources.every((s) => {
|
|
18199
|
+
if (!instanceIds.has(s.node)) return false;
|
|
18200
|
+
const sourcePort = s.port ?? macro.target.port;
|
|
18201
|
+
return connKeys.has(`${s.node}.${sourcePort}->${macro.target.node}.${macro.target.port}`);
|
|
18202
|
+
});
|
|
18203
|
+
}
|
|
18204
|
+
if (macro.type === "coerce") {
|
|
18205
|
+
const toCoerce = `${macro.source.node}.${macro.source.port}->${macro.instanceId}.value`;
|
|
18206
|
+
const fromCoerce = `${macro.instanceId}.result->${macro.target.node}.${macro.target.port}`;
|
|
18207
|
+
return connKeys.has(toCoerce) && connKeys.has(fromCoerce);
|
|
18144
18208
|
}
|
|
18145
18209
|
return true;
|
|
18146
18210
|
});
|
|
@@ -18775,8 +18839,19 @@ var init_annotation_generator = __esm({
|
|
|
18775
18839
|
const existingMacros = filterStaleMacros(
|
|
18776
18840
|
workflow.macros || [],
|
|
18777
18841
|
workflow.connections,
|
|
18778
|
-
workflow.instances
|
|
18842
|
+
workflow.instances,
|
|
18843
|
+
workflow.nodeTypes,
|
|
18844
|
+
workflow.startPorts,
|
|
18845
|
+
workflow.exitPorts
|
|
18779
18846
|
);
|
|
18847
|
+
const survivingCoerceIds = /* @__PURE__ */ new Set();
|
|
18848
|
+
for (const macro of existingMacros) {
|
|
18849
|
+
if (macro.type === "coerce") survivingCoerceIds.add(macro.instanceId);
|
|
18850
|
+
}
|
|
18851
|
+
const droppedCoerceIds = /* @__PURE__ */ new Set();
|
|
18852
|
+
for (const id of coerceInstanceIds) {
|
|
18853
|
+
if (!survivingCoerceIds.has(id)) droppedCoerceIds.add(id);
|
|
18854
|
+
}
|
|
18780
18855
|
const detected = detectSugarPatterns(
|
|
18781
18856
|
workflow.connections,
|
|
18782
18857
|
workflow.instances,
|
|
@@ -18825,6 +18900,7 @@ var init_annotation_generator = __esm({
|
|
|
18825
18900
|
if (!workflow.options?.autoConnect) {
|
|
18826
18901
|
workflow.connections.forEach((conn) => {
|
|
18827
18902
|
if (allMacros.length > 0 && isConnectionCoveredByMacroStatic(conn, allMacros)) return;
|
|
18903
|
+
if (droppedCoerceIds.has(conn.from.node) || droppedCoerceIds.has(conn.to.node)) return;
|
|
18828
18904
|
const fromScope = conn.from.scope ? `:${conn.from.scope}` : "";
|
|
18829
18905
|
const toScope = conn.to.scope ? `:${conn.to.scope}` : "";
|
|
18830
18906
|
lines.push(` * @connect ${conn.from.node}.${conn.from.port}${fromScope} -> ${conn.to.node}.${conn.to.port}${toScope}`);
|
|
@@ -19679,12 +19755,15 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
19679
19755
|
const macroInstanceIds = /* @__PURE__ */ new Set();
|
|
19680
19756
|
const macroChildIds = /* @__PURE__ */ new Set();
|
|
19681
19757
|
const macroScopeNames = /* @__PURE__ */ new Set();
|
|
19758
|
+
const allCoerceInstanceIds = /* @__PURE__ */ new Set();
|
|
19682
19759
|
if (ast.macros && ast.macros.length > 0) {
|
|
19683
19760
|
for (const macro of ast.macros) {
|
|
19684
19761
|
if (macro.type === "map") {
|
|
19685
19762
|
macroInstanceIds.add(macro.instanceId);
|
|
19686
19763
|
macroChildIds.add(macro.childId);
|
|
19687
19764
|
macroScopeNames.add(`${macro.instanceId}.iterate`);
|
|
19765
|
+
} else if (macro.type === "coerce") {
|
|
19766
|
+
allCoerceInstanceIds.add(macro.instanceId);
|
|
19688
19767
|
}
|
|
19689
19768
|
}
|
|
19690
19769
|
}
|
|
@@ -19744,6 +19823,7 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
19744
19823
|
const autoPositions = computeAutoPositions(ast);
|
|
19745
19824
|
for (const instance of ast.instances) {
|
|
19746
19825
|
if (macroInstanceIds.has(instance.id)) continue;
|
|
19826
|
+
if (allCoerceInstanceIds.has(instance.id)) continue;
|
|
19747
19827
|
let inst = instance;
|
|
19748
19828
|
if (inst.config?.x === void 0 || inst.config?.y === void 0) {
|
|
19749
19829
|
const autoPos = autoPositions.get(inst.id);
|
|
@@ -19768,8 +19848,19 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
19768
19848
|
const existingMacros = filterStaleMacros(
|
|
19769
19849
|
ast.macros || [],
|
|
19770
19850
|
ast.connections,
|
|
19771
|
-
ast.instances
|
|
19851
|
+
ast.instances,
|
|
19852
|
+
ast.nodeTypes,
|
|
19853
|
+
ast.startPorts,
|
|
19854
|
+
ast.exitPorts
|
|
19772
19855
|
);
|
|
19856
|
+
const survivingCoerceIds = /* @__PURE__ */ new Set();
|
|
19857
|
+
for (const macro of existingMacros) {
|
|
19858
|
+
if (macro.type === "coerce") survivingCoerceIds.add(macro.instanceId);
|
|
19859
|
+
}
|
|
19860
|
+
const droppedCoerceIds = /* @__PURE__ */ new Set();
|
|
19861
|
+
for (const id of allCoerceInstanceIds) {
|
|
19862
|
+
if (!survivingCoerceIds.has(id)) droppedCoerceIds.add(id);
|
|
19863
|
+
}
|
|
19773
19864
|
const detected = detectSugarPatterns(
|
|
19774
19865
|
ast.connections,
|
|
19775
19866
|
ast.instances,
|
|
@@ -19818,6 +19909,7 @@ function generateWorkflowJSDoc(ast, options = {}) {
|
|
|
19818
19909
|
if (!ast.options?.autoConnect) {
|
|
19819
19910
|
for (const conn of ast.connections) {
|
|
19820
19911
|
if (allMacros.length > 0 && isConnectionCoveredByMacro(conn, allMacros)) continue;
|
|
19912
|
+
if (droppedCoerceIds.has(conn.from.node) || droppedCoerceIds.has(conn.to.node)) continue;
|
|
19821
19913
|
const fromScope = conn.from.scope ? `:${conn.from.scope}` : "";
|
|
19822
19914
|
const toScope = conn.to.scope ? `:${conn.to.scope}` : "";
|
|
19823
19915
|
lines.push(
|
|
@@ -36172,7 +36264,28 @@ ${fn.getText()}` : fn.getText();
|
|
|
36172
36264
|
Object.values(outputs).forEach((port) => {
|
|
36173
36265
|
if (port.scope) portScopes.add(port.scope);
|
|
36174
36266
|
});
|
|
36175
|
-
|
|
36267
|
+
let scopes;
|
|
36268
|
+
if (config2.scope) {
|
|
36269
|
+
scopes = [config2.scope];
|
|
36270
|
+
} else if (portScopes.size > 0) {
|
|
36271
|
+
const orderedScopes = [];
|
|
36272
|
+
try {
|
|
36273
|
+
const params = fn.getParameters();
|
|
36274
|
+
for (const param of params) {
|
|
36275
|
+
const paramName = param.getName();
|
|
36276
|
+
if (portScopes.has(paramName)) {
|
|
36277
|
+
orderedScopes.push(paramName);
|
|
36278
|
+
}
|
|
36279
|
+
}
|
|
36280
|
+
} catch {
|
|
36281
|
+
}
|
|
36282
|
+
for (const scope of portScopes) {
|
|
36283
|
+
if (!orderedScopes.includes(scope)) {
|
|
36284
|
+
orderedScopes.push(scope);
|
|
36285
|
+
}
|
|
36286
|
+
}
|
|
36287
|
+
scopes = orderedScopes;
|
|
36288
|
+
}
|
|
36176
36289
|
nodeTypes.push({
|
|
36177
36290
|
type: "NodeType",
|
|
36178
36291
|
name: nodeTypeName,
|
|
@@ -93117,7 +93230,7 @@ function displayInstalledPackage(pkg) {
|
|
|
93117
93230
|
// src/cli/index.ts
|
|
93118
93231
|
init_logger();
|
|
93119
93232
|
init_error_utils();
|
|
93120
|
-
var version2 = true ? "0.21.
|
|
93233
|
+
var version2 = true ? "0.21.16" : "0.0.0-dev";
|
|
93121
93234
|
var program2 = new Command();
|
|
93122
93235
|
program2.name("fw").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
|
|
93123
93236
|
logger.banner(version2);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.21.
|
|
1
|
+
export declare const VERSION = "0.21.16";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
package/dist/parser.js
CHANGED
|
@@ -789,13 +789,35 @@ export class AnnotationParser {
|
|
|
789
789
|
});
|
|
790
790
|
// Determine scopes array:
|
|
791
791
|
// - If node has node-level scope: use that (old architecture)
|
|
792
|
-
// - Otherwise if ports have scopes: use unique port scopes
|
|
792
|
+
// - Otherwise if ports have scopes: use unique port scopes ordered by function parameter position
|
|
793
793
|
// - Otherwise: undefined (no scopes)
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
794
|
+
let scopes;
|
|
795
|
+
if (config.scope) {
|
|
796
|
+
scopes = [config.scope];
|
|
797
|
+
}
|
|
798
|
+
else if (portScopes.size > 0) {
|
|
799
|
+
// Order scopes by function parameter position (callback params whose name matches a scope)
|
|
800
|
+
const orderedScopes = [];
|
|
801
|
+
try {
|
|
802
|
+
const params = fn.getParameters();
|
|
803
|
+
for (const param of params) {
|
|
804
|
+
const paramName = param.getName();
|
|
805
|
+
if (portScopes.has(paramName)) {
|
|
806
|
+
orderedScopes.push(paramName);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
catch {
|
|
811
|
+
// Fall back to Set order if parameter extraction fails
|
|
812
|
+
}
|
|
813
|
+
// Add any scopes not found as params (e.g. from JSDoc-only scope declarations)
|
|
814
|
+
for (const scope of portScopes) {
|
|
815
|
+
if (!orderedScopes.includes(scope)) {
|
|
816
|
+
orderedScopes.push(scope);
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
scopes = orderedScopes;
|
|
820
|
+
}
|
|
799
821
|
nodeTypes.push({
|
|
800
822
|
type: 'NodeType',
|
|
801
823
|
name: nodeTypeName,
|
|
@@ -10,19 +10,24 @@ export interface DetectedSugar {
|
|
|
10
10
|
paths: TPathMacro[];
|
|
11
11
|
}
|
|
12
12
|
/**
|
|
13
|
-
* Validate that a @path macro's
|
|
14
|
-
*
|
|
15
|
-
* as instances (or are
|
|
13
|
+
* Validate that a @path macro's connections still exist in the actual
|
|
14
|
+
* connection set. Checks both control-flow AND data connections (scope
|
|
15
|
+
* walking). Also checks that all step nodes exist as instances (or are
|
|
16
|
+
* Start/Exit).
|
|
16
17
|
*
|
|
17
18
|
* Returns true if the path is still valid, false if it should be dropped.
|
|
19
|
+
*
|
|
20
|
+
* When nodeTypes/startPorts/exitPorts are provided, data connections are
|
|
21
|
+
* validated using the same scope-walking algorithm that expandPathMacros
|
|
22
|
+
* uses. Without them, only control-flow is checked (legacy behavior).
|
|
18
23
|
*/
|
|
19
|
-
export declare function validatePathMacro(path: TPathMacro, connections: TConnectionAST[], instances: TNodeInstanceAST[]): boolean;
|
|
24
|
+
export declare function validatePathMacro(path: TPathMacro, connections: TConnectionAST[], instances: TNodeInstanceAST[], nodeTypes?: TNodeTypeAST[], startPorts?: Record<string, TPortDefinition>, exitPorts?: Record<string, TPortDefinition>): boolean;
|
|
20
25
|
/**
|
|
21
|
-
* Filter existing macros, removing any @path macros whose
|
|
22
|
-
*
|
|
26
|
+
* Filter existing macros, removing any @path macros whose connections
|
|
27
|
+
* (control-flow AND data) no longer exist in the connection set.
|
|
23
28
|
* Non-path macros are passed through unchanged.
|
|
24
29
|
*/
|
|
25
|
-
export declare function filterStaleMacros(macros: TWorkflowMacro[], connections: TConnectionAST[], instances: TNodeInstanceAST[]): TWorkflowMacro[];
|
|
30
|
+
export declare function filterStaleMacros(macros: TWorkflowMacro[], connections: TConnectionAST[], instances: TNodeInstanceAST[], nodeTypes?: TNodeTypeAST[], startPorts?: Record<string, TPortDefinition>, exitPorts?: Record<string, TPortDefinition>): TWorkflowMacro[];
|
|
26
31
|
/**
|
|
27
32
|
* Detect @path routes from a set of connections.
|
|
28
33
|
*
|
package/dist/sugar-optimizer.js
CHANGED
|
@@ -10,13 +10,18 @@ import { isControlFlowPort } from './constants.js';
|
|
|
10
10
|
// Path Validation (for stale macro detection during round-trip)
|
|
11
11
|
// =============================================================================
|
|
12
12
|
/**
|
|
13
|
-
* Validate that a @path macro's
|
|
14
|
-
*
|
|
15
|
-
* as instances (or are
|
|
13
|
+
* Validate that a @path macro's connections still exist in the actual
|
|
14
|
+
* connection set. Checks both control-flow AND data connections (scope
|
|
15
|
+
* walking). Also checks that all step nodes exist as instances (or are
|
|
16
|
+
* Start/Exit).
|
|
16
17
|
*
|
|
17
18
|
* Returns true if the path is still valid, false if it should be dropped.
|
|
19
|
+
*
|
|
20
|
+
* When nodeTypes/startPorts/exitPorts are provided, data connections are
|
|
21
|
+
* validated using the same scope-walking algorithm that expandPathMacros
|
|
22
|
+
* uses. Without them, only control-flow is checked (legacy behavior).
|
|
18
23
|
*/
|
|
19
|
-
export function validatePathMacro(path, connections, instances) {
|
|
24
|
+
export function validatePathMacro(path, connections, instances, nodeTypes, startPorts, exitPorts) {
|
|
20
25
|
const instanceIds = new Set(instances.map(inst => inst.id));
|
|
21
26
|
// Check all step nodes exist as instances (or are Start/Exit)
|
|
22
27
|
for (const step of path.steps) {
|
|
@@ -61,29 +66,110 @@ export function validatePathMacro(path, connections, instances) {
|
|
|
61
66
|
return false;
|
|
62
67
|
}
|
|
63
68
|
}
|
|
69
|
+
// Validate data connections (scope walking) — same algorithm as
|
|
70
|
+
// expandPathMacros and detectSugarPatterns Step 3.
|
|
71
|
+
// If any data connection that the path would auto-generate is missing,
|
|
72
|
+
// the path is stale (a connection was explicitly removed).
|
|
73
|
+
if (nodeTypes && startPorts && exitPorts) {
|
|
74
|
+
const instanceMap = new Map(instances.map(inst => [inst.id, inst]));
|
|
75
|
+
const nodeTypeMap = new Map();
|
|
76
|
+
for (const nt of nodeTypes) {
|
|
77
|
+
nodeTypeMap.set(nt.name, nt);
|
|
78
|
+
if (nt.functionName !== nt.name) {
|
|
79
|
+
nodeTypeMap.set(nt.functionName, nt);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const getNodeType = (nodeId) => {
|
|
83
|
+
const inst = instanceMap.get(nodeId);
|
|
84
|
+
if (!inst)
|
|
85
|
+
return undefined;
|
|
86
|
+
return nodeTypeMap.get(inst.nodeType);
|
|
87
|
+
};
|
|
88
|
+
const getOutputPorts = (nodeId) => {
|
|
89
|
+
if (nodeId === 'Start')
|
|
90
|
+
return startPorts;
|
|
91
|
+
const nt = getNodeType(nodeId);
|
|
92
|
+
return nt?.outputs || {};
|
|
93
|
+
};
|
|
94
|
+
const getInputPorts = (nodeId) => {
|
|
95
|
+
if (nodeId === 'Exit')
|
|
96
|
+
return exitPorts;
|
|
97
|
+
const nt = getNodeType(nodeId);
|
|
98
|
+
return nt?.inputs || {};
|
|
99
|
+
};
|
|
100
|
+
const { steps } = path;
|
|
101
|
+
for (let i = 0; i < steps.length - 1; i++) {
|
|
102
|
+
const nextId = steps[i + 1].node;
|
|
103
|
+
if (nextId === 'Exit')
|
|
104
|
+
continue;
|
|
105
|
+
const nextInputs = getInputPorts(nextId);
|
|
106
|
+
for (const [inputName] of Object.entries(nextInputs)) {
|
|
107
|
+
if (isControlFlowPort(inputName))
|
|
108
|
+
continue;
|
|
109
|
+
// Walk backward through path steps to find nearest ancestor with same-name output
|
|
110
|
+
for (let j = i; j >= 0; j--) {
|
|
111
|
+
const ancestorId = steps[j].node;
|
|
112
|
+
const ancestorOutputs = getOutputPorts(ancestorId);
|
|
113
|
+
if (inputName in ancestorOutputs && !isControlFlowPort(inputName)) {
|
|
114
|
+
const key = `${ancestorId}.${inputName}->${nextId}.${inputName}`;
|
|
115
|
+
if (!connKeys.has(key)) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
64
124
|
return true;
|
|
65
125
|
}
|
|
66
126
|
/**
|
|
67
|
-
* Filter existing macros, removing any @path macros whose
|
|
68
|
-
*
|
|
127
|
+
* Filter existing macros, removing any @path macros whose connections
|
|
128
|
+
* (control-flow AND data) no longer exist in the connection set.
|
|
69
129
|
* Non-path macros are passed through unchanged.
|
|
70
130
|
*/
|
|
71
|
-
export function filterStaleMacros(macros, connections, instances) {
|
|
131
|
+
export function filterStaleMacros(macros, connections, instances, nodeTypes, startPorts, exitPorts) {
|
|
72
132
|
const instanceIds = new Set(instances.map(i => i.id));
|
|
73
133
|
instanceIds.add('Start');
|
|
74
134
|
instanceIds.add('Exit');
|
|
135
|
+
// Build connection key set for quick lookup
|
|
136
|
+
const connKeys = new Set();
|
|
137
|
+
for (const conn of connections) {
|
|
138
|
+
if (!conn.from.scope && !conn.to.scope) {
|
|
139
|
+
connKeys.add(`${conn.from.node}.${conn.from.port}->${conn.to.node}.${conn.to.port}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
75
142
|
return macros.filter(macro => {
|
|
76
143
|
if (macro.type === 'path')
|
|
77
|
-
return validatePathMacro(macro, connections, instances);
|
|
144
|
+
return validatePathMacro(macro, connections, instances, nodeTypes, startPorts, exitPorts);
|
|
78
145
|
if (macro.type === 'fanOut') {
|
|
79
146
|
if (!instanceIds.has(macro.source.node))
|
|
80
147
|
return false;
|
|
81
|
-
|
|
148
|
+
// Validate that all fanOut connections still exist
|
|
149
|
+
return macro.targets.every(t => {
|
|
150
|
+
if (!instanceIds.has(t.node))
|
|
151
|
+
return false;
|
|
152
|
+
const targetPort = t.port ?? macro.source.port;
|
|
153
|
+
return connKeys.has(`${macro.source.node}.${macro.source.port}->${t.node}.${targetPort}`);
|
|
154
|
+
});
|
|
82
155
|
}
|
|
83
156
|
if (macro.type === 'fanIn') {
|
|
84
157
|
if (!instanceIds.has(macro.target.node))
|
|
85
158
|
return false;
|
|
86
|
-
|
|
159
|
+
// Validate that all fanIn connections still exist
|
|
160
|
+
return macro.sources.every(s => {
|
|
161
|
+
if (!instanceIds.has(s.node))
|
|
162
|
+
return false;
|
|
163
|
+
const sourcePort = s.port ?? macro.target.port;
|
|
164
|
+
return connKeys.has(`${s.node}.${sourcePort}->${macro.target.node}.${macro.target.port}`);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (macro.type === 'coerce') {
|
|
168
|
+
// Validate that both coerce connections still exist:
|
|
169
|
+
// source -> coerceInstance.value AND coerceInstance.result -> target
|
|
170
|
+
const toCoerce = `${macro.source.node}.${macro.source.port}->${macro.instanceId}.value`;
|
|
171
|
+
const fromCoerce = `${macro.instanceId}.result->${macro.target.node}.${macro.target.port}`;
|
|
172
|
+
return connKeys.has(toCoerce) && connKeys.has(fromCoerce);
|
|
87
173
|
}
|
|
88
174
|
return true;
|
|
89
175
|
});
|
package/package.json
CHANGED