@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.
@@ -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);
@@ -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
- const localNodes = ast.nodeTypes.filter((n) => !n.importSource && n.sourceLocation?.file === ast.sourceFile);
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
@@ -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
@@ -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.28.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 functionText = node.functionText;
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
- } catch {
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.28.0" : "0.0.0-dev";
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.28.0";
1
+ export declare const VERSION = "0.29.0";
2
2
  //# sourceMappingURL=generated-version.d.ts.map
@@ -1,3 +1,3 @@
1
1
  // Auto-generated by scripts/generate-version.ts — do not edit manually
2
- export const VERSION = '0.28.0';
2
+ export const VERSION = '0.29.0';
3
3
  //# sourceMappingURL=generated-version.js.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
- lines.push(` let ${safeChildId}Idx: number;`);
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
- const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes);
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
- const inferredNodeTypes = this.inferNodeTypesFromUnannotated(sourceFile, nodeTypes);
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
- // Silently skip packages whose .d.ts can't be parsed
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 the same file
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
- // Find matching function
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.28.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",