@synergenius/flow-weaver 0.5.1 → 0.7.0
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 +2 -1
- package/dist/annotation-generator.js +29 -4
- package/dist/api/generate-in-place.js +3 -0
- package/dist/ast/types.d.ts +22 -2
- package/dist/built-in-nodes/coercion-types.d.ts +15 -0
- package/dist/built-in-nodes/coercion-types.js +61 -0
- package/dist/built-in-nodes/invoke-workflow.js +3 -3
- package/dist/built-in-nodes/mock-types.d.ts +20 -0
- package/dist/built-in-nodes/mock-types.js +30 -0
- package/dist/built-in-nodes/wait-for-agent.js +5 -4
- package/dist/built-in-nodes/wait-for-event.js +3 -3
- package/dist/chevrotain-parser/coerce-parser.d.ts +33 -0
- package/dist/chevrotain-parser/coerce-parser.js +103 -0
- package/dist/chevrotain-parser/index.d.ts +3 -1
- package/dist/chevrotain-parser/index.js +3 -1
- package/dist/chevrotain-parser/port-parser.js +2 -1
- package/dist/chevrotain-parser/tokens.d.ts +2 -0
- package/dist/chevrotain-parser/tokens.js +10 -0
- package/dist/cli/commands/compile.js +6 -0
- package/dist/cli/commands/init.js +2 -0
- package/dist/cli/commands/run.d.ts +4 -0
- package/dist/cli/commands/run.js +68 -1
- package/dist/cli/commands/validate.js +12 -0
- package/dist/cli/flow-weaver.mjs +764 -65
- package/dist/cli/index.js +1 -0
- package/dist/doc-metadata/extractors/annotations.js +17 -0
- package/dist/doc-metadata/extractors/error-codes.js +50 -0
- package/dist/friendly-errors.js +131 -5
- package/dist/generator/inngest.js +27 -0
- package/dist/generator/unified.d.ts +7 -2
- package/dist/generator/unified.js +31 -3
- package/dist/jsdoc-parser.d.ts +14 -0
- package/dist/jsdoc-parser.js +19 -1
- package/dist/mcp/tools-editor.js +20 -1
- package/dist/mcp/workflow-executor.d.ts +1 -0
- package/dist/mcp/workflow-executor.js +11 -4
- package/dist/parser.d.ts +4 -0
- package/dist/parser.js +69 -0
- package/dist/validator.d.ts +5 -0
- package/dist/validator.js +207 -14
- package/docs/reference/advanced-annotations.md +71 -2
- package/docs/reference/concepts.md +44 -4
- package/docs/reference/debugging.md +33 -0
- package/docs/reference/error-codes.md +7 -0
- package/package.json +1 -1
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -11812,14 +11812,84 @@ var init_signature_parser = __esm({
|
|
|
11812
11812
|
}
|
|
11813
11813
|
});
|
|
11814
11814
|
|
|
11815
|
+
// src/type-checker.ts
|
|
11816
|
+
function isRuntimeCoercible(sourceText, targetText) {
|
|
11817
|
+
const sourceLower = sourceText.toLowerCase();
|
|
11818
|
+
const targetLower = targetText.toLowerCase();
|
|
11819
|
+
for (const [from, to] of SAFE_COERCIONS) {
|
|
11820
|
+
if (sourceLower === from && targetLower === to) {
|
|
11821
|
+
return true;
|
|
11822
|
+
}
|
|
11823
|
+
}
|
|
11824
|
+
return false;
|
|
11825
|
+
}
|
|
11826
|
+
function checkTypeCompatibilityFromStrings(sourceText, targetText) {
|
|
11827
|
+
if (sourceText === targetText) {
|
|
11828
|
+
return {
|
|
11829
|
+
isCompatible: true,
|
|
11830
|
+
reason: "exact",
|
|
11831
|
+
sourceType: sourceText,
|
|
11832
|
+
targetType: targetText
|
|
11833
|
+
};
|
|
11834
|
+
}
|
|
11835
|
+
if (sourceText === "any" || targetText === "any") {
|
|
11836
|
+
return {
|
|
11837
|
+
isCompatible: true,
|
|
11838
|
+
reason: "assignable",
|
|
11839
|
+
sourceType: sourceText,
|
|
11840
|
+
targetType: targetText
|
|
11841
|
+
};
|
|
11842
|
+
}
|
|
11843
|
+
if (isRuntimeCoercible(sourceText, targetText)) {
|
|
11844
|
+
return {
|
|
11845
|
+
isCompatible: true,
|
|
11846
|
+
reason: "coercible",
|
|
11847
|
+
sourceType: sourceText,
|
|
11848
|
+
targetType: targetText
|
|
11849
|
+
};
|
|
11850
|
+
}
|
|
11851
|
+
return {
|
|
11852
|
+
isCompatible: false,
|
|
11853
|
+
reason: "incompatible",
|
|
11854
|
+
sourceType: sourceText,
|
|
11855
|
+
targetType: targetText,
|
|
11856
|
+
errorMessage: `Type '${sourceText}' is not assignable to type '${targetText}'`
|
|
11857
|
+
};
|
|
11858
|
+
}
|
|
11859
|
+
var SAFE_COERCIONS;
|
|
11860
|
+
var init_type_checker = __esm({
|
|
11861
|
+
"src/type-checker.ts"() {
|
|
11862
|
+
"use strict";
|
|
11863
|
+
SAFE_COERCIONS = [
|
|
11864
|
+
["number", "string"],
|
|
11865
|
+
// Number.toString()
|
|
11866
|
+
["boolean", "string"]
|
|
11867
|
+
// Boolean.toString()
|
|
11868
|
+
];
|
|
11869
|
+
}
|
|
11870
|
+
});
|
|
11871
|
+
|
|
11815
11872
|
// src/validator.ts
|
|
11816
|
-
var WorkflowValidator, validator;
|
|
11873
|
+
var DOCS_BASE, ERROR_DOC_URLS, WorkflowValidator, validator;
|
|
11817
11874
|
var init_validator = __esm({
|
|
11818
11875
|
"src/validator.ts"() {
|
|
11819
11876
|
"use strict";
|
|
11820
11877
|
init_constants();
|
|
11821
11878
|
init_string_distance();
|
|
11822
11879
|
init_signature_parser();
|
|
11880
|
+
init_type_checker();
|
|
11881
|
+
DOCS_BASE = "https://docs.flowweaver.dev/reference";
|
|
11882
|
+
ERROR_DOC_URLS = {
|
|
11883
|
+
UNKNOWN_NODE_TYPE: `${DOCS_BASE}/concepts#node-registration`,
|
|
11884
|
+
UNKNOWN_SOURCE_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
11885
|
+
UNKNOWN_TARGET_PORT: `${DOCS_BASE}/concepts#port-architecture`,
|
|
11886
|
+
TYPE_MISMATCH: `${DOCS_BASE}/compilation#type-compatibility`,
|
|
11887
|
+
UNREACHABLE_NODE: `${DOCS_BASE}/concepts#graph-structure`,
|
|
11888
|
+
MISSING_START_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
11889
|
+
MISSING_EXIT_CONNECTION: `${DOCS_BASE}/concepts#start-and-exit`,
|
|
11890
|
+
INFERRED_NODE_TYPE: `${DOCS_BASE}/node-conversion`,
|
|
11891
|
+
DUPLICATE_CONNECTION: `${DOCS_BASE}/concepts#connections`
|
|
11892
|
+
};
|
|
11823
11893
|
WorkflowValidator = class {
|
|
11824
11894
|
errors = [];
|
|
11825
11895
|
warnings = [];
|
|
@@ -11947,6 +12017,11 @@ var init_validator = __esm({
|
|
|
11947
12017
|
return true;
|
|
11948
12018
|
});
|
|
11949
12019
|
}
|
|
12020
|
+
for (const diag of [...this.errors, ...this.warnings]) {
|
|
12021
|
+
if (!diag.docUrl && ERROR_DOC_URLS[diag.code]) {
|
|
12022
|
+
diag.docUrl = ERROR_DOC_URLS[diag.code];
|
|
12023
|
+
}
|
|
12024
|
+
}
|
|
11950
12025
|
return {
|
|
11951
12026
|
valid: this.errors.length === 0,
|
|
11952
12027
|
errors: this.errors,
|
|
@@ -12170,11 +12245,13 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12170
12245
|
}
|
|
12171
12246
|
const sourceType = sourcePortDef.dataType;
|
|
12172
12247
|
const targetType = targetPortDef.dataType;
|
|
12248
|
+
const sourceTsType = sourcePortDef.tsType;
|
|
12249
|
+
const targetTsType = targetPortDef.tsType;
|
|
12173
12250
|
if (sourceType === "STEP" && targetType !== "STEP") {
|
|
12174
12251
|
this.errors.push({
|
|
12175
12252
|
type: "error",
|
|
12176
12253
|
code: "STEP_PORT_TYPE_MISMATCH",
|
|
12177
|
-
message: `STEP port "${fromPort}" on node "${fromNode}" cannot connect to non-STEP port "${toPort}" (${targetType}) on node "${toNode}"`,
|
|
12254
|
+
message: `STEP port "${fromPort}" on node "${fromNode}" cannot connect to non-STEP port "${toPort}" (${this.formatType(targetType, targetTsType)}) on node "${toNode}"`,
|
|
12178
12255
|
connection: conn,
|
|
12179
12256
|
location: connLocation
|
|
12180
12257
|
});
|
|
@@ -12184,7 +12261,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12184
12261
|
this.errors.push({
|
|
12185
12262
|
type: "error",
|
|
12186
12263
|
code: "STEP_PORT_TYPE_MISMATCH",
|
|
12187
|
-
message: `Non-STEP port "${fromPort}" (${sourceType}) on node "${fromNode}" cannot connect to STEP port "${toPort}" on node "${toNode}"`,
|
|
12264
|
+
message: `Non-STEP port "${fromPort}" (${this.formatType(sourceType, sourceTsType)}) on node "${fromNode}" cannot connect to STEP port "${toPort}" on node "${toNode}"`,
|
|
12188
12265
|
connection: conn,
|
|
12189
12266
|
location: connLocation
|
|
12190
12267
|
});
|
|
@@ -12195,14 +12272,19 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12195
12272
|
}
|
|
12196
12273
|
if (sourceType === targetType) {
|
|
12197
12274
|
if (sourceType === "OBJECT" && sourcePortDef.tsType && targetPortDef.tsType) {
|
|
12198
|
-
|
|
12199
|
-
|
|
12200
|
-
|
|
12201
|
-
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
|
|
12205
|
-
|
|
12275
|
+
const normalizedSource = this.normalizeTypeString(sourcePortDef.tsType);
|
|
12276
|
+
const normalizedTarget = this.normalizeTypeString(targetPortDef.tsType);
|
|
12277
|
+
if (normalizedSource !== normalizedTarget) {
|
|
12278
|
+
const compat = checkTypeCompatibilityFromStrings(sourcePortDef.tsType, targetPortDef.tsType);
|
|
12279
|
+
if (!compat.isCompatible) {
|
|
12280
|
+
this.warnings.push({
|
|
12281
|
+
type: "warning",
|
|
12282
|
+
code: "OBJECT_TYPE_MISMATCH",
|
|
12283
|
+
message: `Structural type mismatch: ${fromNode}.${fromPort} outputs "${sourcePortDef.tsType}" but ${toNode}.${toPort} expects "${targetPortDef.tsType}". Verify the object shapes are compatible.`,
|
|
12284
|
+
connection: conn,
|
|
12285
|
+
location: connLocation
|
|
12286
|
+
});
|
|
12287
|
+
}
|
|
12206
12288
|
}
|
|
12207
12289
|
}
|
|
12208
12290
|
return;
|
|
@@ -12231,7 +12313,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12231
12313
|
{
|
|
12232
12314
|
type: "warning",
|
|
12233
12315
|
code: "LOSSY_TYPE_COERCION",
|
|
12234
|
-
message: `Lossy type coercion from ${sourceType} to ${targetType} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}. Add @strictTypes to your workflow annotation to enforce type safety.`,
|
|
12316
|
+
message: `Lossy type coercion from ${this.formatType(sourceType, sourceTsType)} to ${this.formatType(targetType, targetTsType)} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}. Add @strictTypes to your workflow annotation to enforce type safety.`,
|
|
12235
12317
|
connection: conn,
|
|
12236
12318
|
location: connLocation
|
|
12237
12319
|
},
|
|
@@ -12256,7 +12338,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12256
12338
|
{
|
|
12257
12339
|
type: "warning",
|
|
12258
12340
|
code: "UNUSUAL_TYPE_COERCION",
|
|
12259
|
-
message: `Unusual type coercion from ${sourceType} to ${targetType} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}.`,
|
|
12341
|
+
message: `Unusual type coercion from ${this.formatType(sourceType, sourceTsType)} to ${this.formatType(targetType, targetTsType)} in connection ${fromNode}.${fromPort} \u2192 ${toNode}.${toPort}. ${reason}.`,
|
|
12260
12342
|
connection: conn,
|
|
12261
12343
|
location: connLocation
|
|
12262
12344
|
},
|
|
@@ -12269,7 +12351,7 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12269
12351
|
{
|
|
12270
12352
|
type: "warning",
|
|
12271
12353
|
code: "TYPE_MISMATCH",
|
|
12272
|
-
message: `Type mismatch in connection ${fromNode}.${fromPort} (${sourceType}) \u2192 ${toNode}.${toPort} (${targetType}). Runtime coercion will be attempted.`,
|
|
12354
|
+
message: `Type mismatch in connection ${fromNode}.${fromPort} (${this.formatType(sourceType, sourceTsType)}) \u2192 ${toNode}.${toPort} (${this.formatType(targetType, targetTsType)}). Runtime coercion will be attempted.`,
|
|
12273
12355
|
connection: conn,
|
|
12274
12356
|
location: connLocation
|
|
12275
12357
|
},
|
|
@@ -12704,6 +12786,30 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12704
12786
|
if (portDef.scope) scopeNames.add(portDef.scope);
|
|
12705
12787
|
}
|
|
12706
12788
|
if (scopeNames.size === 0) continue;
|
|
12789
|
+
for (const conn of workflow.connections) {
|
|
12790
|
+
if (conn.from.scope && conn.from.node === instance.id) {
|
|
12791
|
+
if (!scopeNames.has(conn.from.scope)) {
|
|
12792
|
+
this.errors.push({
|
|
12793
|
+
type: "error",
|
|
12794
|
+
code: "SCOPE_WRONG_SCOPE_NAME",
|
|
12795
|
+
message: `Connection from "${instance.id}.${conn.from.port}" uses scope qualifier ":${conn.from.scope}" but node "${instance.id}" does not define scope "${conn.from.scope}". Available scopes: ${[...scopeNames].join(", ")}.`,
|
|
12796
|
+
connection: conn,
|
|
12797
|
+
location: this.getConnectionLocation(conn)
|
|
12798
|
+
});
|
|
12799
|
+
}
|
|
12800
|
+
}
|
|
12801
|
+
if (conn.to.scope && conn.to.node === instance.id) {
|
|
12802
|
+
if (!scopeNames.has(conn.to.scope)) {
|
|
12803
|
+
this.errors.push({
|
|
12804
|
+
type: "error",
|
|
12805
|
+
code: "SCOPE_WRONG_SCOPE_NAME",
|
|
12806
|
+
message: `Connection to "${instance.id}.${conn.to.port}" uses scope qualifier ":${conn.to.scope}" but node "${instance.id}" does not define scope "${conn.to.scope}". Available scopes: ${[...scopeNames].join(", ")}.`,
|
|
12807
|
+
connection: conn,
|
|
12808
|
+
location: this.getConnectionLocation(conn)
|
|
12809
|
+
});
|
|
12810
|
+
}
|
|
12811
|
+
}
|
|
12812
|
+
}
|
|
12707
12813
|
for (const scopeName of scopeNames) {
|
|
12708
12814
|
const childIds = [];
|
|
12709
12815
|
for (const child of workflow.instances) {
|
|
@@ -12715,6 +12821,108 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12715
12821
|
const scopedConnections = workflow.connections.filter(
|
|
12716
12822
|
(conn) => conn.from.scope === scopeName && conn.from.node === instance.id || conn.to.scope === scopeName && conn.to.node === instance.id || conn.from.scope === scopeName && childIds.includes(conn.from.node) || conn.to.scope === scopeName && childIds.includes(conn.to.node)
|
|
12717
12823
|
);
|
|
12824
|
+
for (const conn of scopedConnections) {
|
|
12825
|
+
if (conn.from.node === instance.id && conn.from.scope === scopeName) {
|
|
12826
|
+
const portDef = nodeType.outputs[conn.from.port];
|
|
12827
|
+
if (!portDef) {
|
|
12828
|
+
const availablePorts = Object.entries(nodeType.outputs).filter(([, p]) => p.scope === scopeName).map(([n]) => n);
|
|
12829
|
+
this.errors.push({
|
|
12830
|
+
type: "error",
|
|
12831
|
+
code: "SCOPE_UNKNOWN_PORT",
|
|
12832
|
+
message: `Scoped connection references non-existent output port "${conn.from.port}" on "${instance.id}" in scope "${scopeName}". Available scoped outputs: ${availablePorts.join(", ") || "none"}.`,
|
|
12833
|
+
connection: conn,
|
|
12834
|
+
location: this.getConnectionLocation(conn)
|
|
12835
|
+
});
|
|
12836
|
+
} else if (portDef.scope !== scopeName) {
|
|
12837
|
+
this.errors.push({
|
|
12838
|
+
type: "error",
|
|
12839
|
+
code: "SCOPE_UNKNOWN_PORT",
|
|
12840
|
+
message: `Output port "${conn.from.port}" on "${instance.id}" is not a scoped port of scope "${scopeName}"${portDef.scope ? ` (it belongs to scope "${portDef.scope}")` : " (it is an unscoped port)"}.`,
|
|
12841
|
+
connection: conn,
|
|
12842
|
+
location: this.getConnectionLocation(conn)
|
|
12843
|
+
});
|
|
12844
|
+
}
|
|
12845
|
+
}
|
|
12846
|
+
if (conn.to.node === instance.id && conn.to.scope === scopeName) {
|
|
12847
|
+
const portDef = nodeType.inputs[conn.to.port];
|
|
12848
|
+
if (!portDef) {
|
|
12849
|
+
const availablePorts = Object.entries(nodeType.inputs).filter(([, p]) => p.scope === scopeName).map(([n]) => n);
|
|
12850
|
+
this.errors.push({
|
|
12851
|
+
type: "error",
|
|
12852
|
+
code: "SCOPE_UNKNOWN_PORT",
|
|
12853
|
+
message: `Scoped connection references non-existent input port "${conn.to.port}" on "${instance.id}" in scope "${scopeName}". Available scoped inputs: ${availablePorts.join(", ") || "none"}.`,
|
|
12854
|
+
connection: conn,
|
|
12855
|
+
location: this.getConnectionLocation(conn)
|
|
12856
|
+
});
|
|
12857
|
+
} else if (portDef.scope !== scopeName) {
|
|
12858
|
+
this.errors.push({
|
|
12859
|
+
type: "error",
|
|
12860
|
+
code: "SCOPE_UNKNOWN_PORT",
|
|
12861
|
+
message: `Input port "${conn.to.port}" on "${instance.id}" is not a scoped port of scope "${scopeName}"${portDef.scope ? ` (it belongs to scope "${portDef.scope}")` : " (it is an unscoped port)"}.`,
|
|
12862
|
+
connection: conn,
|
|
12863
|
+
location: this.getConnectionLocation(conn)
|
|
12864
|
+
});
|
|
12865
|
+
}
|
|
12866
|
+
}
|
|
12867
|
+
}
|
|
12868
|
+
for (const conn of scopedConnections) {
|
|
12869
|
+
if (conn.from.scope === scopeName && childIds.includes(conn.from.node)) {
|
|
12870
|
+
if (conn.to.node !== instance.id && !childIds.includes(conn.to.node)) {
|
|
12871
|
+
this.errors.push({
|
|
12872
|
+
type: "error",
|
|
12873
|
+
code: "SCOPE_CONNECTION_OUTSIDE",
|
|
12874
|
+
message: `Scoped connection from "${conn.from.node}.${conn.from.port}" targets "${conn.to.node}" which is not inside scope "${scopeName}" of "${instance.id}".`,
|
|
12875
|
+
connection: conn,
|
|
12876
|
+
location: this.getConnectionLocation(conn)
|
|
12877
|
+
});
|
|
12878
|
+
}
|
|
12879
|
+
}
|
|
12880
|
+
if (conn.to.scope === scopeName && childIds.includes(conn.to.node)) {
|
|
12881
|
+
if (conn.from.node !== instance.id && !childIds.includes(conn.from.node)) {
|
|
12882
|
+
this.errors.push({
|
|
12883
|
+
type: "error",
|
|
12884
|
+
code: "SCOPE_CONNECTION_OUTSIDE",
|
|
12885
|
+
message: `Scoped connection to "${conn.to.node}.${conn.to.port}" sources from "${conn.from.node}" which is not inside scope "${scopeName}" of "${instance.id}".`,
|
|
12886
|
+
connection: conn,
|
|
12887
|
+
location: this.getConnectionLocation(conn)
|
|
12888
|
+
});
|
|
12889
|
+
}
|
|
12890
|
+
}
|
|
12891
|
+
}
|
|
12892
|
+
for (const conn of scopedConnections) {
|
|
12893
|
+
if (conn.from.node === instance.id && conn.from.scope === scopeName) {
|
|
12894
|
+
const parentPort = nodeType.outputs[conn.from.port];
|
|
12895
|
+
const childType = instanceMap.get(conn.to.node);
|
|
12896
|
+
const childPort = childType?.inputs[conn.to.port];
|
|
12897
|
+
if (parentPort && childPort && parentPort.dataType !== "STEP" && childPort.dataType !== "STEP") {
|
|
12898
|
+
if (parentPort.dataType !== childPort.dataType && parentPort.dataType !== "ANY" && childPort.dataType !== "ANY") {
|
|
12899
|
+
this.warnings.push({
|
|
12900
|
+
type: "warning",
|
|
12901
|
+
code: "SCOPE_PORT_TYPE_MISMATCH",
|
|
12902
|
+
message: `Type mismatch in scope "${scopeName}": "${instance.id}.${conn.from.port}" outputs ${this.formatType(parentPort.dataType, parentPort.tsType)} but "${conn.to.node}.${conn.to.port}" expects ${this.formatType(childPort.dataType, childPort.tsType)}.`,
|
|
12903
|
+
connection: conn,
|
|
12904
|
+
location: this.getConnectionLocation(conn)
|
|
12905
|
+
});
|
|
12906
|
+
}
|
|
12907
|
+
}
|
|
12908
|
+
}
|
|
12909
|
+
if (conn.to.node === instance.id && conn.to.scope === scopeName) {
|
|
12910
|
+
const parentPort = nodeType.inputs[conn.to.port];
|
|
12911
|
+
const childType = instanceMap.get(conn.from.node);
|
|
12912
|
+
const childPort = childType?.outputs[conn.from.port];
|
|
12913
|
+
if (parentPort && childPort && parentPort.dataType !== "STEP" && childPort.dataType !== "STEP") {
|
|
12914
|
+
if (parentPort.dataType !== childPort.dataType && parentPort.dataType !== "ANY" && childPort.dataType !== "ANY") {
|
|
12915
|
+
this.warnings.push({
|
|
12916
|
+
type: "warning",
|
|
12917
|
+
code: "SCOPE_PORT_TYPE_MISMATCH",
|
|
12918
|
+
message: `Type mismatch in scope "${scopeName}": "${conn.from.node}.${conn.from.port}" outputs ${this.formatType(childPort.dataType, childPort.tsType)} but "${instance.id}.${conn.to.port}" expects ${this.formatType(parentPort.dataType, parentPort.tsType)}.`,
|
|
12919
|
+
connection: conn,
|
|
12920
|
+
location: this.getConnectionLocation(conn)
|
|
12921
|
+
});
|
|
12922
|
+
}
|
|
12923
|
+
}
|
|
12924
|
+
}
|
|
12925
|
+
}
|
|
12718
12926
|
for (const childId of childIds) {
|
|
12719
12927
|
const childType = instanceMap.get(childId);
|
|
12720
12928
|
if (!childType) continue;
|
|
@@ -12758,9 +12966,36 @@ Add '@param ${fromPort}' to the workflow JSDoc and include it in the params obje
|
|
|
12758
12966
|
});
|
|
12759
12967
|
}
|
|
12760
12968
|
}
|
|
12969
|
+
for (const childId of childIds) {
|
|
12970
|
+
const hasConnectionFromParent = scopedConnections.some(
|
|
12971
|
+
(conn) => conn.from.node === instance.id && conn.from.scope === scopeName && conn.to.node === childId
|
|
12972
|
+
);
|
|
12973
|
+
const hasConnectionToParent = scopedConnections.some(
|
|
12974
|
+
(conn) => conn.to.node === instance.id && conn.to.scope === scopeName && conn.from.node === childId
|
|
12975
|
+
);
|
|
12976
|
+
if (!hasConnectionFromParent && !hasConnectionToParent) {
|
|
12977
|
+
this.warnings.push({
|
|
12978
|
+
type: "warning",
|
|
12979
|
+
code: "SCOPE_ORPHANED_CHILD",
|
|
12980
|
+
message: `Child node "${childId}" is declared inside scope "${scopeName}" of "${instance.id}" but has no scoped connections to or from the parent. It is disconnected from the scope's data flow.`,
|
|
12981
|
+
node: childId,
|
|
12982
|
+
location: this.getInstanceLocation(workflow, childId)
|
|
12983
|
+
});
|
|
12984
|
+
}
|
|
12985
|
+
}
|
|
12761
12986
|
}
|
|
12762
12987
|
}
|
|
12763
12988
|
}
|
|
12989
|
+
/**
|
|
12990
|
+
* Format a type for display in error messages.
|
|
12991
|
+
* Prefers the structural TypeScript type when available, falling back to the enum name.
|
|
12992
|
+
*/
|
|
12993
|
+
formatType(dataType, tsType) {
|
|
12994
|
+
if (tsType) {
|
|
12995
|
+
return `${tsType} (${dataType})`;
|
|
12996
|
+
}
|
|
12997
|
+
return dataType;
|
|
12998
|
+
}
|
|
12764
12999
|
normalizeTypeString(type2) {
|
|
12765
13000
|
let n = type2;
|
|
12766
13001
|
n = n.replace(/\s+/g, "");
|
|
@@ -28985,7 +29220,29 @@ function generateNodeCallWithContext(instance, nodeType, workflow, _availableVar
|
|
|
28985
29220
|
});
|
|
28986
29221
|
const resultVar = `${safeId}Result`;
|
|
28987
29222
|
const awaitKeyword = nodeType.isAsync ? "await " : "";
|
|
28988
|
-
if (nodeType.
|
|
29223
|
+
if (nodeType.variant === "COERCION") {
|
|
29224
|
+
const coerceExprMap = {
|
|
29225
|
+
__fw_toString: "String",
|
|
29226
|
+
__fw_toNumber: "Number",
|
|
29227
|
+
__fw_toBoolean: "Boolean",
|
|
29228
|
+
__fw_toJSON: "JSON.stringify",
|
|
29229
|
+
__fw_parseJSON: "JSON.parse"
|
|
29230
|
+
};
|
|
29231
|
+
const coerceExpr = coerceExprMap[functionName] || "String";
|
|
29232
|
+
const valueArg = args[0] || "undefined";
|
|
29233
|
+
lines.push(
|
|
29234
|
+
`${indent} const ${resultVar} = ${coerceExpr}(${valueArg});`
|
|
29235
|
+
);
|
|
29236
|
+
lines.push(
|
|
29237
|
+
`${indent} ${setCall}({ id: '${instanceId}', portName: 'result', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, ${resultVar});`
|
|
29238
|
+
);
|
|
29239
|
+
lines.push(
|
|
29240
|
+
`${indent} ${setCall}({ id: '${instanceId}', portName: 'onSuccess', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, true);`
|
|
29241
|
+
);
|
|
29242
|
+
lines.push(
|
|
29243
|
+
`${indent} ${setCall}({ id: '${instanceId}', portName: 'onFailure', executionIndex: ${safeId}Idx, nodeTypeName: '${functionName}' }, false);`
|
|
29244
|
+
);
|
|
29245
|
+
} else if (nodeType.expression) {
|
|
28989
29246
|
lines.push(
|
|
28990
29247
|
`${indent} const ${resultVar} = ${awaitKeyword}${functionName}(${args.join(", ")});`
|
|
28991
29248
|
);
|
|
@@ -30510,6 +30767,9 @@ var AnnotationGenerator = class {
|
|
|
30510
30767
|
if (nodeType.variant === "MAP_ITERATOR") {
|
|
30511
30768
|
return;
|
|
30512
30769
|
}
|
|
30770
|
+
if (nodeType.variant === "COERCION") {
|
|
30771
|
+
return;
|
|
30772
|
+
}
|
|
30513
30773
|
lines.push(
|
|
30514
30774
|
...this.generateNodeTypeAnnotation(
|
|
30515
30775
|
nodeType,
|
|
@@ -30534,7 +30794,9 @@ var AnnotationGenerator = class {
|
|
|
30534
30794
|
}
|
|
30535
30795
|
lines.push("/**");
|
|
30536
30796
|
if (includeComments && nodeType.description) {
|
|
30537
|
-
|
|
30797
|
+
for (const descLine of nodeType.description.split("\n")) {
|
|
30798
|
+
lines.push(` * ${descLine}`);
|
|
30799
|
+
}
|
|
30538
30800
|
lines.push(` *`);
|
|
30539
30801
|
}
|
|
30540
30802
|
lines.push(" * @flowWeaver nodeType");
|
|
@@ -30618,12 +30880,15 @@ var AnnotationGenerator = class {
|
|
|
30618
30880
|
const macroInstanceIds = /* @__PURE__ */ new Set();
|
|
30619
30881
|
const macroChildIds = /* @__PURE__ */ new Set();
|
|
30620
30882
|
const macroScopeNames = /* @__PURE__ */ new Set();
|
|
30883
|
+
const coerceInstanceIds = /* @__PURE__ */ new Set();
|
|
30621
30884
|
if (workflow.macros && workflow.macros.length > 0) {
|
|
30622
30885
|
for (const macro of workflow.macros) {
|
|
30623
30886
|
if (macro.type === "map") {
|
|
30624
30887
|
macroInstanceIds.add(macro.instanceId);
|
|
30625
30888
|
macroChildIds.add(macro.childId);
|
|
30626
30889
|
macroScopeNames.add(`${macro.instanceId}.iterate`);
|
|
30890
|
+
} else if (macro.type === "coerce") {
|
|
30891
|
+
coerceInstanceIds.add(macro.instanceId);
|
|
30627
30892
|
}
|
|
30628
30893
|
}
|
|
30629
30894
|
}
|
|
@@ -30673,6 +30938,7 @@ var AnnotationGenerator = class {
|
|
|
30673
30938
|
}
|
|
30674
30939
|
workflow.instances.forEach((instance) => {
|
|
30675
30940
|
if (macroInstanceIds.has(instance.id)) return;
|
|
30941
|
+
if (coerceInstanceIds.has(instance.id)) return;
|
|
30676
30942
|
if (macroChildIds.has(instance.id) && instance.parent) {
|
|
30677
30943
|
const stripped = { ...instance, parent: void 0 };
|
|
30678
30944
|
lines.push(generateNodeInstanceTag(stripped));
|
|
@@ -30717,6 +30983,10 @@ var AnnotationGenerator = class {
|
|
|
30717
30983
|
const srcs = macro.sources.map((s) => s.port ? `${s.node}.${s.port}` : s.node).join(", ");
|
|
30718
30984
|
const tgt = `${macro.target.node}.${macro.target.port}`;
|
|
30719
30985
|
lines.push(` * @fanIn ${srcs} -> ${tgt}`);
|
|
30986
|
+
} else if (macro.type === "coerce") {
|
|
30987
|
+
const src = `${macro.source.node}.${macro.source.port}`;
|
|
30988
|
+
const tgt = `${macro.target.node}.${macro.target.port}`;
|
|
30989
|
+
lines.push(` * @coerce ${macro.instanceId} ${src} -> ${tgt} as ${macro.targetType}`);
|
|
30720
30990
|
}
|
|
30721
30991
|
}
|
|
30722
30992
|
}
|
|
@@ -30872,6 +31142,11 @@ function isConnectionCoveredByMacroStatic(conn, macros) {
|
|
|
30872
31142
|
}
|
|
30873
31143
|
}
|
|
30874
31144
|
}
|
|
31145
|
+
} else if (macro.type === "coerce") {
|
|
31146
|
+
if (conn.from.scope || conn.to.scope) continue;
|
|
31147
|
+
if (conn.to.node === macro.instanceId || conn.from.node === macro.instanceId) {
|
|
31148
|
+
return true;
|
|
31149
|
+
}
|
|
30875
31150
|
}
|
|
30876
31151
|
}
|
|
30877
31152
|
return false;
|
|
@@ -31254,6 +31529,9 @@ function replaceWorkflowFunctionBody(source, functionName, newBody) {
|
|
|
31254
31529
|
return { code: source, changed: false };
|
|
31255
31530
|
}
|
|
31256
31531
|
const closeBraceIdx = functionNode.body.end - 1;
|
|
31532
|
+
if (closeBraceIdx <= openBraceIdx) {
|
|
31533
|
+
return { code: source, changed: false };
|
|
31534
|
+
}
|
|
31257
31535
|
const before = source.slice(0, openBraceIdx + 1);
|
|
31258
31536
|
const after = source.slice(closeBraceIdx);
|
|
31259
31537
|
const newBodyWithMarkers = [
|
|
@@ -39792,17 +40070,17 @@ var CstVisitorDefinitionError;
|
|
|
39792
40070
|
CstVisitorDefinitionError2[CstVisitorDefinitionError2["REDUNDANT_METHOD"] = 0] = "REDUNDANT_METHOD";
|
|
39793
40071
|
CstVisitorDefinitionError2[CstVisitorDefinitionError2["MISSING_METHOD"] = 1] = "MISSING_METHOD";
|
|
39794
40072
|
})(CstVisitorDefinitionError || (CstVisitorDefinitionError = {}));
|
|
39795
|
-
function validateVisitor(
|
|
39796
|
-
const missingErrors = validateMissingCstMethods(
|
|
40073
|
+
function validateVisitor(visitorInstance11, ruleNames) {
|
|
40074
|
+
const missingErrors = validateMissingCstMethods(visitorInstance11, ruleNames);
|
|
39797
40075
|
return missingErrors;
|
|
39798
40076
|
}
|
|
39799
|
-
function validateMissingCstMethods(
|
|
40077
|
+
function validateMissingCstMethods(visitorInstance11, ruleNames) {
|
|
39800
40078
|
const missingRuleNames = filter_default(ruleNames, (currRuleName) => {
|
|
39801
|
-
return isFunction_default(
|
|
40079
|
+
return isFunction_default(visitorInstance11[currRuleName]) === false;
|
|
39802
40080
|
});
|
|
39803
40081
|
const errors2 = map_default(missingRuleNames, (currRuleName) => {
|
|
39804
40082
|
return {
|
|
39805
|
-
msg: `Missing visitor method: <${currRuleName}> on ${
|
|
40083
|
+
msg: `Missing visitor method: <${currRuleName}> on ${visitorInstance11.constructor.name} CST Visitor.`,
|
|
39806
40084
|
type: CstVisitorDefinitionError.MISSING_METHOD,
|
|
39807
40085
|
methodName: currRuleName
|
|
39808
40086
|
};
|
|
@@ -41207,7 +41485,7 @@ var Parser = class _Parser {
|
|
|
41207
41485
|
/**
|
|
41208
41486
|
* @deprecated use the **instance** method with the same name instead
|
|
41209
41487
|
*/
|
|
41210
|
-
static performSelfAnalysis(
|
|
41488
|
+
static performSelfAnalysis(parserInstance11) {
|
|
41211
41489
|
throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.");
|
|
41212
41490
|
}
|
|
41213
41491
|
performSelfAnalysis() {
|
|
@@ -41418,6 +41696,10 @@ var FanInTag = createToken({
|
|
|
41418
41696
|
name: "FanInTag",
|
|
41419
41697
|
pattern: /@fanIn\b/
|
|
41420
41698
|
});
|
|
41699
|
+
var CoerceTag = createToken({
|
|
41700
|
+
name: "CoerceTag",
|
|
41701
|
+
pattern: /@coerce\b/
|
|
41702
|
+
});
|
|
41421
41703
|
var TriggerTag = createToken({
|
|
41422
41704
|
name: "TriggerTag",
|
|
41423
41705
|
pattern: /@trigger\b/
|
|
@@ -41442,6 +41724,10 @@ var OverKeyword = createToken({
|
|
|
41442
41724
|
name: "OverKeyword",
|
|
41443
41725
|
pattern: /over\b/
|
|
41444
41726
|
});
|
|
41727
|
+
var AsKeyword = createToken({
|
|
41728
|
+
name: "AsKeyword",
|
|
41729
|
+
pattern: /as\b/
|
|
41730
|
+
});
|
|
41445
41731
|
var ParamTag = createToken({
|
|
41446
41732
|
name: "ParamTag",
|
|
41447
41733
|
pattern: /@param\b/
|
|
@@ -41661,6 +41947,7 @@ var allTokens = [
|
|
|
41661
41947
|
PathTag,
|
|
41662
41948
|
FanOutTag,
|
|
41663
41949
|
FanInTag,
|
|
41950
|
+
CoerceTag,
|
|
41664
41951
|
TriggerTag,
|
|
41665
41952
|
CancelOnTag,
|
|
41666
41953
|
RetriesTag,
|
|
@@ -41694,6 +41981,7 @@ var allTokens = [
|
|
|
41694
41981
|
PeriodEq,
|
|
41695
41982
|
// Keywords (before Identifier)
|
|
41696
41983
|
OverKeyword,
|
|
41984
|
+
AsKeyword,
|
|
41697
41985
|
MinimizedKeyword,
|
|
41698
41986
|
TopKeyword,
|
|
41699
41987
|
BottomKeyword,
|
|
@@ -41870,6 +42158,7 @@ var PortParser = class extends CstParser {
|
|
|
41870
42158
|
{ ALT: () => this.CONSUME(Asterisk) },
|
|
41871
42159
|
// Keywords that can appear in description text
|
|
41872
42160
|
{ ALT: () => this.CONSUME(OverKeyword) },
|
|
42161
|
+
{ ALT: () => this.CONSUME(AsKeyword) },
|
|
41873
42162
|
{ ALT: () => this.CONSUME(TopKeyword) },
|
|
41874
42163
|
{ ALT: () => this.CONSUME(BottomKeyword) },
|
|
41875
42164
|
{ ALT: () => this.CONSUME(TrueKeyword) },
|
|
@@ -43021,6 +43310,77 @@ function getFanGrammar() {
|
|
|
43021
43310
|
return parserInstance8.getSerializedGastProductions();
|
|
43022
43311
|
}
|
|
43023
43312
|
|
|
43313
|
+
// src/chevrotain-parser/coerce-parser.ts
|
|
43314
|
+
var VALID_COERCE_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "json", "object"]);
|
|
43315
|
+
var CoerceParser = class extends CstParser {
|
|
43316
|
+
constructor() {
|
|
43317
|
+
super(allTokens);
|
|
43318
|
+
this.performSelfAnalysis();
|
|
43319
|
+
}
|
|
43320
|
+
// @coerce Identifier portRef Arrow portRef AsKeyword Identifier
|
|
43321
|
+
coerceLine = this.RULE("coerceLine", () => {
|
|
43322
|
+
this.CONSUME(CoerceTag);
|
|
43323
|
+
this.CONSUME(Identifier, { LABEL: "instanceId" });
|
|
43324
|
+
this.SUBRULE(this.portRef, { LABEL: "source" });
|
|
43325
|
+
this.CONSUME(Arrow);
|
|
43326
|
+
this.SUBRULE2(this.portRef, { LABEL: "target" });
|
|
43327
|
+
this.CONSUME(AsKeyword);
|
|
43328
|
+
this.CONSUME2(Identifier, { LABEL: "typeName" });
|
|
43329
|
+
});
|
|
43330
|
+
// portRef: Identifier Dot Identifier
|
|
43331
|
+
portRef = this.RULE("portRef", () => {
|
|
43332
|
+
this.CONSUME(Identifier, { LABEL: "nodeName" });
|
|
43333
|
+
this.CONSUME(Dot);
|
|
43334
|
+
this.CONSUME2(Identifier, { LABEL: "portName" });
|
|
43335
|
+
});
|
|
43336
|
+
};
|
|
43337
|
+
var parserInstance9 = new CoerceParser();
|
|
43338
|
+
var BaseVisitor9 = parserInstance9.getBaseCstVisitorConstructor();
|
|
43339
|
+
var CoerceVisitor = class extends BaseVisitor9 {
|
|
43340
|
+
constructor() {
|
|
43341
|
+
super();
|
|
43342
|
+
this.validateVisitor();
|
|
43343
|
+
}
|
|
43344
|
+
coerceLine(ctx) {
|
|
43345
|
+
const instanceId = ctx.instanceId[0].image;
|
|
43346
|
+
const source = this.portRef(ctx.source[0].children);
|
|
43347
|
+
const target = this.portRef(ctx.target[0].children);
|
|
43348
|
+
const targetType = ctx.typeName[0].image;
|
|
43349
|
+
return { instanceId, source, target, targetType };
|
|
43350
|
+
}
|
|
43351
|
+
portRef(ctx) {
|
|
43352
|
+
return {
|
|
43353
|
+
node: ctx.nodeName[0].image,
|
|
43354
|
+
port: ctx.portName[0].image
|
|
43355
|
+
};
|
|
43356
|
+
}
|
|
43357
|
+
};
|
|
43358
|
+
var visitorInstance9 = new CoerceVisitor();
|
|
43359
|
+
function parseCoerceLine(input, warnings) {
|
|
43360
|
+
const lexResult = JSDocLexer.tokenize(input);
|
|
43361
|
+
if (lexResult.errors.length > 0 || lexResult.tokens.length === 0) return null;
|
|
43362
|
+
if (lexResult.tokens[0].tokenType !== CoerceTag) return null;
|
|
43363
|
+
parserInstance9.input = lexResult.tokens;
|
|
43364
|
+
const cst = parserInstance9.coerceLine();
|
|
43365
|
+
if (parserInstance9.errors.length > 0) {
|
|
43366
|
+
const truncatedInput = input.length > 80 ? input.substring(0, 80) + "..." : input;
|
|
43367
|
+
warnings.push(
|
|
43368
|
+
`Failed to parse @coerce line: "${truncatedInput}"
|
|
43369
|
+
Error: ${parserInstance9.errors[0].message}
|
|
43370
|
+
Expected format: @coerce instanceId source.port -> target.port as type`
|
|
43371
|
+
);
|
|
43372
|
+
return null;
|
|
43373
|
+
}
|
|
43374
|
+
const result = visitorInstance9.visit(cst);
|
|
43375
|
+
if (!VALID_COERCE_TYPES.has(result.targetType)) {
|
|
43376
|
+
warnings.push(
|
|
43377
|
+
`@coerce: invalid target type "${result.targetType}". Valid types: ${[...VALID_COERCE_TYPES].join(", ")}`
|
|
43378
|
+
);
|
|
43379
|
+
return null;
|
|
43380
|
+
}
|
|
43381
|
+
return result;
|
|
43382
|
+
}
|
|
43383
|
+
|
|
43024
43384
|
// src/chevrotain-parser/trigger-cancel-parser.ts
|
|
43025
43385
|
function stripQuotes(s) {
|
|
43026
43386
|
if (s.startsWith('"') && s.endsWith('"')) {
|
|
@@ -43085,9 +43445,9 @@ var TriggerCancelParser = class extends CstParser {
|
|
|
43085
43445
|
});
|
|
43086
43446
|
});
|
|
43087
43447
|
};
|
|
43088
|
-
var
|
|
43089
|
-
var
|
|
43090
|
-
var TriggerCancelVisitor = class extends
|
|
43448
|
+
var parserInstance10 = new TriggerCancelParser();
|
|
43449
|
+
var BaseVisitor10 = parserInstance10.getBaseCstVisitorConstructor();
|
|
43450
|
+
var TriggerCancelVisitor = class extends BaseVisitor10 {
|
|
43091
43451
|
constructor() {
|
|
43092
43452
|
super();
|
|
43093
43453
|
this.validateVisitor();
|
|
@@ -43130,7 +43490,7 @@ var TriggerCancelVisitor = class extends BaseVisitor9 {
|
|
|
43130
43490
|
return result;
|
|
43131
43491
|
}
|
|
43132
43492
|
};
|
|
43133
|
-
var
|
|
43493
|
+
var visitorInstance10 = new TriggerCancelVisitor();
|
|
43134
43494
|
function parseTriggerLine(input, warnings) {
|
|
43135
43495
|
const lexResult = JSDocLexer.tokenize(input);
|
|
43136
43496
|
if (lexResult.errors.length > 0) {
|
|
@@ -43143,10 +43503,10 @@ function parseTriggerLine(input, warnings) {
|
|
|
43143
43503
|
if (firstToken.tokenType !== TriggerTag) {
|
|
43144
43504
|
return null;
|
|
43145
43505
|
}
|
|
43146
|
-
|
|
43147
|
-
const cst =
|
|
43148
|
-
if (
|
|
43149
|
-
const firstError =
|
|
43506
|
+
parserInstance10.input = lexResult.tokens;
|
|
43507
|
+
const cst = parserInstance10.triggerLine();
|
|
43508
|
+
if (parserInstance10.errors.length > 0) {
|
|
43509
|
+
const firstError = parserInstance10.errors[0];
|
|
43150
43510
|
const truncatedInput = input.length > 60 ? input.substring(0, 60) + "..." : input;
|
|
43151
43511
|
warnings.push(
|
|
43152
43512
|
`Failed to parse trigger line: "${truncatedInput}"
|
|
@@ -43155,7 +43515,7 @@ function parseTriggerLine(input, warnings) {
|
|
|
43155
43515
|
);
|
|
43156
43516
|
return null;
|
|
43157
43517
|
}
|
|
43158
|
-
const result =
|
|
43518
|
+
const result = visitorInstance10.visit(cst);
|
|
43159
43519
|
if (result.cron && !CRON_REGEX.test(result.cron)) {
|
|
43160
43520
|
warnings.push(`Invalid cron expression: "${result.cron}". Expected 5 fields (minute hour day month weekday).`);
|
|
43161
43521
|
}
|
|
@@ -43173,10 +43533,10 @@ function parseCancelOnLine(input, warnings) {
|
|
|
43173
43533
|
if (firstToken.tokenType !== CancelOnTag) {
|
|
43174
43534
|
return null;
|
|
43175
43535
|
}
|
|
43176
|
-
|
|
43177
|
-
const cst =
|
|
43178
|
-
if (
|
|
43179
|
-
const firstError =
|
|
43536
|
+
parserInstance10.input = lexResult.tokens;
|
|
43537
|
+
const cst = parserInstance10.cancelOnLine();
|
|
43538
|
+
if (parserInstance10.errors.length > 0) {
|
|
43539
|
+
const firstError = parserInstance10.errors[0];
|
|
43180
43540
|
const truncatedInput = input.length > 60 ? input.substring(0, 60) + "..." : input;
|
|
43181
43541
|
warnings.push(
|
|
43182
43542
|
`Failed to parse cancelOn line: "${truncatedInput}"
|
|
@@ -43185,10 +43545,10 @@ function parseCancelOnLine(input, warnings) {
|
|
|
43185
43545
|
);
|
|
43186
43546
|
return null;
|
|
43187
43547
|
}
|
|
43188
|
-
return
|
|
43548
|
+
return visitorInstance10.visit(cst);
|
|
43189
43549
|
}
|
|
43190
43550
|
function getTriggerCancelGrammar() {
|
|
43191
|
-
return
|
|
43551
|
+
return parserInstance10.getSerializedGastProductions();
|
|
43192
43552
|
}
|
|
43193
43553
|
function parseThrottleLine(input, warnings) {
|
|
43194
43554
|
const lexResult = JSDocLexer.tokenize(input);
|
|
@@ -43202,10 +43562,10 @@ function parseThrottleLine(input, warnings) {
|
|
|
43202
43562
|
if (firstToken.tokenType !== ThrottleTag) {
|
|
43203
43563
|
return null;
|
|
43204
43564
|
}
|
|
43205
|
-
|
|
43206
|
-
const cst =
|
|
43207
|
-
if (
|
|
43208
|
-
const firstError =
|
|
43565
|
+
parserInstance10.input = lexResult.tokens;
|
|
43566
|
+
const cst = parserInstance10.throttleLine();
|
|
43567
|
+
if (parserInstance10.errors.length > 0) {
|
|
43568
|
+
const firstError = parserInstance10.errors[0];
|
|
43209
43569
|
const truncatedInput = input.length > 60 ? input.substring(0, 60) + "..." : input;
|
|
43210
43570
|
warnings.push(
|
|
43211
43571
|
`Failed to parse throttle line: "${truncatedInput}"
|
|
@@ -43214,7 +43574,7 @@ function parseThrottleLine(input, warnings) {
|
|
|
43214
43574
|
);
|
|
43215
43575
|
return null;
|
|
43216
43576
|
}
|
|
43217
|
-
return
|
|
43577
|
+
return visitorInstance10.visit(cst);
|
|
43218
43578
|
}
|
|
43219
43579
|
|
|
43220
43580
|
// src/chevrotain-parser/grammar-diagrams.ts
|
|
@@ -43632,6 +43992,9 @@ var JSDocParser = class {
|
|
|
43632
43992
|
case "fanIn":
|
|
43633
43993
|
this.parseFanInTag(tag, config2, warnings);
|
|
43634
43994
|
break;
|
|
43995
|
+
case "coerce":
|
|
43996
|
+
this.parseCoerceTag(tag, config2, warnings);
|
|
43997
|
+
break;
|
|
43635
43998
|
case "trigger":
|
|
43636
43999
|
this.parseTriggerTag(tag, config2, warnings);
|
|
43637
44000
|
break;
|
|
@@ -44229,6 +44592,21 @@ var JSDocParser = class {
|
|
|
44229
44592
|
target: { node: result.target.node, port: result.target.port }
|
|
44230
44593
|
});
|
|
44231
44594
|
}
|
|
44595
|
+
parseCoerceTag(tag, config2, warnings) {
|
|
44596
|
+
const comment = tag.getCommentText() || "";
|
|
44597
|
+
const result = parseCoerceLine(`@coerce ${comment}`, warnings);
|
|
44598
|
+
if (!result) {
|
|
44599
|
+
warnings.push(`Invalid @coerce tag format: ${comment}`);
|
|
44600
|
+
return;
|
|
44601
|
+
}
|
|
44602
|
+
config2.coercions = config2.coercions || [];
|
|
44603
|
+
config2.coercions.push({
|
|
44604
|
+
instanceId: result.instanceId,
|
|
44605
|
+
source: result.source,
|
|
44606
|
+
target: result.target,
|
|
44607
|
+
targetType: result.targetType
|
|
44608
|
+
});
|
|
44609
|
+
}
|
|
44232
44610
|
/**
|
|
44233
44611
|
* Parse @trigger tag using Chevrotain parser.
|
|
44234
44612
|
*/
|
|
@@ -44484,6 +44862,49 @@ var LRUCache2 = class {
|
|
|
44484
44862
|
}
|
|
44485
44863
|
};
|
|
44486
44864
|
|
|
44865
|
+
// src/built-in-nodes/coercion-types.ts
|
|
44866
|
+
var COERCE_TYPE_MAP = {
|
|
44867
|
+
string: "__fw_toString",
|
|
44868
|
+
number: "__fw_toNumber",
|
|
44869
|
+
boolean: "__fw_toBoolean",
|
|
44870
|
+
json: "__fw_toJSON",
|
|
44871
|
+
object: "__fw_parseJSON"
|
|
44872
|
+
};
|
|
44873
|
+
function makeCoercionNodeType(name, outputType, outputTsType) {
|
|
44874
|
+
return {
|
|
44875
|
+
type: "NodeType",
|
|
44876
|
+
name,
|
|
44877
|
+
functionName: name,
|
|
44878
|
+
expression: true,
|
|
44879
|
+
isAsync: false,
|
|
44880
|
+
hasSuccessPort: true,
|
|
44881
|
+
hasFailurePort: true,
|
|
44882
|
+
executeWhen: "CONJUNCTION",
|
|
44883
|
+
variant: "COERCION",
|
|
44884
|
+
inputs: {
|
|
44885
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
44886
|
+
value: { dataType: "ANY", label: "Value", tsType: "unknown" }
|
|
44887
|
+
},
|
|
44888
|
+
outputs: {
|
|
44889
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
44890
|
+
onFailure: { dataType: "STEP", label: "On Failure", failure: true, isControlFlow: true },
|
|
44891
|
+
result: { dataType: outputType, label: "Result", tsType: outputTsType }
|
|
44892
|
+
},
|
|
44893
|
+
visuals: {
|
|
44894
|
+
color: "#0d9488",
|
|
44895
|
+
icon: "transform",
|
|
44896
|
+
tags: [{ label: "coerce" }]
|
|
44897
|
+
}
|
|
44898
|
+
};
|
|
44899
|
+
}
|
|
44900
|
+
var COERCION_NODE_TYPES = {
|
|
44901
|
+
__fw_toString: makeCoercionNodeType("__fw_toString", "STRING", "string"),
|
|
44902
|
+
__fw_toNumber: makeCoercionNodeType("__fw_toNumber", "NUMBER", "number"),
|
|
44903
|
+
__fw_toBoolean: makeCoercionNodeType("__fw_toBoolean", "BOOLEAN", "boolean"),
|
|
44904
|
+
__fw_toJSON: makeCoercionNodeType("__fw_toJSON", "STRING", "string"),
|
|
44905
|
+
__fw_parseJSON: makeCoercionNodeType("__fw_parseJSON", "OBJECT", "object")
|
|
44906
|
+
};
|
|
44907
|
+
|
|
44487
44908
|
// src/parser.ts
|
|
44488
44909
|
function externalToAST(ext2) {
|
|
44489
44910
|
const inputs = {};
|
|
@@ -45314,9 +45735,19 @@ ${fn.getText()}` : fn.getText();
|
|
|
45314
45735
|
if (config2.fanIns && config2.fanIns.length > 0) {
|
|
45315
45736
|
this.expandFanInMacros(config2.fanIns, instances, connections, startPorts, exitPorts, macros, errors2);
|
|
45316
45737
|
}
|
|
45738
|
+
if (config2.coercions && config2.coercions.length > 0) {
|
|
45739
|
+
this.expandCoerceMacros(config2.coercions, instances, connections, startPorts, exitPorts, macros, errors2);
|
|
45740
|
+
}
|
|
45317
45741
|
const importedNames = new Set(importedNpmNodeTypes.map((nt) => nt.name));
|
|
45318
45742
|
const dedupedAvailableTypes = allAvailableNodeTypes.filter((nt) => !importedNames.has(nt.name));
|
|
45319
45743
|
const workflowNodeTypes = [...dedupedAvailableTypes, ...importedNpmNodeTypes];
|
|
45744
|
+
for (const inst of instances) {
|
|
45745
|
+
if (inst.nodeType.startsWith("__fw_") && COERCION_NODE_TYPES[inst.nodeType]) {
|
|
45746
|
+
if (!workflowNodeTypes.some((nt) => nt.functionName === inst.nodeType)) {
|
|
45747
|
+
workflowNodeTypes.push(COERCION_NODE_TYPES[inst.nodeType]);
|
|
45748
|
+
}
|
|
45749
|
+
}
|
|
45750
|
+
}
|
|
45320
45751
|
const ui = {};
|
|
45321
45752
|
const startPosition = config2.positions?.["Start"];
|
|
45322
45753
|
if (startPosition) {
|
|
@@ -45922,6 +46353,57 @@ ${fn.getText()}` : fn.getText();
|
|
|
45922
46353
|
});
|
|
45923
46354
|
}
|
|
45924
46355
|
}
|
|
46356
|
+
/**
|
|
46357
|
+
* Expand @coerce macros into synthetic coercion node instances + connections.
|
|
46358
|
+
*/
|
|
46359
|
+
expandCoerceMacros(coerceConfigs, instances, connections, startPorts, exitPorts, macros, errors2) {
|
|
46360
|
+
const instanceIds = new Set(instances.map((i) => i.id));
|
|
46361
|
+
instanceIds.add("Start");
|
|
46362
|
+
instanceIds.add("Exit");
|
|
46363
|
+
for (const config2 of coerceConfigs) {
|
|
46364
|
+
const { instanceId, source, target, targetType } = config2;
|
|
46365
|
+
if (!instanceIds.has(source.node)) {
|
|
46366
|
+
errors2.push(`@coerce: source node "${source.node}" does not exist`);
|
|
46367
|
+
continue;
|
|
46368
|
+
}
|
|
46369
|
+
if (!instanceIds.has(target.node)) {
|
|
46370
|
+
errors2.push(`@coerce: target node "${target.node}" does not exist`);
|
|
46371
|
+
continue;
|
|
46372
|
+
}
|
|
46373
|
+
if (instanceIds.has(instanceId)) {
|
|
46374
|
+
errors2.push(`@coerce: instance ID "${instanceId}" already exists`);
|
|
46375
|
+
continue;
|
|
46376
|
+
}
|
|
46377
|
+
const nodeTypeName = COERCE_TYPE_MAP[targetType];
|
|
46378
|
+
if (!nodeTypeName) {
|
|
46379
|
+
errors2.push(`@coerce: unknown target type "${targetType}"`);
|
|
46380
|
+
continue;
|
|
46381
|
+
}
|
|
46382
|
+
instances.push({
|
|
46383
|
+
type: "NodeInstance",
|
|
46384
|
+
id: instanceId,
|
|
46385
|
+
nodeType: nodeTypeName
|
|
46386
|
+
});
|
|
46387
|
+
instanceIds.add(instanceId);
|
|
46388
|
+
connections.push({
|
|
46389
|
+
type: "Connection",
|
|
46390
|
+
from: { node: source.node, port: source.port },
|
|
46391
|
+
to: { node: instanceId, port: "value" }
|
|
46392
|
+
});
|
|
46393
|
+
connections.push({
|
|
46394
|
+
type: "Connection",
|
|
46395
|
+
from: { node: instanceId, port: "result" },
|
|
46396
|
+
to: { node: target.node, port: target.port }
|
|
46397
|
+
});
|
|
46398
|
+
macros.push({
|
|
46399
|
+
type: "coerce",
|
|
46400
|
+
instanceId,
|
|
46401
|
+
source: { node: source.node, port: source.port },
|
|
46402
|
+
target: { node: target.node, port: target.port },
|
|
46403
|
+
targetType
|
|
46404
|
+
});
|
|
46405
|
+
}
|
|
46406
|
+
}
|
|
45925
46407
|
/**
|
|
45926
46408
|
* Generate automatic connections for @autoConnect workflows.
|
|
45927
46409
|
* Wires nodes in declaration order as a linear pipeline:
|
|
@@ -52545,12 +53027,31 @@ function extractQuoted(message) {
|
|
|
52545
53027
|
return matches ? matches.map((m) => m.replace(/"/g, "")) : [];
|
|
52546
53028
|
}
|
|
52547
53029
|
function extractTypes(message) {
|
|
53030
|
+
const structuralMatch = message.match(/from (.+?) \(\w+\) to (.+?) \(\w+\)/);
|
|
53031
|
+
if (structuralMatch) return { source: structuralMatch[1], target: structuralMatch[2] };
|
|
52548
53032
|
const fromToMatch = message.match(/from (\w+) to (\w+)/i);
|
|
52549
53033
|
if (fromToMatch) return { source: fromToMatch[1], target: fromToMatch[2] };
|
|
53034
|
+
const structuralParenMatch = message.match(/\((.+?) \(\w+\)\) .* \((.+?) \(\w+\)\)/);
|
|
53035
|
+
if (structuralParenMatch) return { source: structuralParenMatch[1], target: structuralParenMatch[2] };
|
|
52550
53036
|
const parenMatch = message.match(/\((\w+)\) .* \((\w+)\)/);
|
|
52551
53037
|
if (parenMatch) return { source: parenMatch[1], target: parenMatch[2] };
|
|
52552
53038
|
return null;
|
|
52553
53039
|
}
|
|
53040
|
+
var COERCE_TARGET_TYPES = {
|
|
53041
|
+
STRING: "string",
|
|
53042
|
+
NUMBER: "number",
|
|
53043
|
+
BOOLEAN: "boolean",
|
|
53044
|
+
OBJECT: "object"
|
|
53045
|
+
};
|
|
53046
|
+
function buildCoerceSuggestion(quoted, targetType) {
|
|
53047
|
+
if (quoted.length < 2) return null;
|
|
53048
|
+
const portRefPattern = /^[\w]+\.[\w]+$/;
|
|
53049
|
+
const portRefs = quoted.filter((q) => portRefPattern.test(q));
|
|
53050
|
+
if (portRefs.length < 2) return null;
|
|
53051
|
+
const coerceType = COERCE_TARGET_TYPES[targetType.toUpperCase()] || targetType.toLowerCase();
|
|
53052
|
+
if (!["string", "number", "boolean", "json", "object"].includes(coerceType)) return null;
|
|
53053
|
+
return `@coerce c1 ${portRefs[0]} -> ${portRefs[1]} as ${coerceType}`;
|
|
53054
|
+
}
|
|
52554
53055
|
function extractCyclePath(message) {
|
|
52555
53056
|
const match2 = message.match(/:\s*(.+ -> .+)/);
|
|
52556
53057
|
return match2 ? match2[1] : null;
|
|
@@ -52653,10 +53154,12 @@ var errorMappers = {
|
|
|
52653
53154
|
const types2 = extractTypes(error2.message);
|
|
52654
53155
|
const source = types2?.source || "unknown";
|
|
52655
53156
|
const target = types2?.target || "unknown";
|
|
53157
|
+
const quoted = extractQuoted(error2.message);
|
|
53158
|
+
const coerceSuggestion = buildCoerceSuggestion(quoted, target);
|
|
52656
53159
|
return {
|
|
52657
53160
|
title: "Type Mismatch",
|
|
52658
53161
|
explanation: `Type mismatch: you're connecting a ${source} to a ${target}. The value will be automatically converted, but this might cause unexpected behavior.`,
|
|
52659
|
-
fix: `Add a
|
|
53162
|
+
fix: coerceSuggestion ? `Add an explicit coercion: \`${coerceSuggestion}\`, change one of the port types, or use @strictTypes to turn this into an error.` : `Add a @coerce annotation between the two ports, change one of the port types, or use @strictTypes to turn this into an error.`,
|
|
52660
53163
|
code: error2.code
|
|
52661
53164
|
};
|
|
52662
53165
|
},
|
|
@@ -52750,10 +53253,12 @@ var errorMappers = {
|
|
|
52750
53253
|
const types2 = extractTypes(error2.message);
|
|
52751
53254
|
const source = types2?.source || "unknown";
|
|
52752
53255
|
const target = types2?.target || "unknown";
|
|
53256
|
+
const quoted = extractQuoted(error2.message);
|
|
53257
|
+
const coerceSuggestion = buildCoerceSuggestion(quoted, target);
|
|
52753
53258
|
return {
|
|
52754
53259
|
title: "Type Incompatible",
|
|
52755
53260
|
explanation: `Type mismatch: ${source} to ${target}. With @strictTypes enabled, this is an error instead of a warning.`,
|
|
52756
|
-
fix: `Add a
|
|
53261
|
+
fix: coerceSuggestion ? `Add an explicit coercion: \`${coerceSuggestion}\`, change one of the port types, or remove @strictTypes to allow implicit coercions.` : `Add a @coerce annotation between the ports, change one of the port types, or remove @strictTypes to allow implicit coercions.`,
|
|
52757
53262
|
code: error2.code
|
|
52758
53263
|
};
|
|
52759
53264
|
},
|
|
@@ -52761,10 +53266,12 @@ var errorMappers = {
|
|
|
52761
53266
|
const types2 = extractTypes(error2.message);
|
|
52762
53267
|
const source = types2?.source || "unknown";
|
|
52763
53268
|
const target = types2?.target || "unknown";
|
|
53269
|
+
const quoted = extractQuoted(error2.message);
|
|
53270
|
+
const coerceSuggestion = buildCoerceSuggestion(quoted, target);
|
|
52764
53271
|
return {
|
|
52765
53272
|
title: "Unusual Type Coercion",
|
|
52766
53273
|
explanation: `Converting ${source} to ${target} is technically valid but semantically unusual and may produce unexpected behavior.`,
|
|
52767
|
-
fix: `
|
|
53274
|
+
fix: coerceSuggestion ? `If intentional, add an explicit coercion: \`${coerceSuggestion}\`, or use @strictTypes to enforce type safety.` : `If intentional, add an explicit @coerce annotation, or use @strictTypes to enforce type safety.`,
|
|
52768
53275
|
code: error2.code
|
|
52769
53276
|
};
|
|
52770
53277
|
},
|
|
@@ -52789,6 +53296,82 @@ var errorMappers = {
|
|
|
52789
53296
|
code: error2.code
|
|
52790
53297
|
};
|
|
52791
53298
|
},
|
|
53299
|
+
SCOPE_MISSING_REQUIRED_INPUT(error2) {
|
|
53300
|
+
const quoted = extractQuoted(error2.message);
|
|
53301
|
+
const childId = quoted[0] || error2.node || "unknown";
|
|
53302
|
+
const portName = quoted[1] || "unknown";
|
|
53303
|
+
const scopeName = quoted[2] || "unknown";
|
|
53304
|
+
const parentId = quoted[3] || "unknown";
|
|
53305
|
+
return {
|
|
53306
|
+
title: "Scope Child Missing Input",
|
|
53307
|
+
explanation: `Child node '${childId}' inside scope '${scopeName}' of '${parentId}' has a required input '${portName}' with no connection.`,
|
|
53308
|
+
fix: `Connect the parent's scoped output to '${childId}.${portName}' within the scope, or mark the port as optional.`,
|
|
53309
|
+
code: error2.code
|
|
53310
|
+
};
|
|
53311
|
+
},
|
|
53312
|
+
SCOPE_UNUSED_INPUT(error2) {
|
|
53313
|
+
const quoted = extractQuoted(error2.message);
|
|
53314
|
+
const portName = quoted[0] || "unknown";
|
|
53315
|
+
const parentId = quoted[1] || error2.node || "unknown";
|
|
53316
|
+
const scopeName = quoted[2] || "unknown";
|
|
53317
|
+
return {
|
|
53318
|
+
title: "Scope Input Unused",
|
|
53319
|
+
explanation: `Scoped input '${portName}' on '${parentId}' expects data back from inner nodes, but nothing connects to it within scope '${scopeName}'.`,
|
|
53320
|
+
fix: `Connect an inner node's output to '${parentId}.${portName}:${scopeName}' to return data from the scope.`,
|
|
53321
|
+
code: error2.code
|
|
53322
|
+
};
|
|
53323
|
+
},
|
|
53324
|
+
SCOPE_WRONG_SCOPE_NAME(error2) {
|
|
53325
|
+
const quoted = extractQuoted(error2.message);
|
|
53326
|
+
const portRef = quoted[0] || "unknown";
|
|
53327
|
+
const nodeName = quoted[2] || "unknown";
|
|
53328
|
+
return {
|
|
53329
|
+
title: "Invalid Scope Qualifier",
|
|
53330
|
+
explanation: `A connection at '${portRef}' uses an invalid scope name. Node '${nodeName}' doesn't define that scope.`,
|
|
53331
|
+
fix: `Check the scope names defined on the node type. Fix the :scopeName qualifier in the @connect annotation to match an existing scope.`,
|
|
53332
|
+
code: error2.code
|
|
53333
|
+
};
|
|
53334
|
+
},
|
|
53335
|
+
SCOPE_CONNECTION_OUTSIDE(error2) {
|
|
53336
|
+
const quoted = extractQuoted(error2.message);
|
|
53337
|
+
return {
|
|
53338
|
+
title: "Scope Connection Leak",
|
|
53339
|
+
explanation: `A scoped connection references a node that is outside the scope boundary. Scoped connections must stay within the scope.`,
|
|
53340
|
+
fix: `Move the target node inside the scope (declare it with the scope parent in @node), or remove the scope qualifier from the connection.`,
|
|
53341
|
+
code: error2.code
|
|
53342
|
+
};
|
|
53343
|
+
},
|
|
53344
|
+
SCOPE_PORT_TYPE_MISMATCH(error2) {
|
|
53345
|
+
const quoted = extractQuoted(error2.message);
|
|
53346
|
+
return {
|
|
53347
|
+
title: "Scope Port Type Mismatch",
|
|
53348
|
+
explanation: `Type mismatch within scope '${quoted[0] || "unknown"}'. The parent's scoped port and the child's port have incompatible types.`,
|
|
53349
|
+
fix: `Change one of the port types to match, or add a transformation node inside the scope.`,
|
|
53350
|
+
code: error2.code
|
|
53351
|
+
};
|
|
53352
|
+
},
|
|
53353
|
+
SCOPE_UNKNOWN_PORT(error2) {
|
|
53354
|
+
const quoted = extractQuoted(error2.message);
|
|
53355
|
+
const portName = quoted[0] || "unknown";
|
|
53356
|
+
return {
|
|
53357
|
+
title: "Unknown Scoped Port",
|
|
53358
|
+
explanation: `Port '${portName}' is referenced in a scoped connection but doesn't exist as a scoped port on that node, or belongs to a different scope.`,
|
|
53359
|
+
fix: `Add the port as a scoped port with @output ${portName} scope:scopeName, or fix the port name in the @connect annotation.`,
|
|
53360
|
+
code: error2.code
|
|
53361
|
+
};
|
|
53362
|
+
},
|
|
53363
|
+
SCOPE_ORPHANED_CHILD(error2) {
|
|
53364
|
+
const quoted = extractQuoted(error2.message);
|
|
53365
|
+
const childId = quoted[0] || error2.node || "unknown";
|
|
53366
|
+
const scopeName = quoted[1] || "unknown";
|
|
53367
|
+
const parentId = quoted[2] || "unknown";
|
|
53368
|
+
return {
|
|
53369
|
+
title: "Orphaned Scope Child",
|
|
53370
|
+
explanation: `Node '${childId}' is declared inside scope '${scopeName}' of '${parentId}' but has no scoped connections. It won't receive data from or return data to the scope.`,
|
|
53371
|
+
fix: `Connect the parent's scoped output ports to '${childId}' inputs, and connect '${childId}' outputs back to the parent's scoped input ports.`,
|
|
53372
|
+
code: error2.code
|
|
53373
|
+
};
|
|
53374
|
+
},
|
|
52792
53375
|
OBJECT_TYPE_MISMATCH(error2) {
|
|
52793
53376
|
const quoted = extractQuoted(error2.message);
|
|
52794
53377
|
const sourceType = quoted[0] || "unknown";
|
|
@@ -52917,10 +53500,12 @@ var errorMappers = {
|
|
|
52917
53500
|
const types2 = extractTypes(error2.message);
|
|
52918
53501
|
const source = types2?.source || "unknown";
|
|
52919
53502
|
const target = types2?.target || "unknown";
|
|
53503
|
+
const quoted = extractQuoted(error2.message);
|
|
53504
|
+
const coerceSuggestion = buildCoerceSuggestion(quoted, target);
|
|
52920
53505
|
return {
|
|
52921
53506
|
title: "Lossy Type Conversion",
|
|
52922
53507
|
explanation: `Converting ${source} to ${target} may lose data or produce unexpected results (e.g., NaN, truncation).`,
|
|
52923
|
-
fix: `Add an explicit
|
|
53508
|
+
fix: coerceSuggestion ? `Add an explicit coercion: \`${coerceSuggestion}\`, or use @strictTypes to enforce type safety.` : `Add an explicit conversion with @coerce, or use @strictTypes to enforce type safety.`,
|
|
52924
53509
|
code: error2.code
|
|
52925
53510
|
};
|
|
52926
53511
|
},
|
|
@@ -56397,6 +56982,18 @@ ${indent}});`;
|
|
|
56397
56982
|
function generateExpressionCall(instanceId, nodeType, workflow, nodeTypes, indent) {
|
|
56398
56983
|
const args = buildNodeArgs(instanceId, nodeType, workflow, nodeTypes);
|
|
56399
56984
|
const safeId = toValidIdentifier(instanceId);
|
|
56985
|
+
if (nodeType.variant === "COERCION") {
|
|
56986
|
+
const coerceExprMap = {
|
|
56987
|
+
__fw_toString: "String",
|
|
56988
|
+
__fw_toNumber: "Number",
|
|
56989
|
+
__fw_toBoolean: "Boolean",
|
|
56990
|
+
__fw_toJSON: "JSON.stringify",
|
|
56991
|
+
__fw_parseJSON: "JSON.parse"
|
|
56992
|
+
};
|
|
56993
|
+
const coerceExpr = coerceExprMap[nodeType.functionName] || "String";
|
|
56994
|
+
const valueArg = args[0] || "undefined";
|
|
56995
|
+
return `${indent}${safeId}_result = ${coerceExpr}(${valueArg});`;
|
|
56996
|
+
}
|
|
56400
56997
|
const fnCall = `${nodeType.functionName}(${args.join(", ")})`;
|
|
56401
56998
|
const awaitPrefix = nodeType.isAsync ? "await " : "";
|
|
56402
56999
|
return `${indent}${safeId}_result = ${awaitPrefix}${fnCall};`;
|
|
@@ -56605,6 +57202,18 @@ function emitPromiseAll(nodeIds, workflow, nodeTypes, indent, lines, generatedNo
|
|
|
56605
57202
|
if (timeoutArg && timeoutArg !== "undefined") invokeCall += `, timeout: ${timeoutArg}`;
|
|
56606
57203
|
invokeCall += ` })`;
|
|
56607
57204
|
stepCalls.push(invokeCall);
|
|
57205
|
+
} else if (nt.variant === "COERCION") {
|
|
57206
|
+
const coerceExprMap = {
|
|
57207
|
+
__fw_toString: "String",
|
|
57208
|
+
__fw_toNumber: "Number",
|
|
57209
|
+
__fw_toBoolean: "Boolean",
|
|
57210
|
+
__fw_toJSON: "JSON.stringify",
|
|
57211
|
+
__fw_parseJSON: "JSON.parse"
|
|
57212
|
+
};
|
|
57213
|
+
const coerceExpr = coerceExprMap[nt.functionName] || "String";
|
|
57214
|
+
const args = buildNodeArgs(nodeId, nt, workflow, nodeTypes);
|
|
57215
|
+
const valueArg = args[0] || "undefined";
|
|
57216
|
+
stepCalls.push(`${indent} Promise.resolve(${coerceExpr}(${valueArg}))`);
|
|
56608
57217
|
} else if (nt.expression) {
|
|
56609
57218
|
const args = buildNodeArgs(nodeId, nt, workflow, nodeTypes);
|
|
56610
57219
|
const fnCall = `${nt.functionName}(${args.join(", ")})`;
|
|
@@ -57352,12 +57961,18 @@ async function compileCommand(input, options = {}) {
|
|
|
57352
57961
|
const loc = err.location ? `[line ${err.location.line}] ` : "";
|
|
57353
57962
|
logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
57354
57963
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
57964
|
+
if (err.docUrl) {
|
|
57965
|
+
logger.info(` See: ${err.docUrl}`);
|
|
57966
|
+
}
|
|
57355
57967
|
} else {
|
|
57356
57968
|
let msg = ` - ${err.message}`;
|
|
57357
57969
|
if (err.node) {
|
|
57358
57970
|
msg += ` (node: ${err.node})`;
|
|
57359
57971
|
}
|
|
57360
57972
|
logger.error(msg);
|
|
57973
|
+
if (err.docUrl) {
|
|
57974
|
+
logger.info(` See: ${err.docUrl}`);
|
|
57975
|
+
}
|
|
57361
57976
|
}
|
|
57362
57977
|
});
|
|
57363
57978
|
errorCount++;
|
|
@@ -61443,6 +62058,9 @@ async function validateCommand(input, options = {}) {
|
|
|
61443
62058
|
const loc = err.location ? `[line ${err.location.line}] ` : "";
|
|
61444
62059
|
logger.error(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
61445
62060
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
62061
|
+
if (err.docUrl) {
|
|
62062
|
+
logger.info(` See: ${err.docUrl}`);
|
|
62063
|
+
}
|
|
61446
62064
|
} else {
|
|
61447
62065
|
let msg = ` - ${err.message}`;
|
|
61448
62066
|
if (err.location) {
|
|
@@ -61455,6 +62073,9 @@ async function validateCommand(input, options = {}) {
|
|
|
61455
62073
|
msg += ` (connection: ${err.connection.from.node}:${err.connection.from.port} -> ${err.connection.to.node}:${err.connection.to.port})`;
|
|
61456
62074
|
}
|
|
61457
62075
|
logger.error(msg);
|
|
62076
|
+
if (err.docUrl) {
|
|
62077
|
+
logger.info(` See: ${err.docUrl}`);
|
|
62078
|
+
}
|
|
61458
62079
|
}
|
|
61459
62080
|
});
|
|
61460
62081
|
}
|
|
@@ -61469,6 +62090,9 @@ async function validateCommand(input, options = {}) {
|
|
|
61469
62090
|
const loc = warn.location ? `[line ${warn.location.line}] ` : "";
|
|
61470
62091
|
logger.warn(` ${loc}${friendly.title}: ${friendly.explanation}`);
|
|
61471
62092
|
logger.info(` How to fix: ${friendly.fix}`);
|
|
62093
|
+
if (warn.docUrl) {
|
|
62094
|
+
logger.info(` See: ${warn.docUrl}`);
|
|
62095
|
+
}
|
|
61472
62096
|
} else {
|
|
61473
62097
|
let msg = ` - ${warn.message}`;
|
|
61474
62098
|
if (warn.location) {
|
|
@@ -61478,6 +62102,9 @@ async function validateCommand(input, options = {}) {
|
|
|
61478
62102
|
msg += ` (node: ${warn.node})`;
|
|
61479
62103
|
}
|
|
61480
62104
|
logger.warn(msg);
|
|
62105
|
+
if (warn.docUrl) {
|
|
62106
|
+
logger.info(` See: ${warn.docUrl}`);
|
|
62107
|
+
}
|
|
61481
62108
|
}
|
|
61482
62109
|
});
|
|
61483
62110
|
}
|
|
@@ -64783,13 +65410,16 @@ function generateProjectFiles(projectName, template, format = "esm") {
|
|
|
64783
65410
|
const gitignore = `node_modules/
|
|
64784
65411
|
dist/
|
|
64785
65412
|
.tsbuildinfo
|
|
65413
|
+
`;
|
|
65414
|
+
const configYaml = `defaultFileType: ts
|
|
64786
65415
|
`;
|
|
64787
65416
|
return {
|
|
64788
65417
|
"package.json": packageJson,
|
|
64789
65418
|
"tsconfig.json": tsconfigJson,
|
|
64790
65419
|
[`src/${workflowFile}`]: workflowCode,
|
|
64791
65420
|
"src/main.ts": mainTs,
|
|
64792
|
-
".gitignore": gitignore
|
|
65421
|
+
".gitignore": gitignore,
|
|
65422
|
+
".flowweaver/config.yaml": configYaml
|
|
64793
65423
|
};
|
|
64794
65424
|
}
|
|
64795
65425
|
function scaffoldProject(targetDir, files, options) {
|
|
@@ -64989,22 +65619,19 @@ async function watchCommand(input, options = {}) {
|
|
|
64989
65619
|
init_esm5();
|
|
64990
65620
|
import * as path19 from "path";
|
|
64991
65621
|
import * as fs18 from "fs";
|
|
64992
|
-
import * as
|
|
65622
|
+
import * as os from "os";
|
|
64993
65623
|
import { spawn } from "child_process";
|
|
64994
65624
|
|
|
64995
65625
|
// src/mcp/workflow-executor.ts
|
|
64996
65626
|
import * as path18 from "path";
|
|
64997
65627
|
import * as fs17 from "fs";
|
|
64998
|
-
import * as os from "os";
|
|
64999
65628
|
import { pathToFileURL } from "url";
|
|
65000
65629
|
import ts4 from "typescript";
|
|
65001
65630
|
async function executeWorkflowFromFile(filePath, params, options) {
|
|
65002
65631
|
const resolvedPath = path18.resolve(filePath);
|
|
65003
65632
|
const includeTrace = options?.includeTrace !== false;
|
|
65004
|
-
const
|
|
65005
|
-
|
|
65006
|
-
`fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
65007
|
-
);
|
|
65633
|
+
const tmpId = `fw-exec-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
65634
|
+
const tmpBase = path18.join(path18.dirname(resolvedPath), tmpId);
|
|
65008
65635
|
const tmpTsFile = `${tmpBase}.ts`;
|
|
65009
65636
|
const tmpFile = `${tmpBase}.mjs`;
|
|
65010
65637
|
try {
|
|
@@ -65035,11 +65662,13 @@ async function executeWorkflowFromFile(filePath, params, options) {
|
|
|
65035
65662
|
const trace = [];
|
|
65036
65663
|
const debugger_ = includeTrace ? {
|
|
65037
65664
|
sendEvent: (event) => {
|
|
65038
|
-
|
|
65665
|
+
const traceEvent = {
|
|
65039
65666
|
type: event.type || "UNKNOWN",
|
|
65040
65667
|
timestamp: Date.now(),
|
|
65041
65668
|
data: event
|
|
65042
|
-
}
|
|
65669
|
+
};
|
|
65670
|
+
trace.push(traceEvent);
|
|
65671
|
+
options?.onEvent?.(traceEvent);
|
|
65043
65672
|
},
|
|
65044
65673
|
innerFlowInvocation: false
|
|
65045
65674
|
} : void 0;
|
|
@@ -65298,7 +65927,7 @@ async function runInngestDevMode(filePath, options) {
|
|
|
65298
65927
|
if (missingDeps.length > 0) {
|
|
65299
65928
|
throw new Error(`Missing dependencies: ${missingDeps.join(", ")}. Install them with: npm install ${missingDeps.join(" ")}`);
|
|
65300
65929
|
}
|
|
65301
|
-
const tmpDir = fs18.mkdtempSync(path19.join(
|
|
65930
|
+
const tmpDir = fs18.mkdtempSync(path19.join(os.tmpdir(), "fw-inngest-dev-"));
|
|
65302
65931
|
const inngestOutputPath = path19.join(tmpDir, path19.basename(filePath).replace(/\.ts$/, ".inngest.ts"));
|
|
65303
65932
|
let serverProcess = null;
|
|
65304
65933
|
const compileInngest = async () => {
|
|
@@ -83673,15 +84302,30 @@ function registerEditorTools(mcp, connection, buffer) {
|
|
|
83673
84302
|
params: external_exports.record(external_exports.unknown()).optional().describe("Optional execution parameters"),
|
|
83674
84303
|
includeTrace: external_exports.boolean().optional().describe("Include execution trace events (default: true)")
|
|
83675
84304
|
},
|
|
83676
|
-
async (args) => {
|
|
84305
|
+
async (args, extra) => {
|
|
83677
84306
|
if (args.filePath) {
|
|
83678
84307
|
try {
|
|
83679
84308
|
const channel = new AgentChannel();
|
|
83680
84309
|
const runId = `run-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
84310
|
+
const progressToken = extra._meta?.progressToken;
|
|
84311
|
+
let eventCount = 0;
|
|
84312
|
+
const onEvent = progressToken ? (event) => {
|
|
84313
|
+
eventCount++;
|
|
84314
|
+
extra.sendNotification({
|
|
84315
|
+
method: "notifications/progress",
|
|
84316
|
+
params: {
|
|
84317
|
+
progressToken,
|
|
84318
|
+
progress: eventCount,
|
|
84319
|
+
message: event.type === "STATUS_CHANGED" ? `${event.data?.id ?? ""}: ${event.data?.status ?? ""}` : event.type
|
|
84320
|
+
}
|
|
84321
|
+
}).catch(() => {
|
|
84322
|
+
});
|
|
84323
|
+
} : void 0;
|
|
83681
84324
|
const execPromise = executeWorkflowFromFile(args.filePath, args.params, {
|
|
83682
84325
|
workflowName: args.workflowName,
|
|
83683
84326
|
includeTrace: args.includeTrace,
|
|
83684
|
-
agentChannel: channel
|
|
84327
|
+
agentChannel: channel,
|
|
84328
|
+
onEvent
|
|
83685
84329
|
});
|
|
83686
84330
|
const raceResult = await Promise.race([
|
|
83687
84331
|
execPromise.then((r) => ({ type: "completed", result: r })),
|
|
@@ -91637,6 +92281,9 @@ async function runCommand(input, options) {
|
|
|
91637
92281
|
throw new Error(`Failed to parse mocks file: ${options.mocksFile}`);
|
|
91638
92282
|
}
|
|
91639
92283
|
}
|
|
92284
|
+
if (mocks && !options.json) {
|
|
92285
|
+
await validateMockConfig(mocks, filePath, options.workflow);
|
|
92286
|
+
}
|
|
91640
92287
|
let timeoutId;
|
|
91641
92288
|
let timedOut = false;
|
|
91642
92289
|
if (options.timeout) {
|
|
@@ -91649,17 +92296,40 @@ async function runCommand(input, options) {
|
|
|
91649
92296
|
}, options.timeout);
|
|
91650
92297
|
}
|
|
91651
92298
|
try {
|
|
91652
|
-
const includeTrace = options.trace
|
|
92299
|
+
const includeTrace = options.stream || options.trace || !options.production;
|
|
91653
92300
|
if (!options.json && mocks) {
|
|
91654
92301
|
logger.info("Running with mock data");
|
|
91655
92302
|
}
|
|
92303
|
+
const nodeStartTimes = /* @__PURE__ */ new Map();
|
|
92304
|
+
const onEvent = options.stream && !options.json ? (event) => {
|
|
92305
|
+
if (event.type === "STATUS_CHANGED" && event.data) {
|
|
92306
|
+
const nodeId = event.data.id;
|
|
92307
|
+
const status = event.data.status;
|
|
92308
|
+
if (!nodeId || !status) return;
|
|
92309
|
+
if (status === "RUNNING") {
|
|
92310
|
+
nodeStartTimes.set(nodeId, event.timestamp);
|
|
92311
|
+
logger.log(` [STATUS_CHANGED] ${nodeId}: \u2192 RUNNING`);
|
|
92312
|
+
} else {
|
|
92313
|
+
const startTime = nodeStartTimes.get(nodeId);
|
|
92314
|
+
const duration3 = startTime ? ` (${event.timestamp - startTime}ms)` : "";
|
|
92315
|
+
logger.log(` [STATUS_CHANGED] ${nodeId}: \u2192 ${status}${duration3}`);
|
|
92316
|
+
}
|
|
92317
|
+
} else if (event.type === "VARIABLE_SET" && event.data) {
|
|
92318
|
+
const nodeId = event.data.nodeId;
|
|
92319
|
+
const varName = event.data.name;
|
|
92320
|
+
if (nodeId && varName) {
|
|
92321
|
+
logger.log(` [VARIABLE_SET] ${nodeId}.${varName}`);
|
|
92322
|
+
}
|
|
92323
|
+
}
|
|
92324
|
+
} : void 0;
|
|
91656
92325
|
const channel = new AgentChannel();
|
|
91657
92326
|
const execPromise = executeWorkflowFromFile(filePath, params, {
|
|
91658
92327
|
workflowName: options.workflow,
|
|
91659
92328
|
production: options.production ?? false,
|
|
91660
92329
|
includeTrace,
|
|
91661
92330
|
mocks,
|
|
91662
|
-
agentChannel: channel
|
|
92331
|
+
agentChannel: channel,
|
|
92332
|
+
onEvent
|
|
91663
92333
|
});
|
|
91664
92334
|
let result;
|
|
91665
92335
|
let execDone = false;
|
|
@@ -91768,6 +92438,35 @@ async function runCommand(input, options) {
|
|
|
91768
92438
|
}
|
|
91769
92439
|
}
|
|
91770
92440
|
}
|
|
92441
|
+
var VALID_MOCK_KEYS = /* @__PURE__ */ new Set(["events", "invocations", "agents", "fast"]);
|
|
92442
|
+
var MOCK_SECTION_TO_NODE = {
|
|
92443
|
+
events: "waitForEvent",
|
|
92444
|
+
invocations: "invokeWorkflow",
|
|
92445
|
+
agents: "waitForAgent"
|
|
92446
|
+
};
|
|
92447
|
+
async function validateMockConfig(mocks, filePath, workflowName) {
|
|
92448
|
+
for (const key of Object.keys(mocks)) {
|
|
92449
|
+
if (!VALID_MOCK_KEYS.has(key)) {
|
|
92450
|
+
logger.warn(`Mock config has unknown key "${key}". Valid keys: ${[...VALID_MOCK_KEYS].join(", ")}`);
|
|
92451
|
+
}
|
|
92452
|
+
}
|
|
92453
|
+
try {
|
|
92454
|
+
const result = await parseWorkflow(filePath, { workflowName });
|
|
92455
|
+
if (result.errors.length > 0 || !result.ast?.instances) return;
|
|
92456
|
+
const usedNodeTypes = new Set(result.ast.instances.map((i) => i.nodeType));
|
|
92457
|
+
for (const [section, nodeType] of Object.entries(MOCK_SECTION_TO_NODE)) {
|
|
92458
|
+
const mockSection = mocks[section];
|
|
92459
|
+
if (mockSection && typeof mockSection === "object" && Object.keys(mockSection).length > 0) {
|
|
92460
|
+
if (!usedNodeTypes.has(nodeType)) {
|
|
92461
|
+
logger.warn(
|
|
92462
|
+
`Mock config has "${section}" entries but workflow has no ${nodeType} nodes`
|
|
92463
|
+
);
|
|
92464
|
+
}
|
|
92465
|
+
}
|
|
92466
|
+
}
|
|
92467
|
+
} catch {
|
|
92468
|
+
}
|
|
92469
|
+
}
|
|
91771
92470
|
function promptForInput(question) {
|
|
91772
92471
|
return new Promise((resolve27) => {
|
|
91773
92472
|
const rl = readline9.createInterface({
|
|
@@ -92264,7 +92963,7 @@ async function serveCommand(dir, options) {
|
|
|
92264
92963
|
// src/export/index.ts
|
|
92265
92964
|
import * as path33 from "path";
|
|
92266
92965
|
import * as fs32 from "fs";
|
|
92267
|
-
import * as
|
|
92966
|
+
import * as os2 from "os";
|
|
92268
92967
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
92269
92968
|
|
|
92270
92969
|
// src/export/templates.ts
|
|
@@ -92461,7 +93160,7 @@ async function exportMultiWorkflow(options) {
|
|
|
92461
93160
|
throw new Error(`None of the requested workflows found. Available: ${available}`);
|
|
92462
93161
|
}
|
|
92463
93162
|
}
|
|
92464
|
-
const workDir = isDryRun ? path33.join(
|
|
93163
|
+
const workDir = isDryRun ? path33.join(os2.tmpdir(), `fw-export-multi-dryrun-${Date.now()}`) : outputDir;
|
|
92465
93164
|
fs32.mkdirSync(workDir, { recursive: true });
|
|
92466
93165
|
fs32.mkdirSync(path33.join(workDir, "workflows"), { recursive: true });
|
|
92467
93166
|
fs32.mkdirSync(path33.join(workDir, "runtime"), { recursive: true });
|
|
@@ -93185,7 +93884,7 @@ async function exportWorkflow(options) {
|
|
|
93185
93884
|
const available = parseResult.workflows.map((w) => w.name).join(", ");
|
|
93186
93885
|
throw new Error(`Workflow "${options.workflow}" not found. Available: ${available}`);
|
|
93187
93886
|
}
|
|
93188
|
-
const workDir = isDryRun ? path33.join(
|
|
93887
|
+
const workDir = isDryRun ? path33.join(os2.tmpdir(), `fw-export-dryrun-${Date.now()}`) : outputDir;
|
|
93189
93888
|
fs32.mkdirSync(workDir, { recursive: true });
|
|
93190
93889
|
let compiledContent;
|
|
93191
93890
|
let compiledPath;
|
|
@@ -94429,7 +95128,7 @@ function displayInstalledPackage(pkg) {
|
|
|
94429
95128
|
}
|
|
94430
95129
|
|
|
94431
95130
|
// src/cli/index.ts
|
|
94432
|
-
var version2 = true ? "0.
|
|
95131
|
+
var version2 = true ? "0.7.0" : "0.0.0-dev";
|
|
94433
95132
|
var program2 = new Command();
|
|
94434
95133
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
94435
95134
|
program2.configureOutput({
|
|
@@ -94633,7 +95332,7 @@ patternCmd.command("extract <source-file>").description("Extract a pattern from
|
|
|
94633
95332
|
process.exit(1);
|
|
94634
95333
|
}
|
|
94635
95334
|
});
|
|
94636
|
-
program2.command("run <input>").description("Execute a workflow file directly").option("-w, --workflow <name>", "Specific workflow name to run").option("--params <json>", "Input parameters as JSON string").option("--params-file <path>", "Path to JSON file with input parameters").option("-p, --production", "Production mode (no trace events)", false).option("-t, --trace", "Include execution trace events").option("--json", "Output result as JSON", false).option("--timeout <ms>", "Execution timeout in milliseconds", parseInt).option("--mocks <json>", "Mock config for built-in nodes (events, invocations, fast) as JSON").option("--mocks-file <path>", "Path to JSON file with mock config for built-in nodes").action(async (input, options) => {
|
|
95335
|
+
program2.command("run <input>").description("Execute a workflow file directly").option("-w, --workflow <name>", "Specific workflow name to run").option("--params <json>", "Input parameters as JSON string").option("--params-file <path>", "Path to JSON file with input parameters").option("-p, --production", "Production mode (no trace events)", false).option("-t, --trace", "Include execution trace events").option("-s, --stream", "Stream trace events in real-time").option("--json", "Output result as JSON", false).option("--timeout <ms>", "Execution timeout in milliseconds", parseInt).option("--mocks <json>", "Mock config for built-in nodes (events, invocations, fast) as JSON").option("--mocks-file <path>", "Path to JSON file with mock config for built-in nodes").action(async (input, options) => {
|
|
94637
95336
|
try {
|
|
94638
95337
|
await runCommand(input, options);
|
|
94639
95338
|
} catch (error2) {
|