@synergenius/flow-weaver 0.8.2 → 0.8.3
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/ast/types.d.ts +1 -1
- package/dist/cli/flow-weaver.mjs +21 -7
- package/dist/cli/templates/workflows/aggregator.js +1 -1
- package/dist/cli/templates/workflows/sequential.js +1 -1
- package/dist/function-like.d.ts +2 -0
- package/dist/function-like.js +1 -0
- package/dist/jsdoc-parser.js +13 -2
- package/dist/parser.js +9 -4
- package/docs/reference/concepts.md +3 -3
- package/docs/reference/iterative-development.md +8 -9
- package/docs/reference/node-conversion.md +2 -5
- package/package.json +1 -1
package/dist/ast/types.d.ts
CHANGED
|
@@ -240,7 +240,7 @@ export type TNodeTypeAST = {
|
|
|
240
240
|
/** Multiple scopes this node creates */
|
|
241
241
|
scopes?: string[];
|
|
242
242
|
/** Variant identifier (set by app layer) */
|
|
243
|
-
variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR' | 'COERCION';
|
|
243
|
+
variant?: 'FUNCTION' | 'WORKFLOW' | 'IMPORTED_WORKFLOW' | 'MAP_ITERATOR' | 'COERCION' | 'STUB';
|
|
244
244
|
/** File path for external node types */
|
|
245
245
|
path?: string;
|
|
246
246
|
/** Function reference for function-based node types */
|
package/dist/cli/flow-weaver.mjs
CHANGED
|
@@ -32298,7 +32298,8 @@ function wrapFunctionDeclaration(fn) {
|
|
|
32298
32298
|
getStartLineNumber: (inc) => fn.getStartLineNumber(inc),
|
|
32299
32299
|
getText: (inc) => fn.getText(inc),
|
|
32300
32300
|
getSourceFile: () => fn.getSourceFile(),
|
|
32301
|
-
getTypeResolutionNode: () => fn
|
|
32301
|
+
getTypeResolutionNode: () => fn,
|
|
32302
|
+
isAmbient: () => !fn.hasBody()
|
|
32302
32303
|
};
|
|
32303
32304
|
}
|
|
32304
32305
|
function extractFunctionLikes(sourceFile) {
|
|
@@ -43872,14 +43873,20 @@ var JSDocParser = class {
|
|
|
43872
43873
|
if (jsdocs.length === 0) return null;
|
|
43873
43874
|
let jsdoc = null;
|
|
43874
43875
|
let flowWeaverTag = null;
|
|
43876
|
+
let isNodeShorthand = false;
|
|
43875
43877
|
for (const doc of jsdocs) {
|
|
43876
43878
|
const tags2 = doc.getTags();
|
|
43877
43879
|
const tag = tags2.find(
|
|
43878
|
-
(t) =>
|
|
43880
|
+
(t) => {
|
|
43881
|
+
if (t.getTagName() !== "flowWeaver") return false;
|
|
43882
|
+
const comment = t.getCommentText()?.trim();
|
|
43883
|
+
return comment === "nodeType" || comment === "node";
|
|
43884
|
+
}
|
|
43879
43885
|
);
|
|
43880
43886
|
if (tag) {
|
|
43881
43887
|
jsdoc = doc;
|
|
43882
43888
|
flowWeaverTag = tag;
|
|
43889
|
+
isNodeShorthand = flowWeaverTag.getCommentText()?.trim() === "node";
|
|
43883
43890
|
break;
|
|
43884
43891
|
}
|
|
43885
43892
|
}
|
|
@@ -43889,6 +43896,9 @@ var JSDocParser = class {
|
|
|
43889
43896
|
inputs: {},
|
|
43890
43897
|
outputs: {}
|
|
43891
43898
|
};
|
|
43899
|
+
if (isNodeShorthand) {
|
|
43900
|
+
config2.expression = true;
|
|
43901
|
+
}
|
|
43892
43902
|
const descriptionText = jsdoc.getDescription();
|
|
43893
43903
|
if (descriptionText && descriptionText.trim()) {
|
|
43894
43904
|
config2.description = descriptionText.trim();
|
|
@@ -45475,6 +45485,10 @@ var AnnotationParser = class {
|
|
|
45475
45485
|
};
|
|
45476
45486
|
}
|
|
45477
45487
|
}
|
|
45488
|
+
const isStub = fn.isAmbient?.() ?? false;
|
|
45489
|
+
if (isStub) {
|
|
45490
|
+
config2.expression = true;
|
|
45491
|
+
}
|
|
45478
45492
|
if (config2.expression) {
|
|
45479
45493
|
const hasExplicitDataInputs = Object.keys(inputs).some((k) => k !== "execute");
|
|
45480
45494
|
const hasExplicitDataOutputs = Object.keys(outputs).some(
|
|
@@ -45534,7 +45548,7 @@ var AnnotationParser = class {
|
|
|
45534
45548
|
assignImplicitPortOrders(outputs);
|
|
45535
45549
|
const jsDocs = fn.getJsDocs();
|
|
45536
45550
|
const jsDocText = jsDocs.map((doc) => doc.getText()).join("\n");
|
|
45537
|
-
const functionText = jsDocText ? `${jsDocText}
|
|
45551
|
+
const functionText = isStub ? void 0 : jsDocText ? `${jsDocText}
|
|
45538
45552
|
${fn.getText()}` : fn.getText();
|
|
45539
45553
|
const isAsync2 = fn.isAsync();
|
|
45540
45554
|
let defaultConfig = void 0;
|
|
@@ -45557,7 +45571,7 @@ ${fn.getText()}` : fn.getText();
|
|
|
45557
45571
|
type: "NodeType",
|
|
45558
45572
|
name: nodeTypeName,
|
|
45559
45573
|
functionName,
|
|
45560
|
-
variant: "FUNCTION",
|
|
45574
|
+
variant: isStub ? "STUB" : "FUNCTION",
|
|
45561
45575
|
inputs,
|
|
45562
45576
|
outputs,
|
|
45563
45577
|
hasSuccessPort: RESERVED_PORT_NAMES.ON_SUCCESS in outputs,
|
|
@@ -48562,7 +48576,7 @@ var configSchema = {
|
|
|
48562
48576
|
};
|
|
48563
48577
|
function generateDefaultTemplate(workflowName, asyncKeyword, returnType) {
|
|
48564
48578
|
return `
|
|
48565
|
-
// Use @expression for
|
|
48579
|
+
// Use @expression for most functions, normal mode only for error-with-data or void side-effects
|
|
48566
48580
|
|
|
48567
48581
|
/**
|
|
48568
48582
|
* Validates input data
|
|
@@ -50298,7 +50312,7 @@ var aggregatorTemplate = {
|
|
|
50298
50312
|
const asyncKeyword = isAsync2 ? "async " : "";
|
|
50299
50313
|
const returnType = isAsync2 ? "Promise<{ onSuccess: boolean; onFailure: boolean; aggregated: any }>" : "{ onSuccess: boolean; onFailure: boolean; aggregated: any }";
|
|
50300
50314
|
return `
|
|
50301
|
-
// Use @expression for
|
|
50315
|
+
// Use @expression for most functions, normal mode only for error-with-data or void side-effects
|
|
50302
50316
|
|
|
50303
50317
|
/**
|
|
50304
50318
|
* Fetches data from source A
|
|
@@ -95182,7 +95196,7 @@ function displayInstalledPackage(pkg) {
|
|
|
95182
95196
|
}
|
|
95183
95197
|
|
|
95184
95198
|
// src/cli/index.ts
|
|
95185
|
-
var version2 = true ? "0.8.
|
|
95199
|
+
var version2 = true ? "0.8.3" : "0.0.0-dev";
|
|
95186
95200
|
var program2 = new Command();
|
|
95187
95201
|
program2.name("flow-weaver").description("Flow Weaver Annotations - Compile and validate workflow files").version(version2, "-v, --version", "Output the current version");
|
|
95188
95202
|
program2.configureOutput({
|
|
@@ -14,7 +14,7 @@ export const aggregatorTemplate = {
|
|
|
14
14
|
? "Promise<{ onSuccess: boolean; onFailure: boolean; aggregated: any }>"
|
|
15
15
|
: "{ onSuccess: boolean; onFailure: boolean; aggregated: any }";
|
|
16
16
|
return `
|
|
17
|
-
// Use @expression for
|
|
17
|
+
// Use @expression for most functions, normal mode only for error-with-data or void side-effects
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* Fetches data from source A
|
|
@@ -26,7 +26,7 @@ const configSchema = {
|
|
|
26
26
|
};
|
|
27
27
|
function generateDefaultTemplate(workflowName, asyncKeyword, returnType) {
|
|
28
28
|
return `
|
|
29
|
-
// Use @expression for
|
|
29
|
+
// Use @expression for most functions, normal mode only for error-with-data or void side-effects
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
32
|
* Validates input data
|
package/dist/function-like.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface FunctionLike {
|
|
|
23
23
|
getDeclarationKind?(): 'const' | 'let' | 'var' | undefined;
|
|
24
24
|
/** Returns the underlying ts-morph Node for type resolution (e.g., Symbol.getTypeAtLocation). */
|
|
25
25
|
getTypeResolutionNode(): Node;
|
|
26
|
+
/** Whether this is an ambient declaration (declare function) with no implementation body. */
|
|
27
|
+
isAmbient?(): boolean;
|
|
26
28
|
}
|
|
27
29
|
/**
|
|
28
30
|
* Scan a source file and return every function-like declaration that could
|
package/dist/function-like.js
CHANGED
package/dist/jsdoc-parser.js
CHANGED
|
@@ -104,15 +104,22 @@ export class JSDocParser {
|
|
|
104
104
|
const jsdocs = func.getJsDocs();
|
|
105
105
|
if (jsdocs.length === 0)
|
|
106
106
|
return null;
|
|
107
|
-
// Find the JSDoc block that contains @flowWeaver nodeType
|
|
107
|
+
// Find the JSDoc block that contains @flowWeaver nodeType (or @flowWeaver node shorthand)
|
|
108
108
|
let jsdoc = null;
|
|
109
109
|
let flowWeaverTag = null;
|
|
110
|
+
let isNodeShorthand = false;
|
|
110
111
|
for (const doc of jsdocs) {
|
|
111
112
|
const tags = doc.getTags();
|
|
112
|
-
const tag = tags.find((t) =>
|
|
113
|
+
const tag = tags.find((t) => {
|
|
114
|
+
if (t.getTagName() !== 'flowWeaver')
|
|
115
|
+
return false;
|
|
116
|
+
const comment = t.getCommentText()?.trim();
|
|
117
|
+
return comment === 'nodeType' || comment === 'node';
|
|
118
|
+
});
|
|
113
119
|
if (tag) {
|
|
114
120
|
jsdoc = doc;
|
|
115
121
|
flowWeaverTag = tag;
|
|
122
|
+
isNodeShorthand = flowWeaverTag.getCommentText()?.trim() === 'node';
|
|
116
123
|
break;
|
|
117
124
|
}
|
|
118
125
|
}
|
|
@@ -123,6 +130,10 @@ export class JSDocParser {
|
|
|
123
130
|
inputs: {},
|
|
124
131
|
outputs: {},
|
|
125
132
|
};
|
|
133
|
+
// @flowWeaver node implies expression mode (auto-detect from signature)
|
|
134
|
+
if (isNodeShorthand) {
|
|
135
|
+
config.expression = true;
|
|
136
|
+
}
|
|
126
137
|
// Extract description from JSDoc comment text (before tags)
|
|
127
138
|
const descriptionText = jsdoc.getDescription();
|
|
128
139
|
if (descriptionText && descriptionText.trim()) {
|
package/dist/parser.js
CHANGED
|
@@ -629,6 +629,12 @@ export class AnnotationParser {
|
|
|
629
629
|
};
|
|
630
630
|
}
|
|
631
631
|
}
|
|
632
|
+
// Ambient declarations (declare function) are stub nodes — interface only, no implementation.
|
|
633
|
+
// Force expression mode so ports are inferred from the TypeScript signature.
|
|
634
|
+
const isStub = fn.isAmbient?.() ?? false;
|
|
635
|
+
if (isStub) {
|
|
636
|
+
config.expression = true;
|
|
637
|
+
}
|
|
632
638
|
// Auto-infer ports for @expression nodes when @input/@output are missing.
|
|
633
639
|
// If the function has @expression but no explicit port annotations, infer
|
|
634
640
|
// data ports from the TypeScript function signature (same logic as unannotated functions).
|
|
@@ -679,11 +685,10 @@ export class AnnotationParser {
|
|
|
679
685
|
// Assign implicit port orders with mandatory port precedence
|
|
680
686
|
assignImplicitPortOrders(inputs);
|
|
681
687
|
assignImplicitPortOrders(outputs);
|
|
682
|
-
// Get function text (JSDoc comment + function)
|
|
683
|
-
// getText() returns just the function, so we need to prepend the JSDoc
|
|
688
|
+
// Get function text (JSDoc comment + function). Stubs have no body to capture.
|
|
684
689
|
const jsDocs = fn.getJsDocs();
|
|
685
690
|
const jsDocText = jsDocs.map((doc) => doc.getText()).join('\n');
|
|
686
|
-
const functionText = jsDocText ? `${jsDocText}\n${fn.getText()}` : fn.getText();
|
|
691
|
+
const functionText = isStub ? undefined : (jsDocText ? `${jsDocText}\n${fn.getText()}` : fn.getText());
|
|
687
692
|
// Detect async keyword on function declaration
|
|
688
693
|
const isAsync = fn.isAsync();
|
|
689
694
|
// Convert defaultConfig
|
|
@@ -718,7 +723,7 @@ export class AnnotationParser {
|
|
|
718
723
|
type: 'NodeType',
|
|
719
724
|
name: nodeTypeName,
|
|
720
725
|
functionName,
|
|
721
|
-
variant: 'FUNCTION',
|
|
726
|
+
variant: isStub ? 'STUB' : 'FUNCTION',
|
|
722
727
|
inputs,
|
|
723
728
|
outputs,
|
|
724
729
|
hasSuccessPort: RESERVED_PORT_NAMES.ON_SUCCESS in outputs,
|
|
@@ -137,7 +137,7 @@ Expression nodes are pure functions where:
|
|
|
137
137
|
- Object return `{ a, b }` -> one port per property
|
|
138
138
|
- Best for: transformers, math, utilities, data mapping, async fetchers, API calls
|
|
139
139
|
|
|
140
|
-
> **Start with expression mode.** Only switch to normal mode when you need
|
|
140
|
+
> **Start with expression mode.** Only switch to normal mode when you need to return data alongside a failure (error-with-data patterns) or for void side-effect functions. Expression nodes handle success/failure branching automatically — throw to trigger the `onFailure` path.
|
|
141
141
|
|
|
142
142
|
#### Async Expression Example
|
|
143
143
|
|
|
@@ -157,7 +157,7 @@ async function fetchUser(userId: string): Promise<User> {
|
|
|
157
157
|
|
|
158
158
|
### Node Type (Normal Mode)
|
|
159
159
|
|
|
160
|
-
Use normal mode when you need
|
|
160
|
+
Use normal mode when you need to return error data alongside the failure signal, or for `void` side-effect functions.
|
|
161
161
|
|
|
162
162
|
```typescript
|
|
163
163
|
/**
|
|
@@ -196,7 +196,7 @@ export function workflowName(
|
|
|
196
196
|
}
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
-
> STEP connections (`execute`, `onSuccess`, `onFailure`) are auto-wired for expression nodes
|
|
199
|
+
> STEP connections (`execute`, `onSuccess`, `onFailure`) are auto-wired for expression nodes. Add explicit STEP connections only for normal mode nodes or to override the automatic wiring.
|
|
200
200
|
|
|
201
201
|
### Importing External Functions
|
|
202
202
|
|
|
@@ -55,16 +55,15 @@ flow-weaver validate <file>
|
|
|
55
55
|
|
|
56
56
|
### Phase 3: Create the Nodes
|
|
57
57
|
|
|
58
|
-
**Start with `@expression` mode for all nodes.** Only switch to normal mode when you need
|
|
58
|
+
**Start with `@expression` mode for all nodes.** Only switch to normal mode when you need to return error data alongside the failure signal, or for void side-effects.
|
|
59
59
|
|
|
60
60
|
Create nodes by adding `@flowWeaver nodeType` annotated functions.
|
|
61
61
|
|
|
62
62
|
Create at most 3 nodes at a time, test each.
|
|
63
63
|
|
|
64
64
|
Default to expression mode. Use normal mode only for:
|
|
65
|
-
- **
|
|
66
|
-
- **
|
|
67
|
-
- **Error-with-data patterns** -- returning error details alongside the failure signal
|
|
65
|
+
- **Error-with-data patterns** -- returning structured error details alongside the failure signal
|
|
66
|
+
- **Void side-effects** -- functions with no return value that need explicit control flow
|
|
68
67
|
|
|
69
68
|
**Expression mode (recommended for most nodes):**
|
|
70
69
|
|
|
@@ -82,7 +81,7 @@ function addNumbers(a: number, b: number): number {
|
|
|
82
81
|
}
|
|
83
82
|
```
|
|
84
83
|
|
|
85
|
-
> Use `@expression` for most nodes. Only use normal mode
|
|
84
|
+
> Use `@expression` for most nodes. Expression nodes support failure branching via throw. Only use normal mode for error-with-data patterns or void returns.
|
|
86
85
|
|
|
87
86
|
**Normal mode (for custom error handling):**
|
|
88
87
|
|
|
@@ -147,7 +146,7 @@ flow-weaver describe <file> # Get workflow structure as JSON
|
|
|
147
146
|
|
|
148
147
|
### 1. Missing STEP Connections (Normal Mode) or Adding Unnecessary Ones (Expression Mode)
|
|
149
148
|
|
|
150
|
-
**Normal mode nodes** need explicit STEP wiring: `@connect Start.execute -> firstNode.execute`. Without this, no node will run. **Expression mode** nodes auto-wire STEP connections from data flow -- you only need data connections for linear pipelines. Add explicit STEP connections
|
|
149
|
+
**Normal mode nodes** need explicit STEP wiring: `@connect Start.execute -> firstNode.execute`. Without this, no node will run. **Expression mode** nodes auto-wire STEP connections from data flow -- you only need data connections for linear pipelines. Add explicit STEP connections when you need to route `onFailure` to a specific node (both expression and normal mode nodes support this).
|
|
151
150
|
|
|
152
151
|
### 2. Wrapping Node Inputs in Object
|
|
153
152
|
|
|
@@ -173,13 +172,13 @@ Always run `flow-weaver validate` after adding nodes/connections. Don't batch 10
|
|
|
173
172
|
|
|
174
173
|
### 6. Using Normal Mode When Expression Mode Works
|
|
175
174
|
|
|
176
|
-
If the function returns a value
|
|
175
|
+
If the function returns a value, use `@expression`. It's simpler and less error-prone. Expression mode eliminates the `execute` parameter, the `if (!execute)` guard, and the `onSuccess`/`onFailure` boilerplate. The compiler wraps the call in try/catch, so throwing triggers the `onFailure` path automatically.
|
|
177
176
|
|
|
178
177
|
**Rule of thumb:** If you are writing `if (!execute) return ...` and a `try/catch` that just returns `{ onSuccess: false, onFailure: true }`, you should be using expression mode instead.
|
|
179
178
|
|
|
180
179
|
### 7. Defaulting to Normal Mode
|
|
181
180
|
|
|
182
181
|
Normal mode adds boilerplate that expression mode handles automatically. Default to `@expression` for every node. Only reach for normal mode when the function needs to:
|
|
183
|
-
-
|
|
184
|
-
-
|
|
182
|
+
- Return error data alongside the failure signal (not just signal failure)
|
|
183
|
+
- Perform void side-effects with no return value
|
|
185
184
|
- Perform void side-effects with explicit control flow
|
|
@@ -10,15 +10,12 @@ keywords: [conversion, expression mode, normal mode, function, transform, nodeTy
|
|
|
10
10
|
Is it a pure function that returns a value?
|
|
11
11
|
YES -> expression mode (@expression)
|
|
12
12
|
NO |
|
|
13
|
-
Does it need to route onSuccess/onFailure to different nodes?
|
|
14
|
-
YES -> normal mode
|
|
15
|
-
NO |
|
|
16
13
|
Does it need to return error data alongside the failure signal?
|
|
17
14
|
YES -> normal mode
|
|
18
15
|
NO -> expression mode (@expression)
|
|
19
16
|
```
|
|
20
17
|
|
|
21
|
-
**Default to expression mode
|
|
18
|
+
**Default to expression mode.** Expression nodes handle success/failure branching via throw. Only use normal mode when you need to return data on the failure path, or for void side-effects.
|
|
22
19
|
|
|
23
20
|
---
|
|
24
21
|
|
|
@@ -280,7 +277,7 @@ When no `--mode` is specified:
|
|
|
280
277
|
- **Expression** (default): function returns a value (non-void), whether sync or async
|
|
281
278
|
- **Normal**: void return, or user explicitly requests normal mode
|
|
282
279
|
|
|
283
|
-
**Default to expression mode
|
|
280
|
+
**Default to expression mode.** Expression nodes support failure branching via throw. Only switch to normal mode for error-with-data patterns or void side-effects.
|
|
284
281
|
|
|
285
282
|
The compiler fully supports async expression nodes -- `await`, async detection, try/catch wrapping, and onSuccess/onFailure are all handled automatically.
|
|
286
283
|
|
package/package.json
CHANGED