@synergenius/flow-weaver 0.5.1 → 0.6.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 +25 -2
- package/dist/ast/types.d.ts +20 -2
- package/dist/built-in-nodes/coercion-types.d.ts +15 -0
- package/dist/built-in-nodes/coercion-types.js +61 -0
- 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/run.d.ts +2 -0
- package/dist/cli/commands/run.js +30 -1
- package/dist/cli/flow-weaver.mjs +662 -50
- 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/workflow-executor.d.ts +1 -0
- package/dist/mcp/workflow-executor.js +4 -2
- package/dist/parser.d.ts +4 -0
- package/dist/parser.js +69 -0
- package/dist/validator.d.ts +5 -0
- package/dist/validator.js +188 -14
- package/docs/reference/advanced-annotations.md +71 -2
- package/docs/reference/error-codes.md +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -218,7 +218,7 @@ flow-weaver export workflow.ts --target cloudflare --output deploy/
|
|
|
218
218
|
flow-weaver serve ./workflows --port 3000 --swagger
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
-
Inngest
|
|
221
|
+
Both the default TypeScript target and Inngest target parallelize independent nodes with `Promise.all()`. Inngest additionally wraps each node in `step.run()` for individual durability and generates typed Zod event schemas.
|
|
222
222
|
|
|
223
223
|
## Visual Human-in-the-Loop
|
|
224
224
|
|
|
@@ -319,6 +319,7 @@ flow-weaver listen # Stream editor events
|
|
|
319
319
|
|------|---------|
|
|
320
320
|
| `delay` | Sleep for a duration (ms, s, m, h, d). Mockable for fast testing. |
|
|
321
321
|
| `waitForEvent` | Wait for an external event with optional field matching and timeout. Maps to Inngest `step.waitForEvent()` for zero-cost durable pauses. |
|
|
322
|
+
| `waitForAgent` | Pause execution and wait for an external agent to provide a result. Supports multi-step human-in-the-loop and agent delegation patterns. |
|
|
322
323
|
| `invokeWorkflow` | Invoke another workflow by ID with payload and timeout. Maps to Inngest `step.invoke()`. |
|
|
323
324
|
|
|
324
325
|
## STEP Port Architecture
|
|
@@ -20,6 +20,10 @@ export class AnnotationGenerator {
|
|
|
20
20
|
if (nodeType.variant === 'MAP_ITERATOR') {
|
|
21
21
|
return;
|
|
22
22
|
}
|
|
23
|
+
// Skip synthetic COERCION node types — they're generated from @coerce macros
|
|
24
|
+
if (nodeType.variant === 'COERCION') {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
23
27
|
lines.push(...this.generateNodeTypeAnnotation(nodeType, indent, includeComments, includeMetadata));
|
|
24
28
|
lines.push("");
|
|
25
29
|
});
|
|
@@ -136,10 +140,11 @@ export class AnnotationGenerator {
|
|
|
136
140
|
}
|
|
137
141
|
generateWorkflowAnnotation(workflow, indent, includeComments, skipParamReturns = false) {
|
|
138
142
|
const lines = [];
|
|
139
|
-
// Build macro coverage sets for filtering (@map
|
|
143
|
+
// Build macro coverage sets for filtering (@map and @coerce)
|
|
140
144
|
const macroInstanceIds = new Set();
|
|
141
145
|
const macroChildIds = new Set();
|
|
142
146
|
const macroScopeNames = new Set();
|
|
147
|
+
const coerceInstanceIds = new Set();
|
|
143
148
|
if (workflow.macros && workflow.macros.length > 0) {
|
|
144
149
|
for (const macro of workflow.macros) {
|
|
145
150
|
if (macro.type === 'map') {
|
|
@@ -147,6 +152,9 @@ export class AnnotationGenerator {
|
|
|
147
152
|
macroChildIds.add(macro.childId);
|
|
148
153
|
macroScopeNames.add(`${macro.instanceId}.iterate`);
|
|
149
154
|
}
|
|
155
|
+
else if (macro.type === 'coerce') {
|
|
156
|
+
coerceInstanceIds.add(macro.instanceId);
|
|
157
|
+
}
|
|
150
158
|
}
|
|
151
159
|
}
|
|
152
160
|
// Generate JSDoc comment block
|
|
@@ -211,10 +219,12 @@ export class AnnotationGenerator {
|
|
|
211
219
|
if (workflow.description && includeComments) {
|
|
212
220
|
lines.push(` * @description ${workflow.description}`);
|
|
213
221
|
}
|
|
214
|
-
// Add node instances — skip synthetic MAP_ITERATOR instances, strip parent from macro children
|
|
222
|
+
// Add node instances — skip synthetic MAP_ITERATOR/COERCION instances, strip parent from macro children
|
|
215
223
|
workflow.instances.forEach((instance) => {
|
|
216
224
|
if (macroInstanceIds.has(instance.id))
|
|
217
225
|
return;
|
|
226
|
+
if (coerceInstanceIds.has(instance.id))
|
|
227
|
+
return;
|
|
218
228
|
if (macroChildIds.has(instance.id) && instance.parent) {
|
|
219
229
|
const stripped = { ...instance, parent: undefined };
|
|
220
230
|
lines.push(generateNodeInstanceTag(stripped));
|
|
@@ -257,6 +267,11 @@ export class AnnotationGenerator {
|
|
|
257
267
|
const tgt = `${macro.target.node}.${macro.target.port}`;
|
|
258
268
|
lines.push(` * @fanIn ${srcs} -> ${tgt}`);
|
|
259
269
|
}
|
|
270
|
+
else if (macro.type === 'coerce') {
|
|
271
|
+
const src = `${macro.source.node}.${macro.source.port}`;
|
|
272
|
+
const tgt = `${macro.target.node}.${macro.target.port}`;
|
|
273
|
+
lines.push(` * @coerce ${macro.instanceId} ${src} -> ${tgt} as ${macro.targetType}`);
|
|
274
|
+
}
|
|
260
275
|
}
|
|
261
276
|
}
|
|
262
277
|
// Add node positions (if they exist)
|
|
@@ -492,6 +507,14 @@ function isConnectionCoveredByMacroStatic(conn, macros) {
|
|
|
492
507
|
}
|
|
493
508
|
}
|
|
494
509
|
}
|
|
510
|
+
else if (macro.type === 'coerce') {
|
|
511
|
+
if (conn.from.scope || conn.to.scope)
|
|
512
|
+
continue;
|
|
513
|
+
// Connections to/from the synthetic coercion instance are covered
|
|
514
|
+
if (conn.to.node === macro.instanceId || conn.from.node === macro.instanceId) {
|
|
515
|
+
return true;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
495
518
|
}
|
|
496
519
|
return false;
|
|
497
520
|
}
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -111,7 +111,7 @@ export type TWorkflowAST = {
|
|
|
111
111
|
* A sugar macro that expands to a full scope pattern or connection set.
|
|
112
112
|
* Stored on the workflow AST for round-trip preservation in generateInPlace.
|
|
113
113
|
*/
|
|
114
|
-
export type TWorkflowMacro = TMapMacro | TPathMacro | TFanOutMacro | TFanInMacro;
|
|
114
|
+
export type TWorkflowMacro = TMapMacro | TPathMacro | TFanOutMacro | TFanInMacro | TCoerceMacro;
|
|
115
115
|
export type TMapMacro = {
|
|
116
116
|
type: 'map';
|
|
117
117
|
/** Instance ID for the synthetic iterator node (e.g., "loop") */
|
|
@@ -159,6 +159,24 @@ export type TFanInMacro = {
|
|
|
159
159
|
port: string;
|
|
160
160
|
};
|
|
161
161
|
};
|
|
162
|
+
export type TCoerceTargetType = 'string' | 'number' | 'boolean' | 'json' | 'object';
|
|
163
|
+
export type TCoerceMacro = {
|
|
164
|
+
type: 'coerce';
|
|
165
|
+
/** Instance ID for the synthetic coercion node */
|
|
166
|
+
instanceId: string;
|
|
167
|
+
/** Source port in "node.port" format */
|
|
168
|
+
source: {
|
|
169
|
+
node: string;
|
|
170
|
+
port: string;
|
|
171
|
+
};
|
|
172
|
+
/** Target port in "node.port" format */
|
|
173
|
+
target: {
|
|
174
|
+
node: string;
|
|
175
|
+
port: string;
|
|
176
|
+
};
|
|
177
|
+
/** Coercion target type */
|
|
178
|
+
targetType: TCoerceTargetType;
|
|
179
|
+
};
|
|
162
180
|
/**
|
|
163
181
|
* Default configuration for a node type that can be overridden per instance.
|
|
164
182
|
*/
|
|
@@ -222,7 +240,7 @@ export type TNodeTypeAST = {
|
|
|
222
240
|
/** Multiple scopes this node creates */
|
|
223
241
|
scopes?: string[];
|
|
224
242
|
/** Variant identifier (set by app layer) */
|
|
225
|
-
variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR';
|
|
243
|
+
variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR' | 'COERCION';
|
|
226
244
|
/** File path for external node types */
|
|
227
245
|
path?: string;
|
|
228
246
|
/** Function reference for function-based node types */
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthetic node type definitions for explicit type coercion.
|
|
3
|
+
*
|
|
4
|
+
* These are compiler-internal node types created by @coerce macros.
|
|
5
|
+
* They generate inline JavaScript coercion expressions instead of function calls.
|
|
6
|
+
*/
|
|
7
|
+
import type { TNodeTypeAST, TCoerceTargetType } from '../ast/types.js';
|
|
8
|
+
/** Maps coercion target type keyword to the synthetic node type name */
|
|
9
|
+
export declare const COERCE_TYPE_MAP: Record<TCoerceTargetType, string>;
|
|
10
|
+
/** Maps synthetic node type name to the inline JS expression */
|
|
11
|
+
export declare const COERCE_EXPRESSIONS: Record<string, string>;
|
|
12
|
+
export declare const COERCION_NODE_TYPES: Record<string, TNodeTypeAST>;
|
|
13
|
+
/** Check if a function name is a coercion node type */
|
|
14
|
+
export declare function isCoercionNode(functionName: string): boolean;
|
|
15
|
+
//# sourceMappingURL=coercion-types.d.ts.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Synthetic node type definitions for explicit type coercion.
|
|
3
|
+
*
|
|
4
|
+
* These are compiler-internal node types created by @coerce macros.
|
|
5
|
+
* They generate inline JavaScript coercion expressions instead of function calls.
|
|
6
|
+
*/
|
|
7
|
+
/** Maps coercion target type keyword to the synthetic node type name */
|
|
8
|
+
export const COERCE_TYPE_MAP = {
|
|
9
|
+
string: '__fw_toString',
|
|
10
|
+
number: '__fw_toNumber',
|
|
11
|
+
boolean: '__fw_toBoolean',
|
|
12
|
+
json: '__fw_toJSON',
|
|
13
|
+
object: '__fw_parseJSON',
|
|
14
|
+
};
|
|
15
|
+
/** Maps synthetic node type name to the inline JS expression */
|
|
16
|
+
export const COERCE_EXPRESSIONS = {
|
|
17
|
+
__fw_toString: 'String',
|
|
18
|
+
__fw_toNumber: 'Number',
|
|
19
|
+
__fw_toBoolean: 'Boolean',
|
|
20
|
+
__fw_toJSON: 'JSON.stringify',
|
|
21
|
+
__fw_parseJSON: 'JSON.parse',
|
|
22
|
+
};
|
|
23
|
+
function makeCoercionNodeType(name, outputType, outputTsType) {
|
|
24
|
+
return {
|
|
25
|
+
type: 'NodeType',
|
|
26
|
+
name,
|
|
27
|
+
functionName: name,
|
|
28
|
+
expression: true,
|
|
29
|
+
isAsync: false,
|
|
30
|
+
hasSuccessPort: true,
|
|
31
|
+
hasFailurePort: true,
|
|
32
|
+
executeWhen: 'CONJUNCTION',
|
|
33
|
+
variant: 'COERCION',
|
|
34
|
+
inputs: {
|
|
35
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
36
|
+
value: { dataType: 'ANY', label: 'Value', tsType: 'unknown' },
|
|
37
|
+
},
|
|
38
|
+
outputs: {
|
|
39
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
40
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', failure: true, isControlFlow: true },
|
|
41
|
+
result: { dataType: outputType, label: 'Result', tsType: outputTsType },
|
|
42
|
+
},
|
|
43
|
+
visuals: {
|
|
44
|
+
color: '#0d9488',
|
|
45
|
+
icon: 'transform',
|
|
46
|
+
tags: [{ label: 'coerce' }],
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export const COERCION_NODE_TYPES = {
|
|
51
|
+
__fw_toString: makeCoercionNodeType('__fw_toString', 'STRING', 'string'),
|
|
52
|
+
__fw_toNumber: makeCoercionNodeType('__fw_toNumber', 'NUMBER', 'number'),
|
|
53
|
+
__fw_toBoolean: makeCoercionNodeType('__fw_toBoolean', 'BOOLEAN', 'boolean'),
|
|
54
|
+
__fw_toJSON: makeCoercionNodeType('__fw_toJSON', 'STRING', 'string'),
|
|
55
|
+
__fw_parseJSON: makeCoercionNodeType('__fw_parseJSON', 'OBJECT', 'object'),
|
|
56
|
+
};
|
|
57
|
+
/** Check if a function name is a coercion node type */
|
|
58
|
+
export function isCoercionNode(functionName) {
|
|
59
|
+
return functionName in COERCION_NODE_TYPES;
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=coercion-types.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module chevrotain-parser/coerce-parser
|
|
3
|
+
*
|
|
4
|
+
* Parser for @coerce sugar annotation using Chevrotain.
|
|
5
|
+
*
|
|
6
|
+
* Syntax:
|
|
7
|
+
* @coerce instanceId sourceNode.port -> targetNode.port as targetType
|
|
8
|
+
*
|
|
9
|
+
* Where targetType is one of: string, number, boolean, json, object
|
|
10
|
+
*/
|
|
11
|
+
import type { TCoerceTargetType } from '../ast/types.js';
|
|
12
|
+
export interface CoerceParseResult {
|
|
13
|
+
instanceId: string;
|
|
14
|
+
source: {
|
|
15
|
+
node: string;
|
|
16
|
+
port: string;
|
|
17
|
+
};
|
|
18
|
+
target: {
|
|
19
|
+
node: string;
|
|
20
|
+
port: string;
|
|
21
|
+
};
|
|
22
|
+
targetType: TCoerceTargetType;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parse a @coerce line and return structured result.
|
|
26
|
+
* Returns null if the line is not a valid @coerce declaration.
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseCoerceLine(input: string, warnings: string[]): CoerceParseResult | null;
|
|
29
|
+
/**
|
|
30
|
+
* Get serialized grammar for documentation/diagram generation.
|
|
31
|
+
*/
|
|
32
|
+
export declare function getCoerceGrammar(): import("chevrotain").ISerializedGast[];
|
|
33
|
+
//# sourceMappingURL=coerce-parser.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module chevrotain-parser/coerce-parser
|
|
3
|
+
*
|
|
4
|
+
* Parser for @coerce sugar annotation using Chevrotain.
|
|
5
|
+
*
|
|
6
|
+
* Syntax:
|
|
7
|
+
* @coerce instanceId sourceNode.port -> targetNode.port as targetType
|
|
8
|
+
*
|
|
9
|
+
* Where targetType is one of: string, number, boolean, json, object
|
|
10
|
+
*/
|
|
11
|
+
import { CstParser } from 'chevrotain';
|
|
12
|
+
import { JSDocLexer, CoerceTag, Identifier, Arrow, Dot, AsKeyword, allTokens, } from './tokens.js';
|
|
13
|
+
const VALID_COERCE_TYPES = new Set(['string', 'number', 'boolean', 'json', 'object']);
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Parser Definition
|
|
16
|
+
// =============================================================================
|
|
17
|
+
class CoerceParser extends CstParser {
|
|
18
|
+
constructor() {
|
|
19
|
+
super(allTokens);
|
|
20
|
+
this.performSelfAnalysis();
|
|
21
|
+
}
|
|
22
|
+
// @coerce Identifier portRef Arrow portRef AsKeyword Identifier
|
|
23
|
+
coerceLine = this.RULE('coerceLine', () => {
|
|
24
|
+
this.CONSUME(CoerceTag);
|
|
25
|
+
this.CONSUME(Identifier, { LABEL: 'instanceId' });
|
|
26
|
+
this.SUBRULE(this.portRef, { LABEL: 'source' });
|
|
27
|
+
this.CONSUME(Arrow);
|
|
28
|
+
this.SUBRULE2(this.portRef, { LABEL: 'target' });
|
|
29
|
+
this.CONSUME(AsKeyword);
|
|
30
|
+
this.CONSUME2(Identifier, { LABEL: 'typeName' });
|
|
31
|
+
});
|
|
32
|
+
// portRef: Identifier Dot Identifier
|
|
33
|
+
portRef = this.RULE('portRef', () => {
|
|
34
|
+
this.CONSUME(Identifier, { LABEL: 'nodeName' });
|
|
35
|
+
this.CONSUME(Dot);
|
|
36
|
+
this.CONSUME2(Identifier, { LABEL: 'portName' });
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// Parser Instance (singleton)
|
|
41
|
+
// =============================================================================
|
|
42
|
+
const parserInstance = new CoerceParser();
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// CST Visitor
|
|
45
|
+
// =============================================================================
|
|
46
|
+
const BaseVisitor = parserInstance.getBaseCstVisitorConstructor();
|
|
47
|
+
class CoerceVisitor extends BaseVisitor {
|
|
48
|
+
constructor() {
|
|
49
|
+
super();
|
|
50
|
+
this.validateVisitor();
|
|
51
|
+
}
|
|
52
|
+
coerceLine(ctx) {
|
|
53
|
+
const instanceId = ctx.instanceId[0].image;
|
|
54
|
+
const source = this.portRef(ctx.source[0].children);
|
|
55
|
+
const target = this.portRef(ctx.target[0].children);
|
|
56
|
+
const targetType = ctx.typeName[0].image;
|
|
57
|
+
return { instanceId, source, target, targetType };
|
|
58
|
+
}
|
|
59
|
+
portRef(ctx) {
|
|
60
|
+
return {
|
|
61
|
+
node: ctx.nodeName[0].image,
|
|
62
|
+
port: ctx.portName[0].image,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const visitorInstance = new CoerceVisitor();
|
|
67
|
+
// =============================================================================
|
|
68
|
+
// Public API
|
|
69
|
+
// =============================================================================
|
|
70
|
+
/**
|
|
71
|
+
* Parse a @coerce line and return structured result.
|
|
72
|
+
* Returns null if the line is not a valid @coerce declaration.
|
|
73
|
+
*/
|
|
74
|
+
export function parseCoerceLine(input, warnings) {
|
|
75
|
+
const lexResult = JSDocLexer.tokenize(input);
|
|
76
|
+
if (lexResult.errors.length > 0 || lexResult.tokens.length === 0)
|
|
77
|
+
return null;
|
|
78
|
+
if (lexResult.tokens[0].tokenType !== CoerceTag)
|
|
79
|
+
return null;
|
|
80
|
+
parserInstance.input = lexResult.tokens;
|
|
81
|
+
const cst = parserInstance.coerceLine();
|
|
82
|
+
if (parserInstance.errors.length > 0) {
|
|
83
|
+
const truncatedInput = input.length > 80 ? input.substring(0, 80) + '...' : input;
|
|
84
|
+
warnings.push(`Failed to parse @coerce line: "${truncatedInput}"\n` +
|
|
85
|
+
` Error: ${parserInstance.errors[0].message}\n` +
|
|
86
|
+
` Expected format: @coerce instanceId source.port -> target.port as type`);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const result = visitorInstance.visit(cst);
|
|
90
|
+
// Validate the target type
|
|
91
|
+
if (!VALID_COERCE_TYPES.has(result.targetType)) {
|
|
92
|
+
warnings.push(`@coerce: invalid target type "${result.targetType}". Valid types: ${[...VALID_COERCE_TYPES].join(', ')}`);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get serialized grammar for documentation/diagram generation.
|
|
99
|
+
*/
|
|
100
|
+
export function getCoerceGrammar() {
|
|
101
|
+
return parserInstance.getSerializedGastProductions();
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=coerce-parser.js.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Chevrotain-based parsers for JSDoc annotations.
|
|
5
5
|
* Provides structured parsing for @input, @output, @node, @connect, and @position.
|
|
6
6
|
*/
|
|
7
|
-
export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
|
|
7
|
+
export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, CoerceTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
|
|
8
8
|
export { parsePortLine, getPortGrammar } from './port-parser.js';
|
|
9
9
|
export type { PortParseResult } from './port-parser.js';
|
|
10
10
|
export { parseNodeLine, getNodeGrammar } from './node-parser.js';
|
|
@@ -21,6 +21,8 @@ export { parsePathLine, getPathGrammar } from './path-parser.js';
|
|
|
21
21
|
export type { PathParseResult, PathStep } from './path-parser.js';
|
|
22
22
|
export { parseFanOutLine, parseFanInLine } from './fan-parser.js';
|
|
23
23
|
export type { FanOutParseResult, FanInParseResult, PortRef } from './fan-parser.js';
|
|
24
|
+
export { parseCoerceLine } from './coerce-parser.js';
|
|
25
|
+
export type { CoerceParseResult } from './coerce-parser.js';
|
|
24
26
|
export { parseTriggerLine, parseCancelOnLine, parseRetriesLine, parseTimeoutLine, parseThrottleLine } from './trigger-cancel-parser.js';
|
|
25
27
|
export type { TriggerParseResult, CancelOnParseResult, ThrottleParseResult } from './trigger-cancel-parser.js';
|
|
26
28
|
export { generateGrammarDiagrams, getAllGrammars, serializedToEBNF } from './grammar-diagrams.js';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Provides structured parsing for @input, @output, @node, @connect, and @position.
|
|
6
6
|
*/
|
|
7
7
|
// Token exports (for advanced use cases)
|
|
8
|
-
export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
|
|
8
|
+
export { JSDocLexer, allTokens, InputTag, OutputTag, NodeTag, ConnectTag, PositionTag, ScopeTag, MapTag, PathTag, FanOutTag, FanInTag, CoerceTag, TriggerTag, CancelOnTag, RetriesTag, TimeoutTag, ThrottleTag, } from './tokens.js';
|
|
9
9
|
// Port parser
|
|
10
10
|
export { parsePortLine, getPortGrammar } from './port-parser.js';
|
|
11
11
|
// Node parser
|
|
@@ -22,6 +22,8 @@ export { parseMapLine, getMapGrammar } from './map-parser.js';
|
|
|
22
22
|
export { parsePathLine, getPathGrammar } from './path-parser.js';
|
|
23
23
|
// Fan-out / Fan-in parser
|
|
24
24
|
export { parseFanOutLine, parseFanInLine } from './fan-parser.js';
|
|
25
|
+
// Coerce parser
|
|
26
|
+
export { parseCoerceLine } from './coerce-parser.js';
|
|
25
27
|
// Trigger/cancel/retries/timeout/throttle parser
|
|
26
28
|
export { parseTriggerLine, parseCancelOnLine, parseRetriesLine, parseTimeoutLine, parseThrottleLine } from './trigger-cancel-parser.js';
|
|
27
29
|
// Grammar diagram generation
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Parser for @input/@output/@step port declarations using Chevrotain.
|
|
5
5
|
*/
|
|
6
6
|
import { CstParser } from 'chevrotain';
|
|
7
|
-
import { JSDocLexer, InputTag, OutputTag, StepTag, Identifier, ScopePrefix, OrderPrefix, PlacementPrefix, TypePrefix, MergeStrategyPrefix, TopKeyword, BottomKeyword, TrueKeyword, FalseKeyword, OverKeyword, MinimizedKeyword, Integer, StringLiteral, DescriptionText, LBracket, RBracket, LParen, RParen, Comma, Colon, Dot, Dash, Equals, GreaterThan, LessThan, Pipe, Ampersand, LBrace, RBrace, Asterisk, allTokens, } from './tokens.js';
|
|
7
|
+
import { JSDocLexer, InputTag, OutputTag, StepTag, Identifier, ScopePrefix, OrderPrefix, PlacementPrefix, TypePrefix, MergeStrategyPrefix, TopKeyword, BottomKeyword, TrueKeyword, FalseKeyword, OverKeyword, AsKeyword, MinimizedKeyword, Integer, StringLiteral, DescriptionText, LBracket, RBracket, LParen, RParen, Comma, Colon, Dot, Dash, Equals, GreaterThan, LessThan, Pipe, Ampersand, LBrace, RBrace, Asterisk, allTokens, } from './tokens.js';
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// Parser Definition
|
|
10
10
|
// =============================================================================
|
|
@@ -160,6 +160,7 @@ class PortParser extends CstParser {
|
|
|
160
160
|
{ ALT: () => this.CONSUME(Asterisk) },
|
|
161
161
|
// Keywords that can appear in description text
|
|
162
162
|
{ ALT: () => this.CONSUME(OverKeyword) },
|
|
163
|
+
{ ALT: () => this.CONSUME(AsKeyword) },
|
|
163
164
|
{ ALT: () => this.CONSUME(TopKeyword) },
|
|
164
165
|
{ ALT: () => this.CONSUME(BottomKeyword) },
|
|
165
166
|
{ ALT: () => this.CONSUME(TrueKeyword) },
|
|
@@ -19,12 +19,14 @@ export declare const MapTag: import("chevrotain").TokenType;
|
|
|
19
19
|
export declare const PathTag: import("chevrotain").TokenType;
|
|
20
20
|
export declare const FanOutTag: import("chevrotain").TokenType;
|
|
21
21
|
export declare const FanInTag: import("chevrotain").TokenType;
|
|
22
|
+
export declare const CoerceTag: import("chevrotain").TokenType;
|
|
22
23
|
export declare const TriggerTag: import("chevrotain").TokenType;
|
|
23
24
|
export declare const CancelOnTag: import("chevrotain").TokenType;
|
|
24
25
|
export declare const RetriesTag: import("chevrotain").TokenType;
|
|
25
26
|
export declare const TimeoutTag: import("chevrotain").TokenType;
|
|
26
27
|
export declare const ThrottleTag: import("chevrotain").TokenType;
|
|
27
28
|
export declare const OverKeyword: import("chevrotain").TokenType;
|
|
29
|
+
export declare const AsKeyword: import("chevrotain").TokenType;
|
|
28
30
|
export declare const ParamTag: import("chevrotain").TokenType;
|
|
29
31
|
export declare const ReturnsTag: import("chevrotain").TokenType;
|
|
30
32
|
export declare const FlowWeaverTag: import("chevrotain").TokenType;
|
|
@@ -69,6 +69,10 @@ export const FanInTag = createToken({
|
|
|
69
69
|
name: 'FanInTag',
|
|
70
70
|
pattern: /@fanIn\b/,
|
|
71
71
|
});
|
|
72
|
+
export const CoerceTag = createToken({
|
|
73
|
+
name: 'CoerceTag',
|
|
74
|
+
pattern: /@coerce\b/,
|
|
75
|
+
});
|
|
72
76
|
export const TriggerTag = createToken({
|
|
73
77
|
name: 'TriggerTag',
|
|
74
78
|
pattern: /@trigger\b/,
|
|
@@ -93,6 +97,10 @@ export const OverKeyword = createToken({
|
|
|
93
97
|
name: 'OverKeyword',
|
|
94
98
|
pattern: /over\b/,
|
|
95
99
|
});
|
|
100
|
+
export const AsKeyword = createToken({
|
|
101
|
+
name: 'AsKeyword',
|
|
102
|
+
pattern: /as\b/,
|
|
103
|
+
});
|
|
96
104
|
export const ParamTag = createToken({
|
|
97
105
|
name: 'ParamTag',
|
|
98
106
|
pattern: /@param\b/,
|
|
@@ -325,6 +333,7 @@ export const allTokens = [
|
|
|
325
333
|
PathTag,
|
|
326
334
|
FanOutTag,
|
|
327
335
|
FanInTag,
|
|
336
|
+
CoerceTag,
|
|
328
337
|
TriggerTag,
|
|
329
338
|
CancelOnTag,
|
|
330
339
|
RetriesTag,
|
|
@@ -358,6 +367,7 @@ export const allTokens = [
|
|
|
358
367
|
PeriodEq,
|
|
359
368
|
// Keywords (before Identifier)
|
|
360
369
|
OverKeyword,
|
|
370
|
+
AsKeyword,
|
|
361
371
|
MinimizedKeyword,
|
|
362
372
|
TopKeyword,
|
|
363
373
|
BottomKeyword,
|
|
@@ -12,6 +12,8 @@ export interface RunOptions {
|
|
|
12
12
|
production?: boolean;
|
|
13
13
|
/** Include execution trace events */
|
|
14
14
|
trace?: boolean;
|
|
15
|
+
/** Stream trace events in real-time */
|
|
16
|
+
stream?: boolean;
|
|
15
17
|
/** Output result as JSON (for scripting) */
|
|
16
18
|
json?: boolean;
|
|
17
19
|
/** Execution timeout in milliseconds */
|
package/dist/cli/commands/run.js
CHANGED
|
@@ -102,10 +102,38 @@ export async function runCommand(input, options) {
|
|
|
102
102
|
// - If --production is set, no trace (unless --trace explicitly set)
|
|
103
103
|
// - If --trace is set, include trace
|
|
104
104
|
// - Default: include trace in dev mode
|
|
105
|
-
const includeTrace = options.trace
|
|
105
|
+
const includeTrace = options.stream || options.trace || !options.production;
|
|
106
106
|
if (!options.json && mocks) {
|
|
107
107
|
logger.info('Running with mock data');
|
|
108
108
|
}
|
|
109
|
+
// Build onEvent callback for real-time streaming
|
|
110
|
+
const nodeStartTimes = new Map();
|
|
111
|
+
const onEvent = options.stream && !options.json
|
|
112
|
+
? (event) => {
|
|
113
|
+
if (event.type === 'STATUS_CHANGED' && event.data) {
|
|
114
|
+
const nodeId = event.data.id;
|
|
115
|
+
const status = event.data.status;
|
|
116
|
+
if (!nodeId || !status)
|
|
117
|
+
return;
|
|
118
|
+
if (status === 'RUNNING') {
|
|
119
|
+
nodeStartTimes.set(nodeId, event.timestamp);
|
|
120
|
+
logger.log(` [STATUS_CHANGED] ${nodeId}: → RUNNING`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const startTime = nodeStartTimes.get(nodeId);
|
|
124
|
+
const duration = startTime ? ` (${event.timestamp - startTime}ms)` : '';
|
|
125
|
+
logger.log(` [STATUS_CHANGED] ${nodeId}: → ${status}${duration}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (event.type === 'VARIABLE_SET' && event.data) {
|
|
129
|
+
const nodeId = event.data.nodeId;
|
|
130
|
+
const varName = event.data.name;
|
|
131
|
+
if (nodeId && varName) {
|
|
132
|
+
logger.log(` [VARIABLE_SET] ${nodeId}.${varName}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
: undefined;
|
|
109
137
|
const channel = new AgentChannel();
|
|
110
138
|
const execPromise = executeWorkflowFromFile(filePath, params, {
|
|
111
139
|
workflowName: options.workflow,
|
|
@@ -113,6 +141,7 @@ export async function runCommand(input, options) {
|
|
|
113
141
|
includeTrace,
|
|
114
142
|
mocks,
|
|
115
143
|
agentChannel: channel,
|
|
144
|
+
onEvent,
|
|
116
145
|
});
|
|
117
146
|
let result;
|
|
118
147
|
let execDone = false;
|