@synergenius/flow-weaver 0.28.0 → 0.29.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/generate-in-place.js +54 -0
- package/dist/api/generate.js +20 -2
- package/dist/api/parse.js +2 -1
- package/dist/ast/types.d.ts +7 -1
- package/dist/built-in-nodes/generated-registry.d.ts +9 -0
- package/dist/built-in-nodes/generated-registry.js +299 -0
- package/dist/chevrotain-parser/node-parser.js +9 -6
- package/dist/cli/flow-weaver.mjs +425 -18
- package/dist/generated-version.d.ts +1 -1
- package/dist/generated-version.js +1 -1
- package/dist/generator/scope-function-generator.js +3 -2
- package/dist/parser.js +52 -12
- package/package.json +4 -2
|
@@ -46,12 +46,66 @@ export function generateInPlace(sourceCode, ast, options = {}) {
|
|
|
46
46
|
path.resolve(nodeType.sourceLocation.file) !== path.resolve(ast.sourceFile)) {
|
|
47
47
|
continue;
|
|
48
48
|
}
|
|
49
|
+
// Skip built-in auto-injected nodes — step 1.2 handles their insertion
|
|
50
|
+
if (!nodeType.sourceLocation && nodeType.helperText != null) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
49
53
|
const nodeTypeResult = replaceNodeTypeJSDoc(result, nodeType);
|
|
50
54
|
if (nodeTypeResult.changed) {
|
|
51
55
|
result = nodeTypeResult.code;
|
|
52
56
|
hasChanges = true;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
59
|
+
// Step 1.2: Insert built-in node functions that are auto-injected (no source in the file)
|
|
60
|
+
const usedNodeTypes = new Set(ast.instances.map((i) => i.nodeType));
|
|
61
|
+
const builtInNodes = ast.nodeTypes.filter((nt) => !nt.sourceLocation && nt.functionText && nt.helperText != null && usedNodeTypes.has(nt.name));
|
|
62
|
+
if (builtInNodes.length > 0) {
|
|
63
|
+
// Find insertion point: just before the workflow function's JSDoc
|
|
64
|
+
const workflowFnPattern = new RegExp(`(/\\*\\*[\\s\\S]*?@flowWeaver\\s+workflow[\\s\\S]*?\\*/)\\s*\\n\\s*(?:export\\s+)?(?:async\\s+)?function\\s+${ast.functionName}\\b`);
|
|
65
|
+
const match = result.match(workflowFnPattern);
|
|
66
|
+
if (match && match.index !== undefined) {
|
|
67
|
+
const insertionLines = [];
|
|
68
|
+
// Emit shared helpers once (deduplicated)
|
|
69
|
+
const emittedHelpers = new Set();
|
|
70
|
+
for (const node of builtInNodes) {
|
|
71
|
+
const helperText = production ? (node.helperTextProduction ?? null) : (node.helperText ?? null);
|
|
72
|
+
if (helperText && !emittedHelpers.has(helperText)) {
|
|
73
|
+
emittedHelpers.add(helperText);
|
|
74
|
+
insertionLines.push(helperText);
|
|
75
|
+
insertionLines.push('');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Emit each built-in function with its JSDoc
|
|
79
|
+
for (const node of builtInNodes) {
|
|
80
|
+
const funcText = (production && node.functionTextProduction != null) ? node.functionTextProduction : node.functionText;
|
|
81
|
+
if (funcText) {
|
|
82
|
+
// Add JSDoc annotation so the function is recognized on re-parse
|
|
83
|
+
const portAnnotations = [];
|
|
84
|
+
portAnnotations.push('/**');
|
|
85
|
+
portAnnotations.push(` * @flowWeaver nodeType`);
|
|
86
|
+
for (const [name, port] of Object.entries(node.inputs)) {
|
|
87
|
+
if (name === 'execute')
|
|
88
|
+
continue;
|
|
89
|
+
const optPrefix = port.optional ? '[' : '';
|
|
90
|
+
const optSuffix = port.optional ? ']' : '';
|
|
91
|
+
portAnnotations.push(` * @input ${optPrefix}${name}${optSuffix} - ${port.label || name}`);
|
|
92
|
+
}
|
|
93
|
+
for (const [name, port] of Object.entries(node.outputs)) {
|
|
94
|
+
if (name === 'onSuccess' || name === 'onFailure')
|
|
95
|
+
continue;
|
|
96
|
+
portAnnotations.push(` * @output ${name} - ${port.label || name}`);
|
|
97
|
+
}
|
|
98
|
+
portAnnotations.push(' */');
|
|
99
|
+
insertionLines.push(portAnnotations.join('\n'));
|
|
100
|
+
insertionLines.push(funcText);
|
|
101
|
+
insertionLines.push('');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const insertionCode = insertionLines.join('\n');
|
|
105
|
+
result = result.slice(0, match.index) + insertionCode + '\n' + result.slice(match.index);
|
|
106
|
+
hasChanges = true;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
55
109
|
// Step 1.5: Remove orphaned nodeType functions (functions that don't match any AST nodeType)
|
|
56
110
|
// When multi-workflow, consider ALL workflows' node types to avoid deleting types used by siblings
|
|
57
111
|
const cleanupResult = removeOrphanedNodeTypeFunctions(result, ast, allWorkflows);
|
package/dist/api/generate.js
CHANGED
|
@@ -126,7 +126,9 @@ export function generateCode(ast, options) {
|
|
|
126
126
|
// 2. relative file imports (sourceLocation file differs from workflow source)
|
|
127
127
|
// 3. local nodes (same source file)
|
|
128
128
|
const npmPackageNodes = ast.nodeTypes.filter((n) => n.importSource);
|
|
129
|
-
|
|
129
|
+
// Only include built-in nodes (no sourceLocation) that are actually used by this workflow
|
|
130
|
+
const referencedNodeTypes = new Set(ast.instances.map((i) => i.nodeType));
|
|
131
|
+
const localNodes = ast.nodeTypes.filter((n) => !n.importSource && (n.sourceLocation?.file === ast.sourceFile || (!n.sourceLocation && n.functionText && n.helperText != null && referencedNodeTypes.has(n.name))));
|
|
130
132
|
const importedNodes = ast.nodeTypes.filter((n) => !n.importSource && n.sourceLocation?.file !== ast.sourceFile);
|
|
131
133
|
if (importedNodes.length > 0 || npmPackageNodes.length > 0) {
|
|
132
134
|
// Separate FUNCTION imports (from source) vs IMPORTED_WORKFLOW imports (from generated)
|
|
@@ -264,10 +266,26 @@ export function generateCode(ast, options) {
|
|
|
264
266
|
if (inlineFunctions.length > 0) {
|
|
265
267
|
lines.push('');
|
|
266
268
|
addLine();
|
|
269
|
+
// Emit shared helper functions once (deduplicated across built-in nodes)
|
|
270
|
+
// In production mode, use helperTextProduction if set; if undefined, skip helpers entirely
|
|
271
|
+
// (production built-in nodes don't need mock helpers)
|
|
272
|
+
const emittedHelpers = new Set();
|
|
273
|
+
inlineFunctions.forEach((node) => {
|
|
274
|
+
if (node.importSource)
|
|
275
|
+
return;
|
|
276
|
+
const helperText = production ? (node.helperTextProduction ?? null) : (node.helperText ?? null);
|
|
277
|
+
if (helperText && !emittedHelpers.has(helperText)) {
|
|
278
|
+
emittedHelpers.add(helperText);
|
|
279
|
+
lines.push(helperText);
|
|
280
|
+
helperText.split('\n').forEach(() => addLine());
|
|
281
|
+
lines.push('');
|
|
282
|
+
addLine();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
267
285
|
inlineFunctions.forEach((node) => {
|
|
268
286
|
if (node.importSource)
|
|
269
287
|
return; // Never inline npm package functions
|
|
270
|
-
const functionText = node.functionText;
|
|
288
|
+
const functionText = (production && node.functionTextProduction != null) ? node.functionTextProduction : node.functionText;
|
|
271
289
|
if (functionText) {
|
|
272
290
|
const functionWithoutDecorators = removeDecorators(functionText);
|
|
273
291
|
// Add source mapping for the node function
|
package/dist/api/parse.js
CHANGED
|
@@ -32,6 +32,7 @@ export async function parseWorkflow(filePath, options) {
|
|
|
32
32
|
// Parse the file to extract nodes and workflows
|
|
33
33
|
const parsed = parser.parse(filePath);
|
|
34
34
|
warnings.push(...parsed.warnings);
|
|
35
|
+
errors.push(...parsed.errors);
|
|
35
36
|
// Get available workflow names
|
|
36
37
|
availableWorkflows = parsed.workflows.map((w) => w.functionName);
|
|
37
38
|
// Determine which workflow to use
|
|
@@ -82,7 +83,7 @@ export async function parseWorkflow(filePath, options) {
|
|
|
82
83
|
}
|
|
83
84
|
return {
|
|
84
85
|
ast: workflow,
|
|
85
|
-
errors
|
|
86
|
+
errors,
|
|
86
87
|
warnings,
|
|
87
88
|
availableWorkflows,
|
|
88
89
|
allWorkflows: parsed.workflows,
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -259,8 +259,14 @@ export type TNodeTypeAST = {
|
|
|
259
259
|
y?: number;
|
|
260
260
|
/** Source code location */
|
|
261
261
|
sourceLocation?: TSourceLocation;
|
|
262
|
-
/** Original function source text */
|
|
262
|
+
/** Original function source text (main function only) */
|
|
263
263
|
functionText?: string;
|
|
264
|
+
/** Production-optimized function source text (mock code stripped) */
|
|
265
|
+
functionTextProduction?: string;
|
|
266
|
+
/** Shared helper functions (mock helpers, utility functions) emitted once before main functions */
|
|
267
|
+
helperText?: string;
|
|
268
|
+
/** Production-optimized helper text */
|
|
269
|
+
helperTextProduction?: string;
|
|
264
270
|
/** How success/failure branching works */
|
|
265
271
|
branchingStrategy?: TBranchingStrategy;
|
|
266
272
|
/** Field name for value-based branching */
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO NOT EDIT - generated by scripts/generate-built-in-registry.ts
|
|
3
|
+
*
|
|
4
|
+
* Registry of built-in node types for auto-injection into the parser.
|
|
5
|
+
* Generated from the actual source files in src/built-in-nodes/.
|
|
6
|
+
*/
|
|
7
|
+
import type { TNodeTypeAST } from '../ast/types.js';
|
|
8
|
+
export declare const BUILT_IN_NODE_TYPES: TNodeTypeAST[];
|
|
9
|
+
//# sourceMappingURL=generated-registry.d.ts.map
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO NOT EDIT - generated by scripts/generate-built-in-registry.ts
|
|
3
|
+
*
|
|
4
|
+
* Registry of built-in node types for auto-injection into the parser.
|
|
5
|
+
* Generated from the actual source files in src/built-in-nodes/.
|
|
6
|
+
*/
|
|
7
|
+
export const BUILT_IN_NODE_TYPES = [
|
|
8
|
+
{
|
|
9
|
+
type: 'NodeType',
|
|
10
|
+
name: 'delay',
|
|
11
|
+
functionName: 'delay',
|
|
12
|
+
isAsync: true,
|
|
13
|
+
hasSuccessPort: true,
|
|
14
|
+
hasFailurePort: true,
|
|
15
|
+
executeWhen: 'CONJUNCTION',
|
|
16
|
+
variant: 'FUNCTION',
|
|
17
|
+
inputs: {
|
|
18
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
19
|
+
duration: { dataType: 'STRING', label: 'Duration to sleep (e.g. "30s", "5m", "1h", "2d")', tsType: 'string' },
|
|
20
|
+
},
|
|
21
|
+
outputs: {
|
|
22
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
23
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
24
|
+
elapsed: { dataType: 'BOOLEAN', label: 'Always true after sleep completes', tsType: 'boolean' },
|
|
25
|
+
},
|
|
26
|
+
helperText: `
|
|
27
|
+
function __fw_getMockConfig() {
|
|
28
|
+
return globalThis.__fw_mocks__;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function __fw_lookupMock(section, key) {
|
|
32
|
+
if (!section)
|
|
33
|
+
return undefined;
|
|
34
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
35
|
+
if (nodeId) {
|
|
36
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
37
|
+
if (qualified !== undefined)
|
|
38
|
+
return qualified;
|
|
39
|
+
}
|
|
40
|
+
return section[key];
|
|
41
|
+
}
|
|
42
|
+
`.trim(),
|
|
43
|
+
helperTextProduction: undefined,
|
|
44
|
+
functionText: `
|
|
45
|
+
async function delay(execute, duration) {
|
|
46
|
+
if (!execute)
|
|
47
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
48
|
+
const mocks = __fw_getMockConfig();
|
|
49
|
+
if (mocks?.fast) {
|
|
50
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const ms = __fw_parseDuration(duration);
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
55
|
+
}
|
|
56
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
57
|
+
}
|
|
58
|
+
function __fw_parseDuration(duration) {
|
|
59
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
60
|
+
if (!match)
|
|
61
|
+
return 0;
|
|
62
|
+
const [, value, unit] = match;
|
|
63
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
64
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
65
|
+
}
|
|
66
|
+
`.trim(),
|
|
67
|
+
functionTextProduction: `
|
|
68
|
+
async function delay(execute, duration) {
|
|
69
|
+
if (!execute)
|
|
70
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
71
|
+
const ms = __fw_parseDuration(duration);
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
73
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
74
|
+
}
|
|
75
|
+
function __fw_parseDuration(duration) {
|
|
76
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
77
|
+
if (!match)
|
|
78
|
+
return 0;
|
|
79
|
+
const [, value, unit] = match;
|
|
80
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
81
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
82
|
+
}
|
|
83
|
+
`.trim(),
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'NodeType',
|
|
87
|
+
name: 'waitForEvent',
|
|
88
|
+
functionName: 'waitForEvent',
|
|
89
|
+
isAsync: true,
|
|
90
|
+
hasSuccessPort: true,
|
|
91
|
+
hasFailurePort: true,
|
|
92
|
+
executeWhen: 'CONJUNCTION',
|
|
93
|
+
variant: 'FUNCTION',
|
|
94
|
+
inputs: {
|
|
95
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
96
|
+
eventName: { dataType: 'STRING', label: 'Event name to wait for (e.g. "app/approval.received")', tsType: 'string' },
|
|
97
|
+
match: { dataType: 'STRING', label: 'Field to match between trigger and waited event (e.g. "data.requestId")', tsType: 'string', optional: true },
|
|
98
|
+
timeout: { dataType: 'STRING', label: 'Max wait time (e.g. "24h", "7d"). Empty = no timeout', tsType: 'string', optional: true },
|
|
99
|
+
},
|
|
100
|
+
outputs: {
|
|
101
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
102
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
103
|
+
eventData: { dataType: 'OBJECT', label: 'The received event\'s data payload', tsType: 'object' },
|
|
104
|
+
},
|
|
105
|
+
helperText: `
|
|
106
|
+
function __fw_getMockConfig() {
|
|
107
|
+
return globalThis.__fw_mocks__;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function __fw_lookupMock(section, key) {
|
|
111
|
+
if (!section)
|
|
112
|
+
return undefined;
|
|
113
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
114
|
+
if (nodeId) {
|
|
115
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
116
|
+
if (qualified !== undefined)
|
|
117
|
+
return qualified;
|
|
118
|
+
}
|
|
119
|
+
return section[key];
|
|
120
|
+
}
|
|
121
|
+
`.trim(),
|
|
122
|
+
helperTextProduction: undefined,
|
|
123
|
+
functionText: `
|
|
124
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
125
|
+
if (!execute)
|
|
126
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
127
|
+
const mocks = __fw_getMockConfig();
|
|
128
|
+
if (mocks) {
|
|
129
|
+
const mockData = __fw_lookupMock(mocks.events, eventName);
|
|
130
|
+
if (mockData !== undefined) {
|
|
131
|
+
return { onSuccess: true, onFailure: false, eventData: mockData };
|
|
132
|
+
}
|
|
133
|
+
return { onSuccess: false, onFailure: true, eventData: {} };
|
|
134
|
+
}
|
|
135
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
136
|
+
}
|
|
137
|
+
`.trim(),
|
|
138
|
+
functionTextProduction: `
|
|
139
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
140
|
+
if (!execute)
|
|
141
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
142
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
143
|
+
}
|
|
144
|
+
`.trim(),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
type: 'NodeType',
|
|
148
|
+
name: 'invokeWorkflow',
|
|
149
|
+
functionName: 'invokeWorkflow',
|
|
150
|
+
isAsync: true,
|
|
151
|
+
hasSuccessPort: true,
|
|
152
|
+
hasFailurePort: true,
|
|
153
|
+
executeWhen: 'CONJUNCTION',
|
|
154
|
+
variant: 'FUNCTION',
|
|
155
|
+
inputs: {
|
|
156
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
157
|
+
functionId: { dataType: 'STRING', label: 'Function ID of the workflow to invoke (e.g. "my-service/sub-workflow")', tsType: 'string' },
|
|
158
|
+
payload: { dataType: 'OBJECT', label: 'Data to pass as event.data to the invoked function', tsType: 'object' },
|
|
159
|
+
timeout: { dataType: 'STRING', label: 'Max wait time (e.g. "1h")', tsType: 'string', optional: true },
|
|
160
|
+
},
|
|
161
|
+
outputs: {
|
|
162
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
163
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
164
|
+
result: { dataType: 'OBJECT', label: 'Return value from the invoked function', tsType: 'object' },
|
|
165
|
+
},
|
|
166
|
+
helperText: `
|
|
167
|
+
function __fw_getMockConfig() {
|
|
168
|
+
return globalThis.__fw_mocks__;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function __fw_lookupMock(section, key) {
|
|
172
|
+
if (!section)
|
|
173
|
+
return undefined;
|
|
174
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
175
|
+
if (nodeId) {
|
|
176
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
177
|
+
if (qualified !== undefined)
|
|
178
|
+
return qualified;
|
|
179
|
+
}
|
|
180
|
+
return section[key];
|
|
181
|
+
}
|
|
182
|
+
`.trim(),
|
|
183
|
+
helperTextProduction: undefined,
|
|
184
|
+
functionText: `
|
|
185
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
186
|
+
if (!execute)
|
|
187
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
188
|
+
const mocks = __fw_getMockConfig();
|
|
189
|
+
if (mocks) {
|
|
190
|
+
const mockResult = __fw_lookupMock(mocks.invocations, functionId);
|
|
191
|
+
if (mockResult !== undefined) {
|
|
192
|
+
return { onSuccess: true, onFailure: false, result: mockResult };
|
|
193
|
+
}
|
|
194
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
195
|
+
}
|
|
196
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
197
|
+
if (registry?.[functionId]) {
|
|
198
|
+
try {
|
|
199
|
+
const result = await registry[functionId](true, payload);
|
|
200
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
207
|
+
}
|
|
208
|
+
`.trim(),
|
|
209
|
+
functionTextProduction: `
|
|
210
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
211
|
+
if (!execute)
|
|
212
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
213
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
214
|
+
if (registry?.[functionId]) {
|
|
215
|
+
try {
|
|
216
|
+
const result = await registry[functionId](true, payload);
|
|
217
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
224
|
+
}
|
|
225
|
+
`.trim(),
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: 'NodeType',
|
|
229
|
+
name: 'waitForAgent',
|
|
230
|
+
functionName: 'waitForAgent',
|
|
231
|
+
isAsync: true,
|
|
232
|
+
hasSuccessPort: true,
|
|
233
|
+
hasFailurePort: true,
|
|
234
|
+
executeWhen: 'CONJUNCTION',
|
|
235
|
+
variant: 'FUNCTION',
|
|
236
|
+
inputs: {
|
|
237
|
+
execute: { dataType: 'STEP', label: 'Execute' },
|
|
238
|
+
agentId: { dataType: 'STRING', label: 'Agent/task identifier', tsType: 'string' },
|
|
239
|
+
context: { dataType: 'OBJECT', label: 'Context data to send to the agent', tsType: 'object' },
|
|
240
|
+
prompt: { dataType: 'STRING', label: 'Message to display when requesting input', tsType: 'string', optional: true },
|
|
241
|
+
},
|
|
242
|
+
outputs: {
|
|
243
|
+
onSuccess: { dataType: 'STEP', label: 'On Success', isControlFlow: true },
|
|
244
|
+
onFailure: { dataType: 'STEP', label: 'On Failure', isControlFlow: true, failure: true },
|
|
245
|
+
agentResult: { dataType: 'OBJECT', label: 'Result returned by the agent', tsType: 'object' },
|
|
246
|
+
},
|
|
247
|
+
helperText: `
|
|
248
|
+
function __fw_getMockConfig() {
|
|
249
|
+
return globalThis.__fw_mocks__;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function __fw_lookupMock(section, key) {
|
|
253
|
+
if (!section)
|
|
254
|
+
return undefined;
|
|
255
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
256
|
+
if (nodeId) {
|
|
257
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
258
|
+
if (qualified !== undefined)
|
|
259
|
+
return qualified;
|
|
260
|
+
}
|
|
261
|
+
return section[key];
|
|
262
|
+
}
|
|
263
|
+
`.trim(),
|
|
264
|
+
helperTextProduction: undefined,
|
|
265
|
+
functionText: `
|
|
266
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
267
|
+
if (!execute)
|
|
268
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
269
|
+
const mocks = __fw_getMockConfig();
|
|
270
|
+
const mockResult = __fw_lookupMock(mocks?.agents, agentId);
|
|
271
|
+
if (mockResult !== undefined) {
|
|
272
|
+
return { onSuccess: true, onFailure: false, agentResult: mockResult };
|
|
273
|
+
}
|
|
274
|
+
if (mocks?.agents) {
|
|
275
|
+
return { onSuccess: false, onFailure: true, agentResult: {} };
|
|
276
|
+
}
|
|
277
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
278
|
+
if (channel) {
|
|
279
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
280
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
281
|
+
}
|
|
282
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
283
|
+
}
|
|
284
|
+
`.trim(),
|
|
285
|
+
functionTextProduction: `
|
|
286
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
287
|
+
if (!execute)
|
|
288
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
289
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
290
|
+
if (channel) {
|
|
291
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
292
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
293
|
+
}
|
|
294
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
295
|
+
}
|
|
296
|
+
`.trim(),
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
//# sourceMappingURL=generated-registry.js.map
|
|
@@ -65,14 +65,13 @@ class NodeParser extends CstParser {
|
|
|
65
65
|
this.CONSUME(LabelPrefix);
|
|
66
66
|
this.CONSUME(StringLiteral, { LABEL: 'labelValue' });
|
|
67
67
|
});
|
|
68
|
-
// expr: port="value", port2="value2"
|
|
68
|
+
// expr: port="value", port2="value2" (comma optional between assignments)
|
|
69
69
|
exprAttr = this.RULE('exprAttr', () => {
|
|
70
70
|
this.CONSUME(ExprPrefix);
|
|
71
|
-
this.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
},
|
|
71
|
+
this.SUBRULE(this.exprAssignment);
|
|
72
|
+
this.MANY(() => {
|
|
73
|
+
this.OPTION(() => this.CONSUME(Comma));
|
|
74
|
+
this.SUBRULE2(this.exprAssignment);
|
|
76
75
|
});
|
|
77
76
|
});
|
|
78
77
|
// port="value"
|
|
@@ -524,6 +523,10 @@ const visitorInstance = new NodeVisitor();
|
|
|
524
523
|
export function parseNodeLine(input, warnings) {
|
|
525
524
|
const lexResult = JSDocLexer.tokenize(input);
|
|
526
525
|
if (lexResult.errors.length > 0) {
|
|
526
|
+
const truncatedInput = input.length > 60 ? input.substring(0, 60) + '...' : input;
|
|
527
|
+
warnings.push(`Failed to tokenize node line: "${truncatedInput}"\n` +
|
|
528
|
+
` Error: ${lexResult.errors[0].message}\n` +
|
|
529
|
+
` Expected format: @node instanceId NodeType`);
|
|
527
530
|
return null;
|
|
528
531
|
}
|
|
529
532
|
// Check if starts with @node
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -5987,7 +5987,7 @@ var VERSION;
|
|
|
5987
5987
|
var init_generated_version = __esm({
|
|
5988
5988
|
"src/generated-version.ts"() {
|
|
5989
5989
|
"use strict";
|
|
5990
|
-
VERSION = "0.
|
|
5990
|
+
VERSION = "0.29.1";
|
|
5991
5991
|
}
|
|
5992
5992
|
});
|
|
5993
5993
|
|
|
@@ -6651,7 +6651,7 @@ function generateScopeFunctionClosure(scopeName, parentNodeId, parentNodeType, w
|
|
|
6651
6651
|
lines.push(` // Execute: ${child.id} (${child.nodeType})`);
|
|
6652
6652
|
if (emitDebugHooks) {
|
|
6653
6653
|
const awaitHook = isAsync2 ? "await " : "";
|
|
6654
|
-
lines.push(` let ${safeChildId}Idx: number;`);
|
|
6654
|
+
lines.push(` let ${safeChildId}Idx: number = -1;`);
|
|
6655
6655
|
lines.push(` if (${awaitHook}__ctrl__.beforeNode('${child.id}', scopedCtx)) {`);
|
|
6656
6656
|
childIndent = " ";
|
|
6657
6657
|
}
|
|
@@ -14039,8 +14039,9 @@ function generateCode(ast, options) {
|
|
|
14039
14039
|
}
|
|
14040
14040
|
}
|
|
14041
14041
|
const npmPackageNodes = ast.nodeTypes.filter((n7) => n7.importSource);
|
|
14042
|
+
const referencedNodeTypes = new Set(ast.instances.map((i) => i.nodeType));
|
|
14042
14043
|
const localNodes = ast.nodeTypes.filter(
|
|
14043
|
-
(n7) => !n7.importSource && n7.sourceLocation?.file === ast.sourceFile
|
|
14044
|
+
(n7) => !n7.importSource && (n7.sourceLocation?.file === ast.sourceFile || !n7.sourceLocation && n7.functionText && n7.helperText != null && referencedNodeTypes.has(n7.name))
|
|
14044
14045
|
);
|
|
14045
14046
|
const importedNodes = ast.nodeTypes.filter(
|
|
14046
14047
|
(n7) => !n7.importSource && n7.sourceLocation?.file !== ast.sourceFile
|
|
@@ -14151,9 +14152,21 @@ function generateCode(ast, options) {
|
|
|
14151
14152
|
if (inlineFunctions.length > 0) {
|
|
14152
14153
|
lines.push("");
|
|
14153
14154
|
addLine();
|
|
14155
|
+
const emittedHelpers = /* @__PURE__ */ new Set();
|
|
14154
14156
|
inlineFunctions.forEach((node) => {
|
|
14155
14157
|
if (node.importSource) return;
|
|
14156
|
-
const
|
|
14158
|
+
const helperText = production ? node.helperTextProduction ?? null : node.helperText ?? null;
|
|
14159
|
+
if (helperText && !emittedHelpers.has(helperText)) {
|
|
14160
|
+
emittedHelpers.add(helperText);
|
|
14161
|
+
lines.push(helperText);
|
|
14162
|
+
helperText.split("\n").forEach(() => addLine());
|
|
14163
|
+
lines.push("");
|
|
14164
|
+
addLine();
|
|
14165
|
+
}
|
|
14166
|
+
});
|
|
14167
|
+
inlineFunctions.forEach((node) => {
|
|
14168
|
+
if (node.importSource) return;
|
|
14169
|
+
const functionText = production && node.functionTextProduction != null ? node.functionTextProduction : node.functionText;
|
|
14157
14170
|
if (functionText) {
|
|
14158
14171
|
const functionWithoutDecorators = removeDecorators(functionText);
|
|
14159
14172
|
if (node.sourceLocation) {
|
|
@@ -15275,12 +15288,62 @@ function generateInPlace(sourceCode, ast, options = {}) {
|
|
|
15275
15288
|
if (nodeType.sourceLocation?.file && path2.resolve(nodeType.sourceLocation.file) !== path2.resolve(ast.sourceFile)) {
|
|
15276
15289
|
continue;
|
|
15277
15290
|
}
|
|
15291
|
+
if (!nodeType.sourceLocation && nodeType.helperText != null) {
|
|
15292
|
+
continue;
|
|
15293
|
+
}
|
|
15278
15294
|
const nodeTypeResult = replaceNodeTypeJSDoc(result, nodeType);
|
|
15279
15295
|
if (nodeTypeResult.changed) {
|
|
15280
15296
|
result = nodeTypeResult.code;
|
|
15281
15297
|
hasChanges = true;
|
|
15282
15298
|
}
|
|
15283
15299
|
}
|
|
15300
|
+
const usedNodeTypes = new Set(ast.instances.map((i) => i.nodeType));
|
|
15301
|
+
const builtInNodes = ast.nodeTypes.filter(
|
|
15302
|
+
(nt2) => !nt2.sourceLocation && nt2.functionText && nt2.helperText != null && usedNodeTypes.has(nt2.name)
|
|
15303
|
+
);
|
|
15304
|
+
if (builtInNodes.length > 0) {
|
|
15305
|
+
const workflowFnPattern = new RegExp(
|
|
15306
|
+
`(/\\*\\*[\\s\\S]*?@flowWeaver\\s+workflow[\\s\\S]*?\\*/)\\s*\\n\\s*(?:export\\s+)?(?:async\\s+)?function\\s+${ast.functionName}\\b`
|
|
15307
|
+
);
|
|
15308
|
+
const match = result.match(workflowFnPattern);
|
|
15309
|
+
if (match && match.index !== void 0) {
|
|
15310
|
+
const insertionLines = [];
|
|
15311
|
+
const emittedHelpers = /* @__PURE__ */ new Set();
|
|
15312
|
+
for (const node of builtInNodes) {
|
|
15313
|
+
const helperText = production ? node.helperTextProduction ?? null : node.helperText ?? null;
|
|
15314
|
+
if (helperText && !emittedHelpers.has(helperText)) {
|
|
15315
|
+
emittedHelpers.add(helperText);
|
|
15316
|
+
insertionLines.push(helperText);
|
|
15317
|
+
insertionLines.push("");
|
|
15318
|
+
}
|
|
15319
|
+
}
|
|
15320
|
+
for (const node of builtInNodes) {
|
|
15321
|
+
const funcText = production && node.functionTextProduction != null ? node.functionTextProduction : node.functionText;
|
|
15322
|
+
if (funcText) {
|
|
15323
|
+
const portAnnotations = [];
|
|
15324
|
+
portAnnotations.push("/**");
|
|
15325
|
+
portAnnotations.push(` * @flowWeaver nodeType`);
|
|
15326
|
+
for (const [name, port] of Object.entries(node.inputs)) {
|
|
15327
|
+
if (name === "execute") continue;
|
|
15328
|
+
const optPrefix = port.optional ? "[" : "";
|
|
15329
|
+
const optSuffix = port.optional ? "]" : "";
|
|
15330
|
+
portAnnotations.push(` * @input ${optPrefix}${name}${optSuffix} - ${port.label || name}`);
|
|
15331
|
+
}
|
|
15332
|
+
for (const [name, port] of Object.entries(node.outputs)) {
|
|
15333
|
+
if (name === "onSuccess" || name === "onFailure") continue;
|
|
15334
|
+
portAnnotations.push(` * @output ${name} - ${port.label || name}`);
|
|
15335
|
+
}
|
|
15336
|
+
portAnnotations.push(" */");
|
|
15337
|
+
insertionLines.push(portAnnotations.join("\n"));
|
|
15338
|
+
insertionLines.push(funcText);
|
|
15339
|
+
insertionLines.push("");
|
|
15340
|
+
}
|
|
15341
|
+
}
|
|
15342
|
+
const insertionCode = insertionLines.join("\n");
|
|
15343
|
+
result = result.slice(0, match.index) + insertionCode + "\n" + result.slice(match.index);
|
|
15344
|
+
hasChanges = true;
|
|
15345
|
+
}
|
|
15346
|
+
}
|
|
15284
15347
|
const cleanupResult = removeOrphanedNodeTypeFunctions(result, ast, allWorkflows);
|
|
15285
15348
|
if (cleanupResult.changed) {
|
|
15286
15349
|
result = cleanupResult.code;
|
|
@@ -24302,6 +24365,12 @@ var init_port_parser = __esm({
|
|
|
24302
24365
|
function parseNodeLine(input, warnings) {
|
|
24303
24366
|
const lexResult = JSDocLexer.tokenize(input);
|
|
24304
24367
|
if (lexResult.errors.length > 0) {
|
|
24368
|
+
const truncatedInput = input.length > 60 ? input.substring(0, 60) + "..." : input;
|
|
24369
|
+
warnings.push(
|
|
24370
|
+
`Failed to tokenize node line: "${truncatedInput}"
|
|
24371
|
+
Error: ${lexResult.errors[0].message}
|
|
24372
|
+
Expected format: @node instanceId NodeType`
|
|
24373
|
+
);
|
|
24305
24374
|
return null;
|
|
24306
24375
|
}
|
|
24307
24376
|
if (lexResult.tokens.length === 0) {
|
|
@@ -24389,14 +24458,13 @@ var init_node_parser = __esm({
|
|
|
24389
24458
|
this.CONSUME(LabelPrefix);
|
|
24390
24459
|
this.CONSUME(StringLiteral, { LABEL: "labelValue" });
|
|
24391
24460
|
});
|
|
24392
|
-
// expr: port="value", port2="value2"
|
|
24461
|
+
// expr: port="value", port2="value2" (comma optional between assignments)
|
|
24393
24462
|
exprAttr = this.RULE("exprAttr", () => {
|
|
24394
24463
|
this.CONSUME(ExprPrefix);
|
|
24395
|
-
this.
|
|
24396
|
-
|
|
24397
|
-
|
|
24398
|
-
|
|
24399
|
-
}
|
|
24464
|
+
this.SUBRULE(this.exprAssignment);
|
|
24465
|
+
this.MANY(() => {
|
|
24466
|
+
this.OPTION(() => this.CONSUME(Comma));
|
|
24467
|
+
this.SUBRULE2(this.exprAssignment);
|
|
24400
24468
|
});
|
|
24401
24469
|
});
|
|
24402
24470
|
// port="value"
|
|
@@ -27260,6 +27328,306 @@ var init_coercion_types = __esm({
|
|
|
27260
27328
|
}
|
|
27261
27329
|
});
|
|
27262
27330
|
|
|
27331
|
+
// src/built-in-nodes/generated-registry.ts
|
|
27332
|
+
var BUILT_IN_NODE_TYPES;
|
|
27333
|
+
var init_generated_registry = __esm({
|
|
27334
|
+
"src/built-in-nodes/generated-registry.ts"() {
|
|
27335
|
+
"use strict";
|
|
27336
|
+
BUILT_IN_NODE_TYPES = [
|
|
27337
|
+
{
|
|
27338
|
+
type: "NodeType",
|
|
27339
|
+
name: "delay",
|
|
27340
|
+
functionName: "delay",
|
|
27341
|
+
isAsync: true,
|
|
27342
|
+
hasSuccessPort: true,
|
|
27343
|
+
hasFailurePort: true,
|
|
27344
|
+
executeWhen: "CONJUNCTION",
|
|
27345
|
+
variant: "FUNCTION",
|
|
27346
|
+
inputs: {
|
|
27347
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27348
|
+
duration: { dataType: "STRING", label: 'Duration to sleep (e.g. "30s", "5m", "1h", "2d")', tsType: "string" }
|
|
27349
|
+
},
|
|
27350
|
+
outputs: {
|
|
27351
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27352
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27353
|
+
elapsed: { dataType: "BOOLEAN", label: "Always true after sleep completes", tsType: "boolean" }
|
|
27354
|
+
},
|
|
27355
|
+
helperText: `
|
|
27356
|
+
function __fw_getMockConfig() {
|
|
27357
|
+
return globalThis.__fw_mocks__;
|
|
27358
|
+
}
|
|
27359
|
+
|
|
27360
|
+
function __fw_lookupMock(section, key) {
|
|
27361
|
+
if (!section)
|
|
27362
|
+
return undefined;
|
|
27363
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27364
|
+
if (nodeId) {
|
|
27365
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27366
|
+
if (qualified !== undefined)
|
|
27367
|
+
return qualified;
|
|
27368
|
+
}
|
|
27369
|
+
return section[key];
|
|
27370
|
+
}
|
|
27371
|
+
`.trim(),
|
|
27372
|
+
helperTextProduction: void 0,
|
|
27373
|
+
functionText: `
|
|
27374
|
+
async function delay(execute, duration) {
|
|
27375
|
+
if (!execute)
|
|
27376
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
27377
|
+
const mocks = __fw_getMockConfig();
|
|
27378
|
+
if (mocks?.fast) {
|
|
27379
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
27380
|
+
}
|
|
27381
|
+
else {
|
|
27382
|
+
const ms = __fw_parseDuration(duration);
|
|
27383
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
27384
|
+
}
|
|
27385
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
27386
|
+
}
|
|
27387
|
+
function __fw_parseDuration(duration) {
|
|
27388
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
27389
|
+
if (!match)
|
|
27390
|
+
return 0;
|
|
27391
|
+
const [, value, unit] = match;
|
|
27392
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
27393
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
27394
|
+
}
|
|
27395
|
+
`.trim(),
|
|
27396
|
+
functionTextProduction: `
|
|
27397
|
+
async function delay(execute, duration) {
|
|
27398
|
+
if (!execute)
|
|
27399
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
27400
|
+
const ms = __fw_parseDuration(duration);
|
|
27401
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
27402
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
27403
|
+
}
|
|
27404
|
+
function __fw_parseDuration(duration) {
|
|
27405
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
27406
|
+
if (!match)
|
|
27407
|
+
return 0;
|
|
27408
|
+
const [, value, unit] = match;
|
|
27409
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
27410
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
27411
|
+
}
|
|
27412
|
+
`.trim()
|
|
27413
|
+
},
|
|
27414
|
+
{
|
|
27415
|
+
type: "NodeType",
|
|
27416
|
+
name: "waitForEvent",
|
|
27417
|
+
functionName: "waitForEvent",
|
|
27418
|
+
isAsync: true,
|
|
27419
|
+
hasSuccessPort: true,
|
|
27420
|
+
hasFailurePort: true,
|
|
27421
|
+
executeWhen: "CONJUNCTION",
|
|
27422
|
+
variant: "FUNCTION",
|
|
27423
|
+
inputs: {
|
|
27424
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27425
|
+
eventName: { dataType: "STRING", label: 'Event name to wait for (e.g. "app/approval.received")', tsType: "string" },
|
|
27426
|
+
match: { dataType: "STRING", label: 'Field to match between trigger and waited event (e.g. "data.requestId")', tsType: "string", optional: true },
|
|
27427
|
+
timeout: { dataType: "STRING", label: 'Max wait time (e.g. "24h", "7d"). Empty = no timeout', tsType: "string", optional: true }
|
|
27428
|
+
},
|
|
27429
|
+
outputs: {
|
|
27430
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27431
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27432
|
+
eventData: { dataType: "OBJECT", label: "The received event's data payload", tsType: "object" }
|
|
27433
|
+
},
|
|
27434
|
+
helperText: `
|
|
27435
|
+
function __fw_getMockConfig() {
|
|
27436
|
+
return globalThis.__fw_mocks__;
|
|
27437
|
+
}
|
|
27438
|
+
|
|
27439
|
+
function __fw_lookupMock(section, key) {
|
|
27440
|
+
if (!section)
|
|
27441
|
+
return undefined;
|
|
27442
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27443
|
+
if (nodeId) {
|
|
27444
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27445
|
+
if (qualified !== undefined)
|
|
27446
|
+
return qualified;
|
|
27447
|
+
}
|
|
27448
|
+
return section[key];
|
|
27449
|
+
}
|
|
27450
|
+
`.trim(),
|
|
27451
|
+
helperTextProduction: void 0,
|
|
27452
|
+
functionText: `
|
|
27453
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
27454
|
+
if (!execute)
|
|
27455
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
27456
|
+
const mocks = __fw_getMockConfig();
|
|
27457
|
+
if (mocks) {
|
|
27458
|
+
const mockData = __fw_lookupMock(mocks.events, eventName);
|
|
27459
|
+
if (mockData !== undefined) {
|
|
27460
|
+
return { onSuccess: true, onFailure: false, eventData: mockData };
|
|
27461
|
+
}
|
|
27462
|
+
return { onSuccess: false, onFailure: true, eventData: {} };
|
|
27463
|
+
}
|
|
27464
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
27465
|
+
}
|
|
27466
|
+
`.trim(),
|
|
27467
|
+
functionTextProduction: `
|
|
27468
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
27469
|
+
if (!execute)
|
|
27470
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
27471
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
27472
|
+
}
|
|
27473
|
+
`.trim()
|
|
27474
|
+
},
|
|
27475
|
+
{
|
|
27476
|
+
type: "NodeType",
|
|
27477
|
+
name: "invokeWorkflow",
|
|
27478
|
+
functionName: "invokeWorkflow",
|
|
27479
|
+
isAsync: true,
|
|
27480
|
+
hasSuccessPort: true,
|
|
27481
|
+
hasFailurePort: true,
|
|
27482
|
+
executeWhen: "CONJUNCTION",
|
|
27483
|
+
variant: "FUNCTION",
|
|
27484
|
+
inputs: {
|
|
27485
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27486
|
+
functionId: { dataType: "STRING", label: 'Function ID of the workflow to invoke (e.g. "my-service/sub-workflow")', tsType: "string" },
|
|
27487
|
+
payload: { dataType: "OBJECT", label: "Data to pass as event.data to the invoked function", tsType: "object" },
|
|
27488
|
+
timeout: { dataType: "STRING", label: 'Max wait time (e.g. "1h")', tsType: "string", optional: true }
|
|
27489
|
+
},
|
|
27490
|
+
outputs: {
|
|
27491
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27492
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27493
|
+
result: { dataType: "OBJECT", label: "Return value from the invoked function", tsType: "object" }
|
|
27494
|
+
},
|
|
27495
|
+
helperText: `
|
|
27496
|
+
function __fw_getMockConfig() {
|
|
27497
|
+
return globalThis.__fw_mocks__;
|
|
27498
|
+
}
|
|
27499
|
+
|
|
27500
|
+
function __fw_lookupMock(section, key) {
|
|
27501
|
+
if (!section)
|
|
27502
|
+
return undefined;
|
|
27503
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27504
|
+
if (nodeId) {
|
|
27505
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27506
|
+
if (qualified !== undefined)
|
|
27507
|
+
return qualified;
|
|
27508
|
+
}
|
|
27509
|
+
return section[key];
|
|
27510
|
+
}
|
|
27511
|
+
`.trim(),
|
|
27512
|
+
helperTextProduction: void 0,
|
|
27513
|
+
functionText: `
|
|
27514
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
27515
|
+
if (!execute)
|
|
27516
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
27517
|
+
const mocks = __fw_getMockConfig();
|
|
27518
|
+
if (mocks) {
|
|
27519
|
+
const mockResult = __fw_lookupMock(mocks.invocations, functionId);
|
|
27520
|
+
if (mockResult !== undefined) {
|
|
27521
|
+
return { onSuccess: true, onFailure: false, result: mockResult };
|
|
27522
|
+
}
|
|
27523
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
27524
|
+
}
|
|
27525
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
27526
|
+
if (registry?.[functionId]) {
|
|
27527
|
+
try {
|
|
27528
|
+
const result = await registry[functionId](true, payload);
|
|
27529
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
27530
|
+
}
|
|
27531
|
+
catch {
|
|
27532
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
27533
|
+
}
|
|
27534
|
+
}
|
|
27535
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
27536
|
+
}
|
|
27537
|
+
`.trim(),
|
|
27538
|
+
functionTextProduction: `
|
|
27539
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
27540
|
+
if (!execute)
|
|
27541
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
27542
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
27543
|
+
if (registry?.[functionId]) {
|
|
27544
|
+
try {
|
|
27545
|
+
const result = await registry[functionId](true, payload);
|
|
27546
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
27547
|
+
}
|
|
27548
|
+
catch {
|
|
27549
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
27550
|
+
}
|
|
27551
|
+
}
|
|
27552
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
27553
|
+
}
|
|
27554
|
+
`.trim()
|
|
27555
|
+
},
|
|
27556
|
+
{
|
|
27557
|
+
type: "NodeType",
|
|
27558
|
+
name: "waitForAgent",
|
|
27559
|
+
functionName: "waitForAgent",
|
|
27560
|
+
isAsync: true,
|
|
27561
|
+
hasSuccessPort: true,
|
|
27562
|
+
hasFailurePort: true,
|
|
27563
|
+
executeWhen: "CONJUNCTION",
|
|
27564
|
+
variant: "FUNCTION",
|
|
27565
|
+
inputs: {
|
|
27566
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27567
|
+
agentId: { dataType: "STRING", label: "Agent/task identifier", tsType: "string" },
|
|
27568
|
+
context: { dataType: "OBJECT", label: "Context data to send to the agent", tsType: "object" },
|
|
27569
|
+
prompt: { dataType: "STRING", label: "Message to display when requesting input", tsType: "string", optional: true }
|
|
27570
|
+
},
|
|
27571
|
+
outputs: {
|
|
27572
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27573
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27574
|
+
agentResult: { dataType: "OBJECT", label: "Result returned by the agent", tsType: "object" }
|
|
27575
|
+
},
|
|
27576
|
+
helperText: `
|
|
27577
|
+
function __fw_getMockConfig() {
|
|
27578
|
+
return globalThis.__fw_mocks__;
|
|
27579
|
+
}
|
|
27580
|
+
|
|
27581
|
+
function __fw_lookupMock(section, key) {
|
|
27582
|
+
if (!section)
|
|
27583
|
+
return undefined;
|
|
27584
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27585
|
+
if (nodeId) {
|
|
27586
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27587
|
+
if (qualified !== undefined)
|
|
27588
|
+
return qualified;
|
|
27589
|
+
}
|
|
27590
|
+
return section[key];
|
|
27591
|
+
}
|
|
27592
|
+
`.trim(),
|
|
27593
|
+
helperTextProduction: void 0,
|
|
27594
|
+
functionText: `
|
|
27595
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
27596
|
+
if (!execute)
|
|
27597
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
27598
|
+
const mocks = __fw_getMockConfig();
|
|
27599
|
+
const mockResult = __fw_lookupMock(mocks?.agents, agentId);
|
|
27600
|
+
if (mockResult !== undefined) {
|
|
27601
|
+
return { onSuccess: true, onFailure: false, agentResult: mockResult };
|
|
27602
|
+
}
|
|
27603
|
+
if (mocks?.agents) {
|
|
27604
|
+
return { onSuccess: false, onFailure: true, agentResult: {} };
|
|
27605
|
+
}
|
|
27606
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
27607
|
+
if (channel) {
|
|
27608
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
27609
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
27610
|
+
}
|
|
27611
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
27612
|
+
}
|
|
27613
|
+
`.trim(),
|
|
27614
|
+
functionTextProduction: `
|
|
27615
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
27616
|
+
if (!execute)
|
|
27617
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
27618
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
27619
|
+
if (channel) {
|
|
27620
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
27621
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
27622
|
+
}
|
|
27623
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
27624
|
+
}
|
|
27625
|
+
`.trim()
|
|
27626
|
+
}
|
|
27627
|
+
];
|
|
27628
|
+
}
|
|
27629
|
+
});
|
|
27630
|
+
|
|
27263
27631
|
// src/parser/tag-registry.ts
|
|
27264
27632
|
var TagHandlerRegistry, tagHandlerRegistry;
|
|
27265
27633
|
var init_tag_registry = __esm({
|
|
@@ -27588,6 +27956,7 @@ var init_parser2 = __esm({
|
|
|
27588
27956
|
init_shared_project();
|
|
27589
27957
|
init_lru_cache();
|
|
27590
27958
|
init_coercion_types();
|
|
27959
|
+
init_generated_registry();
|
|
27591
27960
|
init_tag_registry();
|
|
27592
27961
|
AnnotationParser = class {
|
|
27593
27962
|
project;
|
|
@@ -27731,7 +28100,7 @@ var init_parser2 = __esm({
|
|
|
27731
28100
|
}
|
|
27732
28101
|
}
|
|
27733
28102
|
}
|
|
27734
|
-
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes);
|
|
28103
|
+
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
27735
28104
|
nodeTypes.push(...inferredNodeTypes);
|
|
27736
28105
|
const workflows = this.extractWorkflows(sourceFile, nodeTypes, filePath, errors2, warnings);
|
|
27737
28106
|
const patterns = this.extractPatterns(sourceFile, nodeTypes, filePath, errors2, warnings);
|
|
@@ -27771,7 +28140,7 @@ var init_parser2 = __esm({
|
|
|
27771
28140
|
const workflowSignatures = this.extractWorkflowSignatures(sourceFile, virtualPath, warnings);
|
|
27772
28141
|
const sameFileWorkflowNodeTypes = workflowSignatures.map((wf) => this.workflowToNodeType(wf));
|
|
27773
28142
|
const nodeTypes = [...localNodeTypes, ...sameFileWorkflowNodeTypes];
|
|
27774
|
-
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes);
|
|
28143
|
+
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
27775
28144
|
nodeTypes.push(...inferredNodeTypes);
|
|
27776
28145
|
const workflows = this.extractWorkflows(sourceFile, nodeTypes, virtualPath, errors2, warnings);
|
|
27777
28146
|
const patterns = this.extractPatterns(sourceFile, nodeTypes, virtualPath, errors2, warnings);
|
|
@@ -28003,7 +28372,7 @@ var init_parser2 = __esm({
|
|
|
28003
28372
|
if (imp.importSource.startsWith(".")) {
|
|
28004
28373
|
return this.resolveLocalImportAnnotation(imp, currentDir, warnings);
|
|
28005
28374
|
} else {
|
|
28006
|
-
return this.resolveNpmImportAnnotation(imp, currentDir);
|
|
28375
|
+
return this.resolveNpmImportAnnotation(imp, currentDir, warnings);
|
|
28007
28376
|
}
|
|
28008
28377
|
}
|
|
28009
28378
|
/**
|
|
@@ -28048,7 +28417,7 @@ var init_parser2 = __esm({
|
|
|
28048
28417
|
/**
|
|
28049
28418
|
* Resolve an npm package @fwImport to a node type by reading .d.ts declarations.
|
|
28050
28419
|
*/
|
|
28051
|
-
resolveNpmImportAnnotation(imp, currentDir) {
|
|
28420
|
+
resolveNpmImportAnnotation(imp, currentDir, warnings) {
|
|
28052
28421
|
const cacheKey = `npm:${imp.importSource}`;
|
|
28053
28422
|
if (this.importCache.has(cacheKey)) {
|
|
28054
28423
|
const cached2 = this.importCache.get(cacheKey);
|
|
@@ -28072,6 +28441,9 @@ var init_parser2 = __esm({
|
|
|
28072
28441
|
}
|
|
28073
28442
|
const dtsPath = resolvePackageTypesPath(imp.importSource, currentDir);
|
|
28074
28443
|
if (!dtsPath) {
|
|
28444
|
+
warnings.push(
|
|
28445
|
+
`@fwImport: Package "${imp.importSource}" has no type declarations (.d.ts). Install @types/${imp.importSource} or add a local wrapper with @flowWeaver nodeType annotations.`
|
|
28446
|
+
);
|
|
28075
28447
|
return this.createImportStub(imp);
|
|
28076
28448
|
}
|
|
28077
28449
|
try {
|
|
@@ -28101,7 +28473,13 @@ var init_parser2 = __esm({
|
|
|
28101
28473
|
if (found) {
|
|
28102
28474
|
return { ...found, name: imp.name, importSource: imp.importSource };
|
|
28103
28475
|
}
|
|
28104
|
-
|
|
28476
|
+
warnings.push(
|
|
28477
|
+
`@fwImport: Function "${imp.functionName}" not found in type declarations for "${imp.importSource}". Available exports: ${allNodeTypes.map((nt2) => nt2.functionName).join(", ") || "(none)"}.`
|
|
28478
|
+
);
|
|
28479
|
+
} catch (err) {
|
|
28480
|
+
warnings.push(
|
|
28481
|
+
`@fwImport: Failed to parse type declarations for "${imp.importSource}": ${err.message}. Node "${imp.name}" will use a generic stub.`
|
|
28482
|
+
);
|
|
28105
28483
|
}
|
|
28106
28484
|
return this.createImportStub(imp);
|
|
28107
28485
|
}
|
|
@@ -28400,6 +28778,18 @@ ${fn.getText()}` : fn.getText();
|
|
|
28400
28778
|
const importedNpmNodeTypes = (config2.imports || []).map(
|
|
28401
28779
|
(imp) => this.resolveImportAnnotation(imp, filePath, warnings)
|
|
28402
28780
|
);
|
|
28781
|
+
for (const nt2 of importedNpmNodeTypes) {
|
|
28782
|
+
const dataInputs = Object.keys(nt2.inputs).filter((p) => p !== "execute");
|
|
28783
|
+
const nonControlOutputs = Object.keys(nt2.outputs).filter(
|
|
28784
|
+
(p) => p !== "onSuccess" && p !== "onFailure"
|
|
28785
|
+
);
|
|
28786
|
+
const isStubOnly = nonControlOutputs.length === 1 && nonControlOutputs[0] === "result" && nt2.outputs.result?.dataType === "ANY";
|
|
28787
|
+
if (dataInputs.length === 0 && isStubOnly) {
|
|
28788
|
+
warnings.push(
|
|
28789
|
+
`Could not infer ports for "${nt2.functionName}" from "${nt2.importSource}". Wrap it in a local function with @flowWeaver nodeType annotations instead.`
|
|
28790
|
+
);
|
|
28791
|
+
}
|
|
28792
|
+
}
|
|
28403
28793
|
const allAvailableNodeTypes = [...availableNodeTypes, ...importedNpmNodeTypes];
|
|
28404
28794
|
const instances = (config2.instances || []).map((inst) => {
|
|
28405
28795
|
const nodeTypeExists = allAvailableNodeTypes.some(
|
|
@@ -28750,7 +29140,7 @@ ${fn.getText()}` : fn.getText();
|
|
|
28750
29140
|
* nodeType annotation, we infer an expression node type from its TypeScript
|
|
28751
29141
|
* signature. Phase 1: same-file functions only.
|
|
28752
29142
|
*/
|
|
28753
|
-
inferNodeTypesFromUnannotated(sourceFile, existingNodeTypes) {
|
|
29143
|
+
inferNodeTypesFromUnannotated(sourceFile, existingNodeTypes, localNodeTypes, warnings) {
|
|
28754
29144
|
const allFunctions = extractFunctionLikes(sourceFile);
|
|
28755
29145
|
const referencedTypes = /* @__PURE__ */ new Set();
|
|
28756
29146
|
for (const fn of allFunctions) {
|
|
@@ -28774,8 +29164,24 @@ ${fn.getText()}` : fn.getText();
|
|
|
28774
29164
|
if (unresolvedTypes.size === 0) return [];
|
|
28775
29165
|
const inferredNodeTypes = [];
|
|
28776
29166
|
const alreadyInferred = /* @__PURE__ */ new Set();
|
|
29167
|
+
const builtInByName = new Map(BUILT_IN_NODE_TYPES.map((nt2) => [nt2.name, nt2]));
|
|
29168
|
+
const annotatedNames = new Set(localNodeTypes.map((nt2) => nt2.functionName));
|
|
28777
29169
|
for (const unresolvedType of unresolvedTypes) {
|
|
28778
29170
|
if (alreadyInferred.has(unresolvedType)) continue;
|
|
29171
|
+
const builtIn = builtInByName.get(unresolvedType);
|
|
29172
|
+
if (builtIn) {
|
|
29173
|
+
inferredNodeTypes.push(builtIn);
|
|
29174
|
+
alreadyInferred.add(unresolvedType);
|
|
29175
|
+
if (!annotatedNames.has(unresolvedType)) {
|
|
29176
|
+
const shadowFn = allFunctions.find((fn) => fn.getName() === unresolvedType && !this.hasFlowWeaverAnnotation(fn));
|
|
29177
|
+
if (shadowFn) {
|
|
29178
|
+
warnings.push(
|
|
29179
|
+
`Function '${unresolvedType}' exists in this file but is not annotated with @flowWeaver nodeType. The built-in '${unresolvedType}' will be used instead. Add @flowWeaver nodeType to use your version.`
|
|
29180
|
+
);
|
|
29181
|
+
}
|
|
29182
|
+
}
|
|
29183
|
+
continue;
|
|
29184
|
+
}
|
|
28779
29185
|
const matchedFn = allFunctions.find((fn) => {
|
|
28780
29186
|
if (fn.getName() !== unresolvedType) return false;
|
|
28781
29187
|
return !this.hasFlowWeaverAnnotation(fn);
|
|
@@ -29674,6 +30080,7 @@ async function parseWorkflow(filePath, options) {
|
|
|
29674
30080
|
}
|
|
29675
30081
|
const parsed = parser.parse(filePath);
|
|
29676
30082
|
warnings.push(...parsed.warnings);
|
|
30083
|
+
errors2.push(...parsed.errors);
|
|
29677
30084
|
availableWorkflows = parsed.workflows.map((w) => w.functionName);
|
|
29678
30085
|
let workflowName = options?.workflowName;
|
|
29679
30086
|
if (!workflowName) {
|
|
@@ -29723,7 +30130,7 @@ async function parseWorkflow(filePath, options) {
|
|
|
29723
30130
|
}
|
|
29724
30131
|
return {
|
|
29725
30132
|
ast: workflow,
|
|
29726
|
-
errors:
|
|
30133
|
+
errors: errors2,
|
|
29727
30134
|
warnings,
|
|
29728
30135
|
availableWorkflows,
|
|
29729
30136
|
allWorkflows: parsed.workflows
|
|
@@ -88526,7 +88933,7 @@ function parseIntStrict(value) {
|
|
|
88526
88933
|
// src/cli/index.ts
|
|
88527
88934
|
init_logger();
|
|
88528
88935
|
init_error_utils();
|
|
88529
|
-
var version2 = true ? "0.
|
|
88936
|
+
var version2 = true ? "0.29.1" : "0.0.0-dev";
|
|
88530
88937
|
var program2 = new Command();
|
|
88531
88938
|
program2.name("fw").description("Flow Weaver Annotations - Compile and validate workflow files").option("-v, --version", "Output the current version").option("--no-color", "Disable colors").option("--color", "Force colors").on("option:version", () => {
|
|
88532
88939
|
logger.banner(version2);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.29.1";
|
|
2
2
|
//# sourceMappingURL=generated-version.d.ts.map
|
|
@@ -196,8 +196,9 @@ export function generateScopeFunctionClosure(scopeName, parentNodeId, parentNode
|
|
|
196
196
|
// When enabled, wraps the child execution so breakpoints can pause on scoped nodes.
|
|
197
197
|
if (emitDebugHooks) {
|
|
198
198
|
const awaitHook = isAsync ? 'await ' : '';
|
|
199
|
-
// Hoist Idx declaration before the if block so it stays in scope after
|
|
200
|
-
|
|
199
|
+
// Hoist Idx declaration before the if block so it stays in scope after.
|
|
200
|
+
// Initialize to -1 so getVariable can still read checkpointed values when beforeNode skips.
|
|
201
|
+
lines.push(` let ${safeChildId}Idx: number = -1;`);
|
|
201
202
|
lines.push(` if (${awaitHook}__ctrl__.beforeNode('${child.id}', scopedCtx)) {`);
|
|
202
203
|
childIndent = ' ';
|
|
203
204
|
}
|
package/dist/parser.js
CHANGED
|
@@ -14,6 +14,7 @@ import { getPackageExports } from './npm-packages.js';
|
|
|
14
14
|
import { getSharedProject } from './shared-project.js';
|
|
15
15
|
import { LRUCache } from './utils/lru-cache.js';
|
|
16
16
|
import { COERCION_NODE_TYPES, COERCE_TYPE_MAP } from './built-in-nodes/coercion-types.js';
|
|
17
|
+
import { BUILT_IN_NODE_TYPES } from './built-in-nodes/generated-registry.js';
|
|
17
18
|
import { tagHandlerRegistry } from './parser/tag-registry.js';
|
|
18
19
|
/**
|
|
19
20
|
* Convert a TExternalNodeType to a TNodeTypeAST with sensible defaults.
|
|
@@ -233,8 +234,9 @@ export class AnnotationParser {
|
|
|
233
234
|
}
|
|
234
235
|
}
|
|
235
236
|
}
|
|
236
|
-
// Auto-infer node types from unannotated functions referenced by @node
|
|
237
|
-
|
|
237
|
+
// Auto-infer node types from unannotated functions referenced by @node,
|
|
238
|
+
// and lazily inject built-in nodes (delay, waitForEvent, etc.) when referenced
|
|
239
|
+
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
238
240
|
nodeTypes.push(...inferredNodeTypes);
|
|
239
241
|
const workflows = this.extractWorkflows(sourceFile, nodeTypes, filePath, errors, warnings);
|
|
240
242
|
const patterns = this.extractPatterns(sourceFile, nodeTypes, filePath, errors, warnings);
|
|
@@ -282,8 +284,9 @@ export class AnnotationParser {
|
|
|
282
284
|
const workflowSignatures = this.extractWorkflowSignatures(sourceFile, virtualPath, warnings);
|
|
283
285
|
const sameFileWorkflowNodeTypes = workflowSignatures.map((wf) => this.workflowToNodeType(wf));
|
|
284
286
|
const nodeTypes = [...localNodeTypes, ...sameFileWorkflowNodeTypes];
|
|
285
|
-
// Auto-infer node types from unannotated functions referenced by @node
|
|
286
|
-
|
|
287
|
+
// Auto-infer node types from unannotated functions referenced by @node,
|
|
288
|
+
// and lazily inject built-in nodes (delay, waitForEvent, etc.) when referenced
|
|
289
|
+
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
287
290
|
nodeTypes.push(...inferredNodeTypes);
|
|
288
291
|
// Note: imports not supported for virtual files - would need filesystem access
|
|
289
292
|
const workflows = this.extractWorkflows(sourceFile, nodeTypes, virtualPath, errors, warnings);
|
|
@@ -555,7 +558,7 @@ export class AnnotationParser {
|
|
|
555
558
|
}
|
|
556
559
|
else {
|
|
557
560
|
// npm package import - use .d.ts inference
|
|
558
|
-
return this.resolveNpmImportAnnotation(imp, currentDir);
|
|
561
|
+
return this.resolveNpmImportAnnotation(imp, currentDir, warnings);
|
|
559
562
|
}
|
|
560
563
|
}
|
|
561
564
|
/**
|
|
@@ -609,7 +612,7 @@ export class AnnotationParser {
|
|
|
609
612
|
/**
|
|
610
613
|
* Resolve an npm package @fwImport to a node type by reading .d.ts declarations.
|
|
611
614
|
*/
|
|
612
|
-
resolveNpmImportAnnotation(imp, currentDir) {
|
|
615
|
+
resolveNpmImportAnnotation(imp, currentDir, warnings) {
|
|
613
616
|
// Check cache (with mtime validation — same pattern as resolveNpmImports)
|
|
614
617
|
const cacheKey = `npm:${imp.importSource}`;
|
|
615
618
|
if (this.importCache.has(cacheKey)) {
|
|
@@ -637,6 +640,8 @@ export class AnnotationParser {
|
|
|
637
640
|
// Resolve .d.ts path
|
|
638
641
|
const dtsPath = resolvePackageTypesPath(imp.importSource, currentDir);
|
|
639
642
|
if (!dtsPath) {
|
|
643
|
+
warnings.push(`@fwImport: Package "${imp.importSource}" has no type declarations (.d.ts). ` +
|
|
644
|
+
`Install @types/${imp.importSource} or add a local wrapper with @flowWeaver nodeType annotations.`);
|
|
640
645
|
return this.createImportStub(imp);
|
|
641
646
|
}
|
|
642
647
|
try {
|
|
@@ -667,9 +672,12 @@ export class AnnotationParser {
|
|
|
667
672
|
if (found) {
|
|
668
673
|
return { ...found, name: imp.name, importSource: imp.importSource };
|
|
669
674
|
}
|
|
675
|
+
// Function not found in .d.ts
|
|
676
|
+
warnings.push(`@fwImport: Function "${imp.functionName}" not found in type declarations for "${imp.importSource}". ` +
|
|
677
|
+
`Available exports: ${allNodeTypes.map((nt) => nt.functionName).join(', ') || '(none)'}.`);
|
|
670
678
|
}
|
|
671
|
-
catch {
|
|
672
|
-
|
|
679
|
+
catch (err) {
|
|
680
|
+
warnings.push(`@fwImport: Failed to parse type declarations for "${imp.importSource}": ${err.message}. Node "${imp.name}" will use a generic stub.`);
|
|
673
681
|
}
|
|
674
682
|
return this.createImportStub(imp);
|
|
675
683
|
}
|
|
@@ -982,6 +990,22 @@ export class AnnotationParser {
|
|
|
982
990
|
// These are persisted in JSDoc so they survive file re-parsing
|
|
983
991
|
// Uses the same inference logic as TS imports for consistency
|
|
984
992
|
const importedNpmNodeTypes = (config.imports || []).map((imp) => this.resolveImportAnnotation(imp, filePath, warnings));
|
|
993
|
+
// Post-resolution check: warn when inferred node type has zero data ports
|
|
994
|
+
// (excluding control-flow ports). This usually means the .d.ts inference
|
|
995
|
+
// couldn't extract meaningful port info and a local wrapper is needed.
|
|
996
|
+
for (const nt of importedNpmNodeTypes) {
|
|
997
|
+
const dataInputs = Object.keys(nt.inputs).filter((p) => p !== 'execute');
|
|
998
|
+
const nonControlOutputs = Object.keys(nt.outputs).filter((p) => p !== 'onSuccess' && p !== 'onFailure');
|
|
999
|
+
// Stub fallback has only result: ANY — if that's all we have with zero
|
|
1000
|
+
// data inputs, inference likely failed
|
|
1001
|
+
const isStubOnly = nonControlOutputs.length === 1 &&
|
|
1002
|
+
nonControlOutputs[0] === 'result' &&
|
|
1003
|
+
nt.outputs.result?.dataType === 'ANY';
|
|
1004
|
+
if (dataInputs.length === 0 && isStubOnly) {
|
|
1005
|
+
warnings.push(`Could not infer ports for "${nt.functionName}" from "${nt.importSource}". ` +
|
|
1006
|
+
`Wrap it in a local function with @flowWeaver nodeType annotations instead.`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
985
1009
|
// Combine available node types with imported npm types for validation
|
|
986
1010
|
const allAvailableNodeTypes = [...availableNodeTypes, ...importedNpmNodeTypes];
|
|
987
1011
|
// Convert instances to NodeInstanceAST
|
|
@@ -1350,7 +1374,7 @@ export class AnnotationParser {
|
|
|
1350
1374
|
* nodeType annotation, we infer an expression node type from its TypeScript
|
|
1351
1375
|
* signature. Phase 1: same-file functions only.
|
|
1352
1376
|
*/
|
|
1353
|
-
inferNodeTypesFromUnannotated(sourceFile, existingNodeTypes) {
|
|
1377
|
+
inferNodeTypesFromUnannotated(sourceFile, existingNodeTypes, localNodeTypes, warnings) {
|
|
1354
1378
|
const allFunctions = extractFunctionLikes(sourceFile);
|
|
1355
1379
|
// 1. Pre-scan workflows for @node references to collect referenced type names
|
|
1356
1380
|
const referencedTypes = new Set();
|
|
@@ -1376,17 +1400,33 @@ export class AnnotationParser {
|
|
|
1376
1400
|
}
|
|
1377
1401
|
if (unresolvedTypes.size === 0)
|
|
1378
1402
|
return [];
|
|
1379
|
-
// 3. Match unresolved types to unannotated functions in
|
|
1403
|
+
// 3. Match unresolved types to unannotated functions OR built-in nodes
|
|
1380
1404
|
const inferredNodeTypes = [];
|
|
1381
1405
|
const alreadyInferred = new Set();
|
|
1406
|
+
const builtInByName = new Map(BUILT_IN_NODE_TYPES.map((nt) => [nt.name, nt]));
|
|
1407
|
+
const annotatedNames = new Set(localNodeTypes.map((nt) => nt.functionName));
|
|
1382
1408
|
for (const unresolvedType of unresolvedTypes) {
|
|
1383
1409
|
if (alreadyInferred.has(unresolvedType))
|
|
1384
1410
|
continue;
|
|
1385
|
-
//
|
|
1411
|
+
// 3a. Check built-in nodes first
|
|
1412
|
+
const builtIn = builtInByName.get(unresolvedType);
|
|
1413
|
+
if (builtIn) {
|
|
1414
|
+
inferredNodeTypes.push(builtIn);
|
|
1415
|
+
alreadyInferred.add(unresolvedType);
|
|
1416
|
+
// Warn if a local unannotated function has the same name (it will be shadowed)
|
|
1417
|
+
if (!annotatedNames.has(unresolvedType)) {
|
|
1418
|
+
const shadowFn = allFunctions.find((fn) => fn.getName() === unresolvedType && !this.hasFlowWeaverAnnotation(fn));
|
|
1419
|
+
if (shadowFn) {
|
|
1420
|
+
warnings.push(`Function '${unresolvedType}' exists in this file but is not annotated with @flowWeaver nodeType. ` +
|
|
1421
|
+
`The built-in '${unresolvedType}' will be used instead. Add @flowWeaver nodeType to use your version.`);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
continue;
|
|
1425
|
+
}
|
|
1426
|
+
// 3b. Match unannotated local function
|
|
1386
1427
|
const matchedFn = allFunctions.find((fn) => {
|
|
1387
1428
|
if (fn.getName() !== unresolvedType)
|
|
1388
1429
|
return false;
|
|
1389
|
-
// Must NOT have a valid @flowWeaver annotation
|
|
1390
1430
|
return !this.hasFlowWeaverAnnotation(fn);
|
|
1391
1431
|
});
|
|
1392
1432
|
if (!matchedFn)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synergenius/flow-weaver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "Flow Weaver: deterministic TypeScript workflow compiler. Define workflows with JSDoc annotations, compile to standalone functions with zero runtime dependencies.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -113,7 +113,9 @@
|
|
|
113
113
|
"LICENSE"
|
|
114
114
|
],
|
|
115
115
|
"scripts": {
|
|
116
|
-
"prebuild": "tsx scripts/generate-version.ts",
|
|
116
|
+
"prebuild": "tsx scripts/generate-version.ts && tsx scripts/generate-built-in-registry.ts",
|
|
117
|
+
"generate:registry": "tsx scripts/generate-built-in-registry.ts",
|
|
118
|
+
"generate:registry:check": "tsx scripts/generate-built-in-registry.ts --check",
|
|
117
119
|
"build": "rimraf dist .tsbuildinfo && tsc -p tsconfig.build.json && npm run build:cli",
|
|
118
120
|
"postbuild": "npx tsx scripts/postbuild.ts && npm run generate:docs",
|
|
119
121
|
"generate:docs": "tsx scripts/generate-docs.ts",
|