@synergenius/flow-weaver 0.28.0 → 0.29.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/dist/api/generate-in-place.js +54 -0
- package/dist/api/generate.js +20 -2
- 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/cli/flow-weaver.mjs +412 -11
- 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/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
|
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.0";
|
|
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;
|
|
@@ -27260,6 +27323,306 @@ var init_coercion_types = __esm({
|
|
|
27260
27323
|
}
|
|
27261
27324
|
});
|
|
27262
27325
|
|
|
27326
|
+
// src/built-in-nodes/generated-registry.ts
|
|
27327
|
+
var BUILT_IN_NODE_TYPES;
|
|
27328
|
+
var init_generated_registry = __esm({
|
|
27329
|
+
"src/built-in-nodes/generated-registry.ts"() {
|
|
27330
|
+
"use strict";
|
|
27331
|
+
BUILT_IN_NODE_TYPES = [
|
|
27332
|
+
{
|
|
27333
|
+
type: "NodeType",
|
|
27334
|
+
name: "delay",
|
|
27335
|
+
functionName: "delay",
|
|
27336
|
+
isAsync: true,
|
|
27337
|
+
hasSuccessPort: true,
|
|
27338
|
+
hasFailurePort: true,
|
|
27339
|
+
executeWhen: "CONJUNCTION",
|
|
27340
|
+
variant: "FUNCTION",
|
|
27341
|
+
inputs: {
|
|
27342
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27343
|
+
duration: { dataType: "STRING", label: 'Duration to sleep (e.g. "30s", "5m", "1h", "2d")', tsType: "string" }
|
|
27344
|
+
},
|
|
27345
|
+
outputs: {
|
|
27346
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27347
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27348
|
+
elapsed: { dataType: "BOOLEAN", label: "Always true after sleep completes", tsType: "boolean" }
|
|
27349
|
+
},
|
|
27350
|
+
helperText: `
|
|
27351
|
+
function __fw_getMockConfig() {
|
|
27352
|
+
return globalThis.__fw_mocks__;
|
|
27353
|
+
}
|
|
27354
|
+
|
|
27355
|
+
function __fw_lookupMock(section, key) {
|
|
27356
|
+
if (!section)
|
|
27357
|
+
return undefined;
|
|
27358
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27359
|
+
if (nodeId) {
|
|
27360
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27361
|
+
if (qualified !== undefined)
|
|
27362
|
+
return qualified;
|
|
27363
|
+
}
|
|
27364
|
+
return section[key];
|
|
27365
|
+
}
|
|
27366
|
+
`.trim(),
|
|
27367
|
+
helperTextProduction: void 0,
|
|
27368
|
+
functionText: `
|
|
27369
|
+
async function delay(execute, duration) {
|
|
27370
|
+
if (!execute)
|
|
27371
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
27372
|
+
const mocks = __fw_getMockConfig();
|
|
27373
|
+
if (mocks?.fast) {
|
|
27374
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
27375
|
+
}
|
|
27376
|
+
else {
|
|
27377
|
+
const ms = __fw_parseDuration(duration);
|
|
27378
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
27379
|
+
}
|
|
27380
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
27381
|
+
}
|
|
27382
|
+
function __fw_parseDuration(duration) {
|
|
27383
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
27384
|
+
if (!match)
|
|
27385
|
+
return 0;
|
|
27386
|
+
const [, value, unit] = match;
|
|
27387
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
27388
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
27389
|
+
}
|
|
27390
|
+
`.trim(),
|
|
27391
|
+
functionTextProduction: `
|
|
27392
|
+
async function delay(execute, duration) {
|
|
27393
|
+
if (!execute)
|
|
27394
|
+
return { onSuccess: false, onFailure: false, elapsed: false };
|
|
27395
|
+
const ms = __fw_parseDuration(duration);
|
|
27396
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
27397
|
+
return { onSuccess: true, onFailure: false, elapsed: true };
|
|
27398
|
+
}
|
|
27399
|
+
function __fw_parseDuration(duration) {
|
|
27400
|
+
const match = duration.match(/^(\\d+)(ms|s|m|h|d)$/);
|
|
27401
|
+
if (!match)
|
|
27402
|
+
return 0;
|
|
27403
|
+
const [, value, unit] = match;
|
|
27404
|
+
const multipliers = { ms: 1, s: 1000, m: 60000, h: 3600000, d: 86400000 };
|
|
27405
|
+
return parseInt(value) * (multipliers[unit] || 0);
|
|
27406
|
+
}
|
|
27407
|
+
`.trim()
|
|
27408
|
+
},
|
|
27409
|
+
{
|
|
27410
|
+
type: "NodeType",
|
|
27411
|
+
name: "waitForEvent",
|
|
27412
|
+
functionName: "waitForEvent",
|
|
27413
|
+
isAsync: true,
|
|
27414
|
+
hasSuccessPort: true,
|
|
27415
|
+
hasFailurePort: true,
|
|
27416
|
+
executeWhen: "CONJUNCTION",
|
|
27417
|
+
variant: "FUNCTION",
|
|
27418
|
+
inputs: {
|
|
27419
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27420
|
+
eventName: { dataType: "STRING", label: 'Event name to wait for (e.g. "app/approval.received")', tsType: "string" },
|
|
27421
|
+
match: { dataType: "STRING", label: 'Field to match between trigger and waited event (e.g. "data.requestId")', tsType: "string", optional: true },
|
|
27422
|
+
timeout: { dataType: "STRING", label: 'Max wait time (e.g. "24h", "7d"). Empty = no timeout', tsType: "string", optional: true }
|
|
27423
|
+
},
|
|
27424
|
+
outputs: {
|
|
27425
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27426
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27427
|
+
eventData: { dataType: "OBJECT", label: "The received event's data payload", tsType: "object" }
|
|
27428
|
+
},
|
|
27429
|
+
helperText: `
|
|
27430
|
+
function __fw_getMockConfig() {
|
|
27431
|
+
return globalThis.__fw_mocks__;
|
|
27432
|
+
}
|
|
27433
|
+
|
|
27434
|
+
function __fw_lookupMock(section, key) {
|
|
27435
|
+
if (!section)
|
|
27436
|
+
return undefined;
|
|
27437
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27438
|
+
if (nodeId) {
|
|
27439
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27440
|
+
if (qualified !== undefined)
|
|
27441
|
+
return qualified;
|
|
27442
|
+
}
|
|
27443
|
+
return section[key];
|
|
27444
|
+
}
|
|
27445
|
+
`.trim(),
|
|
27446
|
+
helperTextProduction: void 0,
|
|
27447
|
+
functionText: `
|
|
27448
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
27449
|
+
if (!execute)
|
|
27450
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
27451
|
+
const mocks = __fw_getMockConfig();
|
|
27452
|
+
if (mocks) {
|
|
27453
|
+
const mockData = __fw_lookupMock(mocks.events, eventName);
|
|
27454
|
+
if (mockData !== undefined) {
|
|
27455
|
+
return { onSuccess: true, onFailure: false, eventData: mockData };
|
|
27456
|
+
}
|
|
27457
|
+
return { onSuccess: false, onFailure: true, eventData: {} };
|
|
27458
|
+
}
|
|
27459
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
27460
|
+
}
|
|
27461
|
+
`.trim(),
|
|
27462
|
+
functionTextProduction: `
|
|
27463
|
+
async function waitForEvent(execute, eventName, match, timeout) {
|
|
27464
|
+
if (!execute)
|
|
27465
|
+
return { onSuccess: false, onFailure: false, eventData: {} };
|
|
27466
|
+
return { onSuccess: true, onFailure: false, eventData: {} };
|
|
27467
|
+
}
|
|
27468
|
+
`.trim()
|
|
27469
|
+
},
|
|
27470
|
+
{
|
|
27471
|
+
type: "NodeType",
|
|
27472
|
+
name: "invokeWorkflow",
|
|
27473
|
+
functionName: "invokeWorkflow",
|
|
27474
|
+
isAsync: true,
|
|
27475
|
+
hasSuccessPort: true,
|
|
27476
|
+
hasFailurePort: true,
|
|
27477
|
+
executeWhen: "CONJUNCTION",
|
|
27478
|
+
variant: "FUNCTION",
|
|
27479
|
+
inputs: {
|
|
27480
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27481
|
+
functionId: { dataType: "STRING", label: 'Function ID of the workflow to invoke (e.g. "my-service/sub-workflow")', tsType: "string" },
|
|
27482
|
+
payload: { dataType: "OBJECT", label: "Data to pass as event.data to the invoked function", tsType: "object" },
|
|
27483
|
+
timeout: { dataType: "STRING", label: 'Max wait time (e.g. "1h")', tsType: "string", optional: true }
|
|
27484
|
+
},
|
|
27485
|
+
outputs: {
|
|
27486
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27487
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27488
|
+
result: { dataType: "OBJECT", label: "Return value from the invoked function", tsType: "object" }
|
|
27489
|
+
},
|
|
27490
|
+
helperText: `
|
|
27491
|
+
function __fw_getMockConfig() {
|
|
27492
|
+
return globalThis.__fw_mocks__;
|
|
27493
|
+
}
|
|
27494
|
+
|
|
27495
|
+
function __fw_lookupMock(section, key) {
|
|
27496
|
+
if (!section)
|
|
27497
|
+
return undefined;
|
|
27498
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27499
|
+
if (nodeId) {
|
|
27500
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27501
|
+
if (qualified !== undefined)
|
|
27502
|
+
return qualified;
|
|
27503
|
+
}
|
|
27504
|
+
return section[key];
|
|
27505
|
+
}
|
|
27506
|
+
`.trim(),
|
|
27507
|
+
helperTextProduction: void 0,
|
|
27508
|
+
functionText: `
|
|
27509
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
27510
|
+
if (!execute)
|
|
27511
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
27512
|
+
const mocks = __fw_getMockConfig();
|
|
27513
|
+
if (mocks) {
|
|
27514
|
+
const mockResult = __fw_lookupMock(mocks.invocations, functionId);
|
|
27515
|
+
if (mockResult !== undefined) {
|
|
27516
|
+
return { onSuccess: true, onFailure: false, result: mockResult };
|
|
27517
|
+
}
|
|
27518
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
27519
|
+
}
|
|
27520
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
27521
|
+
if (registry?.[functionId]) {
|
|
27522
|
+
try {
|
|
27523
|
+
const result = await registry[functionId](true, payload);
|
|
27524
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
27525
|
+
}
|
|
27526
|
+
catch {
|
|
27527
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
27528
|
+
}
|
|
27529
|
+
}
|
|
27530
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
27531
|
+
}
|
|
27532
|
+
`.trim(),
|
|
27533
|
+
functionTextProduction: `
|
|
27534
|
+
async function invokeWorkflow(execute, functionId, payload, timeout) {
|
|
27535
|
+
if (!execute)
|
|
27536
|
+
return { onSuccess: false, onFailure: false, result: {} };
|
|
27537
|
+
const registry = globalThis.__fw_workflow_registry__;
|
|
27538
|
+
if (registry?.[functionId]) {
|
|
27539
|
+
try {
|
|
27540
|
+
const result = await registry[functionId](true, payload);
|
|
27541
|
+
return { onSuccess: true, onFailure: false, result: result ?? {} };
|
|
27542
|
+
}
|
|
27543
|
+
catch {
|
|
27544
|
+
return { onSuccess: false, onFailure: true, result: {} };
|
|
27545
|
+
}
|
|
27546
|
+
}
|
|
27547
|
+
return { onSuccess: true, onFailure: false, result: {} };
|
|
27548
|
+
}
|
|
27549
|
+
`.trim()
|
|
27550
|
+
},
|
|
27551
|
+
{
|
|
27552
|
+
type: "NodeType",
|
|
27553
|
+
name: "waitForAgent",
|
|
27554
|
+
functionName: "waitForAgent",
|
|
27555
|
+
isAsync: true,
|
|
27556
|
+
hasSuccessPort: true,
|
|
27557
|
+
hasFailurePort: true,
|
|
27558
|
+
executeWhen: "CONJUNCTION",
|
|
27559
|
+
variant: "FUNCTION",
|
|
27560
|
+
inputs: {
|
|
27561
|
+
execute: { dataType: "STEP", label: "Execute" },
|
|
27562
|
+
agentId: { dataType: "STRING", label: "Agent/task identifier", tsType: "string" },
|
|
27563
|
+
context: { dataType: "OBJECT", label: "Context data to send to the agent", tsType: "object" },
|
|
27564
|
+
prompt: { dataType: "STRING", label: "Message to display when requesting input", tsType: "string", optional: true }
|
|
27565
|
+
},
|
|
27566
|
+
outputs: {
|
|
27567
|
+
onSuccess: { dataType: "STEP", label: "On Success", isControlFlow: true },
|
|
27568
|
+
onFailure: { dataType: "STEP", label: "On Failure", isControlFlow: true, failure: true },
|
|
27569
|
+
agentResult: { dataType: "OBJECT", label: "Result returned by the agent", tsType: "object" }
|
|
27570
|
+
},
|
|
27571
|
+
helperText: `
|
|
27572
|
+
function __fw_getMockConfig() {
|
|
27573
|
+
return globalThis.__fw_mocks__;
|
|
27574
|
+
}
|
|
27575
|
+
|
|
27576
|
+
function __fw_lookupMock(section, key) {
|
|
27577
|
+
if (!section)
|
|
27578
|
+
return undefined;
|
|
27579
|
+
const nodeId = globalThis.__fw_current_node_id__;
|
|
27580
|
+
if (nodeId) {
|
|
27581
|
+
const qualified = section[\`\${nodeId}:\${key}\`];
|
|
27582
|
+
if (qualified !== undefined)
|
|
27583
|
+
return qualified;
|
|
27584
|
+
}
|
|
27585
|
+
return section[key];
|
|
27586
|
+
}
|
|
27587
|
+
`.trim(),
|
|
27588
|
+
helperTextProduction: void 0,
|
|
27589
|
+
functionText: `
|
|
27590
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
27591
|
+
if (!execute)
|
|
27592
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
27593
|
+
const mocks = __fw_getMockConfig();
|
|
27594
|
+
const mockResult = __fw_lookupMock(mocks?.agents, agentId);
|
|
27595
|
+
if (mockResult !== undefined) {
|
|
27596
|
+
return { onSuccess: true, onFailure: false, agentResult: mockResult };
|
|
27597
|
+
}
|
|
27598
|
+
if (mocks?.agents) {
|
|
27599
|
+
return { onSuccess: false, onFailure: true, agentResult: {} };
|
|
27600
|
+
}
|
|
27601
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
27602
|
+
if (channel) {
|
|
27603
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
27604
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
27605
|
+
}
|
|
27606
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
27607
|
+
}
|
|
27608
|
+
`.trim(),
|
|
27609
|
+
functionTextProduction: `
|
|
27610
|
+
async function waitForAgent(execute, agentId, context, prompt) {
|
|
27611
|
+
if (!execute)
|
|
27612
|
+
return { onSuccess: false, onFailure: false, agentResult: {} };
|
|
27613
|
+
const channel = globalThis.__fw_agent_channel__;
|
|
27614
|
+
if (channel) {
|
|
27615
|
+
const result = await channel.request({ agentId, context, prompt });
|
|
27616
|
+
return { onSuccess: true, onFailure: false, agentResult: result };
|
|
27617
|
+
}
|
|
27618
|
+
return { onSuccess: true, onFailure: false, agentResult: {} };
|
|
27619
|
+
}
|
|
27620
|
+
`.trim()
|
|
27621
|
+
}
|
|
27622
|
+
];
|
|
27623
|
+
}
|
|
27624
|
+
});
|
|
27625
|
+
|
|
27263
27626
|
// src/parser/tag-registry.ts
|
|
27264
27627
|
var TagHandlerRegistry, tagHandlerRegistry;
|
|
27265
27628
|
var init_tag_registry = __esm({
|
|
@@ -27588,6 +27951,7 @@ var init_parser2 = __esm({
|
|
|
27588
27951
|
init_shared_project();
|
|
27589
27952
|
init_lru_cache();
|
|
27590
27953
|
init_coercion_types();
|
|
27954
|
+
init_generated_registry();
|
|
27591
27955
|
init_tag_registry();
|
|
27592
27956
|
AnnotationParser = class {
|
|
27593
27957
|
project;
|
|
@@ -27731,7 +28095,7 @@ var init_parser2 = __esm({
|
|
|
27731
28095
|
}
|
|
27732
28096
|
}
|
|
27733
28097
|
}
|
|
27734
|
-
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes);
|
|
28098
|
+
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
27735
28099
|
nodeTypes.push(...inferredNodeTypes);
|
|
27736
28100
|
const workflows = this.extractWorkflows(sourceFile, nodeTypes, filePath, errors2, warnings);
|
|
27737
28101
|
const patterns = this.extractPatterns(sourceFile, nodeTypes, filePath, errors2, warnings);
|
|
@@ -27771,7 +28135,7 @@ var init_parser2 = __esm({
|
|
|
27771
28135
|
const workflowSignatures = this.extractWorkflowSignatures(sourceFile, virtualPath, warnings);
|
|
27772
28136
|
const sameFileWorkflowNodeTypes = workflowSignatures.map((wf) => this.workflowToNodeType(wf));
|
|
27773
28137
|
const nodeTypes = [...localNodeTypes, ...sameFileWorkflowNodeTypes];
|
|
27774
|
-
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes);
|
|
28138
|
+
const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes, localNodeTypes, warnings);
|
|
27775
28139
|
nodeTypes.push(...inferredNodeTypes);
|
|
27776
28140
|
const workflows = this.extractWorkflows(sourceFile, nodeTypes, virtualPath, errors2, warnings);
|
|
27777
28141
|
const patterns = this.extractPatterns(sourceFile, nodeTypes, virtualPath, errors2, warnings);
|
|
@@ -28003,7 +28367,7 @@ var init_parser2 = __esm({
|
|
|
28003
28367
|
if (imp.importSource.startsWith(".")) {
|
|
28004
28368
|
return this.resolveLocalImportAnnotation(imp, currentDir, warnings);
|
|
28005
28369
|
} else {
|
|
28006
|
-
return this.resolveNpmImportAnnotation(imp, currentDir);
|
|
28370
|
+
return this.resolveNpmImportAnnotation(imp, currentDir, warnings);
|
|
28007
28371
|
}
|
|
28008
28372
|
}
|
|
28009
28373
|
/**
|
|
@@ -28048,7 +28412,7 @@ var init_parser2 = __esm({
|
|
|
28048
28412
|
/**
|
|
28049
28413
|
* Resolve an npm package @fwImport to a node type by reading .d.ts declarations.
|
|
28050
28414
|
*/
|
|
28051
|
-
resolveNpmImportAnnotation(imp, currentDir) {
|
|
28415
|
+
resolveNpmImportAnnotation(imp, currentDir, warnings) {
|
|
28052
28416
|
const cacheKey = `npm:${imp.importSource}`;
|
|
28053
28417
|
if (this.importCache.has(cacheKey)) {
|
|
28054
28418
|
const cached2 = this.importCache.get(cacheKey);
|
|
@@ -28072,6 +28436,9 @@ var init_parser2 = __esm({
|
|
|
28072
28436
|
}
|
|
28073
28437
|
const dtsPath = resolvePackageTypesPath(imp.importSource, currentDir);
|
|
28074
28438
|
if (!dtsPath) {
|
|
28439
|
+
warnings.push(
|
|
28440
|
+
`@fwImport: Package "${imp.importSource}" has no type declarations (.d.ts). Install @types/${imp.importSource} or add a local wrapper with @flowWeaver nodeType annotations.`
|
|
28441
|
+
);
|
|
28075
28442
|
return this.createImportStub(imp);
|
|
28076
28443
|
}
|
|
28077
28444
|
try {
|
|
@@ -28101,7 +28468,13 @@ var init_parser2 = __esm({
|
|
|
28101
28468
|
if (found) {
|
|
28102
28469
|
return { ...found, name: imp.name, importSource: imp.importSource };
|
|
28103
28470
|
}
|
|
28104
|
-
|
|
28471
|
+
warnings.push(
|
|
28472
|
+
`@fwImport: Function "${imp.functionName}" not found in type declarations for "${imp.importSource}". Available exports: ${allNodeTypes.map((nt2) => nt2.functionName).join(", ") || "(none)"}.`
|
|
28473
|
+
);
|
|
28474
|
+
} catch (err) {
|
|
28475
|
+
warnings.push(
|
|
28476
|
+
`@fwImport: Failed to parse type declarations for "${imp.importSource}": ${err.message}. Node "${imp.name}" will use a generic stub.`
|
|
28477
|
+
);
|
|
28105
28478
|
}
|
|
28106
28479
|
return this.createImportStub(imp);
|
|
28107
28480
|
}
|
|
@@ -28400,6 +28773,18 @@ ${fn.getText()}` : fn.getText();
|
|
|
28400
28773
|
const importedNpmNodeTypes = (config2.imports || []).map(
|
|
28401
28774
|
(imp) => this.resolveImportAnnotation(imp, filePath, warnings)
|
|
28402
28775
|
);
|
|
28776
|
+
for (const nt2 of importedNpmNodeTypes) {
|
|
28777
|
+
const dataInputs = Object.keys(nt2.inputs).filter((p) => p !== "execute");
|
|
28778
|
+
const nonControlOutputs = Object.keys(nt2.outputs).filter(
|
|
28779
|
+
(p) => p !== "onSuccess" && p !== "onFailure"
|
|
28780
|
+
);
|
|
28781
|
+
const isStubOnly = nonControlOutputs.length === 1 && nonControlOutputs[0] === "result" && nt2.outputs.result?.dataType === "ANY";
|
|
28782
|
+
if (dataInputs.length === 0 && isStubOnly) {
|
|
28783
|
+
warnings.push(
|
|
28784
|
+
`Could not infer ports for "${nt2.functionName}" from "${nt2.importSource}". Wrap it in a local function with @flowWeaver nodeType annotations instead.`
|
|
28785
|
+
);
|
|
28786
|
+
}
|
|
28787
|
+
}
|
|
28403
28788
|
const allAvailableNodeTypes = [...availableNodeTypes, ...importedNpmNodeTypes];
|
|
28404
28789
|
const instances = (config2.instances || []).map((inst) => {
|
|
28405
28790
|
const nodeTypeExists = allAvailableNodeTypes.some(
|
|
@@ -28750,7 +29135,7 @@ ${fn.getText()}` : fn.getText();
|
|
|
28750
29135
|
* nodeType annotation, we infer an expression node type from its TypeScript
|
|
28751
29136
|
* signature. Phase 1: same-file functions only.
|
|
28752
29137
|
*/
|
|
28753
|
-
inferNodeTypesFromUnannotated(sourceFile, existingNodeTypes) {
|
|
29138
|
+
inferNodeTypesFromUnannotated(sourceFile, existingNodeTypes, localNodeTypes, warnings) {
|
|
28754
29139
|
const allFunctions = extractFunctionLikes(sourceFile);
|
|
28755
29140
|
const referencedTypes = /* @__PURE__ */ new Set();
|
|
28756
29141
|
for (const fn of allFunctions) {
|
|
@@ -28774,8 +29159,24 @@ ${fn.getText()}` : fn.getText();
|
|
|
28774
29159
|
if (unresolvedTypes.size === 0) return [];
|
|
28775
29160
|
const inferredNodeTypes = [];
|
|
28776
29161
|
const alreadyInferred = /* @__PURE__ */ new Set();
|
|
29162
|
+
const builtInByName = new Map(BUILT_IN_NODE_TYPES.map((nt2) => [nt2.name, nt2]));
|
|
29163
|
+
const annotatedNames = new Set(localNodeTypes.map((nt2) => nt2.functionName));
|
|
28777
29164
|
for (const unresolvedType of unresolvedTypes) {
|
|
28778
29165
|
if (alreadyInferred.has(unresolvedType)) continue;
|
|
29166
|
+
const builtIn = builtInByName.get(unresolvedType);
|
|
29167
|
+
if (builtIn) {
|
|
29168
|
+
inferredNodeTypes.push(builtIn);
|
|
29169
|
+
alreadyInferred.add(unresolvedType);
|
|
29170
|
+
if (!annotatedNames.has(unresolvedType)) {
|
|
29171
|
+
const shadowFn = allFunctions.find((fn) => fn.getName() === unresolvedType && !this.hasFlowWeaverAnnotation(fn));
|
|
29172
|
+
if (shadowFn) {
|
|
29173
|
+
warnings.push(
|
|
29174
|
+
`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.`
|
|
29175
|
+
);
|
|
29176
|
+
}
|
|
29177
|
+
}
|
|
29178
|
+
continue;
|
|
29179
|
+
}
|
|
28779
29180
|
const matchedFn = allFunctions.find((fn) => {
|
|
28780
29181
|
if (fn.getName() !== unresolvedType) return false;
|
|
28781
29182
|
return !this.hasFlowWeaverAnnotation(fn);
|
|
@@ -88526,7 +88927,7 @@ function parseIntStrict(value) {
|
|
|
88526
88927
|
// src/cli/index.ts
|
|
88527
88928
|
init_logger();
|
|
88528
88929
|
init_error_utils();
|
|
88529
|
-
var version2 = true ? "0.
|
|
88930
|
+
var version2 = true ? "0.29.0" : "0.0.0-dev";
|
|
88530
88931
|
var program2 = new Command();
|
|
88531
88932
|
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
88933
|
logger.banner(version2);
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.29.0";
|
|
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.0",
|
|
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",
|