awaitly-analyze 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +517 -0
- package/dist/cli.js +58 -0
- package/dist/index.cjs +35 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1568 -0
- package/dist/index.d.ts +1568 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/package.json +76 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Jag Reehal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
# awaitly-analyze
|
|
2
|
+
|
|
3
|
+
Static workflow analysis for [awaitly](https://github.com/jagreehal/awaitly). Analyze workflow source code to extract structure, calculate complexity metrics, and generate visualizations.
|
|
4
|
+
|
|
5
|
+
## CLI Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Basic usage - output to stdout
|
|
9
|
+
awaitly-analyze ./src/workflows/checkout.ts
|
|
10
|
+
|
|
11
|
+
# JSON format
|
|
12
|
+
awaitly-analyze ./src/workflows/checkout.ts --format=json
|
|
13
|
+
|
|
14
|
+
# Show step keys and change diagram direction
|
|
15
|
+
awaitly-analyze ./src/workflows/checkout.ts --keys --direction=LR
|
|
16
|
+
|
|
17
|
+
# Write output file adjacent to source (creates checkout.workflow.md)
|
|
18
|
+
awaitly-analyze ./src/workflows/checkout.ts --output-adjacent
|
|
19
|
+
|
|
20
|
+
# Custom suffix (creates checkout.diagram.md)
|
|
21
|
+
awaitly-analyze ./src/workflows/checkout.ts -o --suffix=diagram
|
|
22
|
+
|
|
23
|
+
# JSON format with adjacent output (creates checkout.analysis.json)
|
|
24
|
+
awaitly-analyze ./src/workflows/checkout.ts -o --suffix=analysis --format=json
|
|
25
|
+
|
|
26
|
+
# Write to file only, suppress stdout
|
|
27
|
+
awaitly-analyze ./src/workflows/checkout.ts -o --no-stdout
|
|
28
|
+
|
|
29
|
+
# Generate interactive HTML with click-to-inspect
|
|
30
|
+
awaitly-analyze ./src/workflows/checkout.ts --html
|
|
31
|
+
|
|
32
|
+
# Custom HTML output path
|
|
33
|
+
awaitly-analyze ./src/workflows/checkout.ts --html --html-output=./docs/checkout.html
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### CLI Options
|
|
37
|
+
|
|
38
|
+
| Option | Default | Description |
|
|
39
|
+
|--------|---------|-------------|
|
|
40
|
+
| `--format=<format>` | `mermaid` | Output format: `mermaid` or `json` |
|
|
41
|
+
| `--html` | - | Generate interactive HTML file (Mermaid CDN + click-to-inspect) |
|
|
42
|
+
| `--html-output=<path>` | `<basename>.html` | Output path for the HTML file |
|
|
43
|
+
| `--keys` | - | Show step cache keys in diagram |
|
|
44
|
+
| `--direction=<dir>` | `TB` | Diagram direction: `TB`, `LR`, `BT`, `RL` |
|
|
45
|
+
| `--output-adjacent`, `-o` | - | Write output file next to source file |
|
|
46
|
+
| `--suffix=<value>` | `workflow` | Configurable suffix for output file |
|
|
47
|
+
| `--no-stdout` | - | Suppress stdout when writing to file (requires `-o`) |
|
|
48
|
+
| `--dsl-output=<value>` | `off` | Write DSL: `off`, `.awaitly`, or custom path (for visualization) |
|
|
49
|
+
| `--write-dsl` | - | Shorthand for `--dsl-output=.awaitly` |
|
|
50
|
+
| `--help`, `-h` | - | Show help message |
|
|
51
|
+
|
|
52
|
+
### Output File Naming
|
|
53
|
+
|
|
54
|
+
When using `--output-adjacent`:
|
|
55
|
+
- Mermaid format: `{basename}.{suffix}.md`
|
|
56
|
+
- JSON format: `{basename}.{suffix}.json`
|
|
57
|
+
|
|
58
|
+
Example: `checkout.ts` with `--suffix=workflow` produces `checkout.workflow.md`
|
|
59
|
+
|
|
60
|
+
## Features
|
|
61
|
+
|
|
62
|
+
- **Static Analysis** - Extract workflow structure from TypeScript source without execution
|
|
63
|
+
- **Path Generation** - Enumerate all possible execution paths through a workflow
|
|
64
|
+
- **Complexity Metrics** - Calculate cyclomatic complexity, cognitive complexity, and more
|
|
65
|
+
- **Mermaid Diagrams** - Generate flowchart visualizations
|
|
66
|
+
- **Test Matrix** - Generate test coverage matrices for workflow paths
|
|
67
|
+
- **Cross-Workflow Composition** - Analyze dependencies between workflows
|
|
68
|
+
|
|
69
|
+
## Installation
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npm install awaitly-analyze ts-morph
|
|
73
|
+
# or
|
|
74
|
+
pnpm add awaitly-analyze ts-morph
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`ts-morph` is a required peer dependency.
|
|
78
|
+
|
|
79
|
+
## Quick Start
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import {
|
|
83
|
+
analyze,
|
|
84
|
+
generatePaths,
|
|
85
|
+
calculateComplexity,
|
|
86
|
+
renderStaticMermaid,
|
|
87
|
+
} from 'awaitly-analyze';
|
|
88
|
+
|
|
89
|
+
// Analyze a workflow file
|
|
90
|
+
const ir = analyze('./src/workflows/checkout.ts').single();
|
|
91
|
+
|
|
92
|
+
// Generate all possible paths
|
|
93
|
+
const paths = generatePaths(ir);
|
|
94
|
+
console.log(`Found ${paths.length} unique execution paths`);
|
|
95
|
+
|
|
96
|
+
// Calculate complexity metrics
|
|
97
|
+
const metrics = calculateComplexity(ir);
|
|
98
|
+
console.log(`Cyclomatic complexity: ${metrics.cyclomaticComplexity}`);
|
|
99
|
+
|
|
100
|
+
// Generate Mermaid diagram
|
|
101
|
+
const mermaid = renderStaticMermaid(ir);
|
|
102
|
+
console.log(mermaid);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## API Reference
|
|
106
|
+
|
|
107
|
+
### Analyzing Workflows
|
|
108
|
+
|
|
109
|
+
#### `analyze(filePath, options?)`
|
|
110
|
+
|
|
111
|
+
Returns a fluent interface for analyzing workflows in a file.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { analyze } from 'awaitly-analyze';
|
|
115
|
+
|
|
116
|
+
// Single workflow file - get the workflow directly
|
|
117
|
+
const ir = analyze('./checkout.ts').single();
|
|
118
|
+
|
|
119
|
+
// Multiple workflows - get all as array
|
|
120
|
+
const workflows = analyze('./workflows.ts').all();
|
|
121
|
+
|
|
122
|
+
// Get specific workflow by name
|
|
123
|
+
const checkout = analyze('./workflows.ts').named('checkoutWorkflow');
|
|
124
|
+
|
|
125
|
+
// Safe access (returns null instead of throwing)
|
|
126
|
+
const ir = analyze('./checkout.ts').singleOrNull();
|
|
127
|
+
const first = analyze('./workflows.ts').firstOrNull();
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### `analyze.source(code, options?)`
|
|
131
|
+
|
|
132
|
+
Analyze workflow source code from a string.
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
const ir = analyze.source(`
|
|
136
|
+
const workflow = createWorkflow({
|
|
137
|
+
fetchUser: async (id: string) => ({ id, name: 'Alice' }),
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
async function run(id: string) {
|
|
141
|
+
return await workflow(async (step, deps) => {
|
|
142
|
+
const user = await step('fetchUser', () => deps.fetchUser(id), { key: 'user' });
|
|
143
|
+
return user;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
`).single();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Path Generation
|
|
150
|
+
|
|
151
|
+
#### `generatePaths(ir, options?)`
|
|
152
|
+
|
|
153
|
+
Generate all unique execution paths through a workflow.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { generatePaths, calculatePathStatistics } from 'awaitly-analyze';
|
|
157
|
+
|
|
158
|
+
const paths = generatePaths(ir, { maxPaths: 100 });
|
|
159
|
+
|
|
160
|
+
// Get statistics
|
|
161
|
+
const stats = calculatePathStatistics(paths);
|
|
162
|
+
console.log(`Total paths: ${stats.totalPaths}`);
|
|
163
|
+
console.log(`Shortest path: ${stats.shortestPathLength} steps`);
|
|
164
|
+
console.log(`Longest path: ${stats.longestPathLength} steps`);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### `generatePathsWithMetadata(ir, options?)`
|
|
168
|
+
|
|
169
|
+
Generate paths with additional metadata about limit hits.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { generatePathsWithMetadata } from 'awaitly-analyze';
|
|
173
|
+
|
|
174
|
+
const { paths, limitHit } = generatePathsWithMetadata(ir, { maxPaths: 50 });
|
|
175
|
+
|
|
176
|
+
if (limitHit) {
|
|
177
|
+
console.log('Warning: Path limit reached, not all paths generated');
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Complexity Metrics
|
|
182
|
+
|
|
183
|
+
#### `calculateComplexity(ir)`
|
|
184
|
+
|
|
185
|
+
Calculate complexity metrics for a workflow.
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
import { calculateComplexity } from 'awaitly-analyze';
|
|
189
|
+
|
|
190
|
+
const metrics = calculateComplexity(ir);
|
|
191
|
+
|
|
192
|
+
console.log(`Cyclomatic complexity: ${metrics.cyclomaticComplexity}`);
|
|
193
|
+
console.log(`Cognitive complexity: ${metrics.cognitiveComplexity}`);
|
|
194
|
+
console.log(`Max nesting depth: ${metrics.maxDepth}`);
|
|
195
|
+
console.log(`Max parallel breadth: ${metrics.maxParallelBreadth}`);
|
|
196
|
+
console.log(`Decision points: ${metrics.decisionPoints}`);
|
|
197
|
+
console.log(`Path count: ${metrics.pathCount}`);
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `assessComplexity(metrics, thresholds?)`
|
|
201
|
+
|
|
202
|
+
Get a complexity assessment with warnings.
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
import {
|
|
206
|
+
analyze,
|
|
207
|
+
calculateComplexity,
|
|
208
|
+
assessComplexity,
|
|
209
|
+
formatComplexitySummary
|
|
210
|
+
} from 'awaitly-analyze';
|
|
211
|
+
|
|
212
|
+
const ir = analyze('./checkout.ts').single();
|
|
213
|
+
const metrics = calculateComplexity(ir);
|
|
214
|
+
const assessment = assessComplexity(metrics);
|
|
215
|
+
console.log(formatComplexitySummary(metrics, assessment));
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Mermaid Diagrams
|
|
219
|
+
|
|
220
|
+
#### `renderStaticMermaid(ir, options?)`
|
|
221
|
+
|
|
222
|
+
Generate a Mermaid flowchart diagram.
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
import { renderStaticMermaid } from 'awaitly-analyze';
|
|
226
|
+
|
|
227
|
+
const mermaid = renderStaticMermaid(ir, {
|
|
228
|
+
direction: 'TB', // 'TB' | 'LR' | 'BT' | 'RL'
|
|
229
|
+
includeKeys: true,
|
|
230
|
+
includeDescriptions: true,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Use in markdown:
|
|
234
|
+
// ```mermaid
|
|
235
|
+
// ${mermaid}
|
|
236
|
+
// ```
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### `renderPathsMermaid(ir, paths, options?)`
|
|
240
|
+
|
|
241
|
+
Generate a diagram highlighting specific paths.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { renderPathsMermaid, generatePaths } from 'awaitly-analyze';
|
|
245
|
+
|
|
246
|
+
const paths = generatePaths(ir);
|
|
247
|
+
const diagram = renderPathsMermaid(ir, paths.slice(0, 3));
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Test Matrix
|
|
251
|
+
|
|
252
|
+
#### `generateTestMatrix(paths)`
|
|
253
|
+
|
|
254
|
+
Generate a test coverage matrix from paths.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { generateTestMatrix, formatTestMatrixMarkdown } from 'awaitly-analyze';
|
|
258
|
+
|
|
259
|
+
const paths = generatePaths(ir);
|
|
260
|
+
const matrix = generateTestMatrix(paths);
|
|
261
|
+
|
|
262
|
+
// Format as markdown table
|
|
263
|
+
console.log(formatTestMatrixMarkdown(matrix));
|
|
264
|
+
|
|
265
|
+
// Or as code
|
|
266
|
+
import { formatTestMatrixAsCode } from 'awaitly-analyze';
|
|
267
|
+
console.log(formatTestMatrixAsCode(matrix));
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Cross-Workflow Composition
|
|
271
|
+
|
|
272
|
+
#### `analyzeWorkflowGraph(files, options?)`
|
|
273
|
+
|
|
274
|
+
Analyze dependencies between workflows across multiple files.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
import {
|
|
278
|
+
analyzeWorkflowGraph,
|
|
279
|
+
getTopologicalOrder,
|
|
280
|
+
renderGraphMermaid,
|
|
281
|
+
} from 'awaitly-analyze';
|
|
282
|
+
|
|
283
|
+
const graph = analyzeWorkflowGraph([
|
|
284
|
+
'./src/workflows/checkout.ts',
|
|
285
|
+
'./src/workflows/payment.ts',
|
|
286
|
+
'./src/workflows/shipping.ts',
|
|
287
|
+
]);
|
|
288
|
+
|
|
289
|
+
// Get execution order (workflows with no dependencies first)
|
|
290
|
+
const order = getTopologicalOrder(graph);
|
|
291
|
+
console.log('Execution order:', order.map(n => n.name).join(' -> '));
|
|
292
|
+
|
|
293
|
+
// Visualize the dependency graph
|
|
294
|
+
const diagram = renderGraphMermaid(graph);
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Interactive HTML
|
|
298
|
+
|
|
299
|
+
Generate a self-contained HTML file with an interactive Mermaid diagram and a click-to-inspect panel. The output includes 6 color themes, system preference auto-detection, and localStorage persistence.
|
|
300
|
+
|
|
301
|
+
#### `extractNodeMetadata(ir)`
|
|
302
|
+
|
|
303
|
+
Walk the IR tree and produce a `WorkflowMetadata` object with per-node details (step IDs, callees, retry/timeout config, types, source locations) keyed by Mermaid node ID.
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
import { analyze, extractNodeMetadata } from 'awaitly-analyze';
|
|
307
|
+
import type { WorkflowMetadata } from 'awaitly-analyze';
|
|
308
|
+
|
|
309
|
+
const ir = analyze('./checkout.ts').single();
|
|
310
|
+
const metadata: WorkflowMetadata = extractNodeMetadata(ir);
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
#### `generateInteractiveHTML(mermaidText, metadata, options?)`
|
|
314
|
+
|
|
315
|
+
Combine Mermaid text from `renderStaticMermaid()` with metadata from `extractNodeMetadata()` into a complete HTML string.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import {
|
|
319
|
+
analyze,
|
|
320
|
+
renderStaticMermaid,
|
|
321
|
+
extractNodeMetadata,
|
|
322
|
+
generateInteractiveHTML,
|
|
323
|
+
} from 'awaitly-analyze';
|
|
324
|
+
import type { InteractiveHTMLOptions } from 'awaitly-analyze';
|
|
325
|
+
|
|
326
|
+
const ir = analyze('./checkout.ts').single();
|
|
327
|
+
const mermaid = renderStaticMermaid(ir);
|
|
328
|
+
const metadata = extractNodeMetadata(ir);
|
|
329
|
+
const html = generateInteractiveHTML(mermaid, metadata, {
|
|
330
|
+
theme: 'midnight', // 'midnight' | 'ocean' | 'ember' | 'forest' | 'daylight' | 'paper'
|
|
331
|
+
title: 'Checkout Workflow', // defaults to workflow name
|
|
332
|
+
direction: 'TB', // diagram direction
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
import { writeFileSync } from 'node:fs';
|
|
336
|
+
writeFileSync('checkout.html', html);
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
**`InteractiveHTMLOptions`:**
|
|
340
|
+
|
|
341
|
+
| Option | Type | Default | Description |
|
|
342
|
+
|--------|------|---------|-------------|
|
|
343
|
+
| `title` | `string` | workflow name | Page title |
|
|
344
|
+
| `theme` | `string` | auto-detect | Initial color theme |
|
|
345
|
+
| `mermaidCdnUrl` | `string` | latest v11 | Mermaid CDN URL |
|
|
346
|
+
| `direction` | `"TB" \| "LR" \| "BT" \| "RL"` | `"TB"` | Diagram direction |
|
|
347
|
+
|
|
348
|
+
### Workflow Diagram DSL
|
|
349
|
+
|
|
350
|
+
#### `renderWorkflowDSL(ir)`
|
|
351
|
+
|
|
352
|
+
Produce a state-machine-like DSL for xstate-style visualization (states and transitions with event labels). Types are defined in `awaitly/workflow`; the analyzer emits DSL that conforms to them.
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { analyze, renderWorkflowDSL, writeDSLToAwaitlyDir } from 'awaitly-analyze';
|
|
356
|
+
import type { WorkflowDiagramDSL } from 'awaitly/workflow';
|
|
357
|
+
|
|
358
|
+
const ir = analyze('./checkout.ts').single();
|
|
359
|
+
const dsl: WorkflowDiagramDSL = renderWorkflowDSL(ir);
|
|
360
|
+
|
|
361
|
+
// Optional: write to .awaitly/dsl/ (default) or a custom folder
|
|
362
|
+
await writeDSLToAwaitlyDir(dsl, { rootDir: process.cwd() });
|
|
363
|
+
// Custom folder: await writeDSLToAwaitlyDir(dsl, { rootDir: process.cwd(), outputDir: 'dist/dsl' });
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Snapshot alignment:** When a workflow is running, `WorkflowSnapshot.execution.currentStepId` holds the step key. DSL step state ids use the same step key so a visualizer can highlight the current node. See `awaitly/workflow` diagram-dsl types for details.
|
|
367
|
+
|
|
368
|
+
### JSON Output
|
|
369
|
+
|
|
370
|
+
#### `renderStaticJSON(ir, options?)`
|
|
371
|
+
|
|
372
|
+
Serialize workflow IR to JSON.
|
|
373
|
+
|
|
374
|
+
```typescript
|
|
375
|
+
import { renderStaticJSON } from 'awaitly-analyze';
|
|
376
|
+
|
|
377
|
+
const json = renderStaticJSON(ir, { pretty: true });
|
|
378
|
+
fs.writeFileSync('workflow.json', json);
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
## Detected Patterns
|
|
382
|
+
|
|
383
|
+
The analyzer detects the following awaitly patterns:
|
|
384
|
+
|
|
385
|
+
- `createWorkflow()` - Standard workflow creation
|
|
386
|
+
- `run()` - Inline workflow execution
|
|
387
|
+
- `createSagaWorkflow()` - Saga workflow creation
|
|
388
|
+
- `runSaga()` - Inline saga execution
|
|
389
|
+
|
|
390
|
+
Within workflows, it detects:
|
|
391
|
+
|
|
392
|
+
- `step('id', fn, opts?)` - Single steps (string ID required) with retry/timeout options
|
|
393
|
+
- `step.parallel()` / `allAsync()` / `allSettledAsync()` - Parallel execution
|
|
394
|
+
- `step.race()` / `anyAsync()` - Race execution
|
|
395
|
+
- `step.sleep(id, duration, opts?)` - Sleep steps (ID required as first argument)
|
|
396
|
+
- `step.retry(id, operation, opts)` - Retry wrappers (ID required as first argument)
|
|
397
|
+
- `step.withTimeout(id, operation, opts)` - Timeout wrappers (ID required as first argument)
|
|
398
|
+
- `step.try(id, operation, opts)` - Try/catch steps (ID required as first argument)
|
|
399
|
+
- `step.fromResult(id, operation, opts)` - FromResult steps (ID required as first argument)
|
|
400
|
+
- `step.getWritable()` / `step.getReadable()` / `step.streamForEach()` - Streaming
|
|
401
|
+
- `when()` / `unless()` / `whenOr()` / `unlessOr()` - Conditional helpers
|
|
402
|
+
- `if/else`, `switch` - Control flow
|
|
403
|
+
- `for`, `while`, `forEach`, `map` - Loops
|
|
404
|
+
- Saga steps with compensation (`saga.step()`, `saga.tryStep()`)
|
|
405
|
+
|
|
406
|
+
## Import Styles
|
|
407
|
+
|
|
408
|
+
The analyzer supports various import styles:
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
// Named imports
|
|
412
|
+
import { createWorkflow, run } from 'awaitly';
|
|
413
|
+
|
|
414
|
+
// Aliased imports
|
|
415
|
+
import { createWorkflow as cw } from 'awaitly';
|
|
416
|
+
|
|
417
|
+
// Namespace imports
|
|
418
|
+
import * as Awaitly from 'awaitly';
|
|
419
|
+
Awaitly.createWorkflow({...});
|
|
420
|
+
|
|
421
|
+
// Default imports
|
|
422
|
+
import Awaitly from 'awaitly';
|
|
423
|
+
Awaitly.run(async (step) => {...});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Types
|
|
427
|
+
|
|
428
|
+
Key types exported from the package:
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import type {
|
|
432
|
+
// IR nodes
|
|
433
|
+
StaticWorkflowIR,
|
|
434
|
+
StaticFlowNode,
|
|
435
|
+
StaticStepNode,
|
|
436
|
+
StaticParallelNode,
|
|
437
|
+
StaticConditionalNode,
|
|
438
|
+
|
|
439
|
+
// Paths
|
|
440
|
+
WorkflowPath,
|
|
441
|
+
PathStatistics,
|
|
442
|
+
|
|
443
|
+
// Complexity
|
|
444
|
+
ComplexityMetrics,
|
|
445
|
+
ComplexityAssessment,
|
|
446
|
+
|
|
447
|
+
// Test matrix
|
|
448
|
+
TestMatrix,
|
|
449
|
+
TestPath,
|
|
450
|
+
|
|
451
|
+
// Graph
|
|
452
|
+
WorkflowGraph,
|
|
453
|
+
WorkflowGraphNode,
|
|
454
|
+
|
|
455
|
+
// Interactive HTML
|
|
456
|
+
NodeMetadata,
|
|
457
|
+
WorkflowMetadata,
|
|
458
|
+
InteractiveHTMLOptions,
|
|
459
|
+
} from 'awaitly-analyze';
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### API types reference
|
|
463
|
+
|
|
464
|
+
Main static-analysis node types and when fields are populated:
|
|
465
|
+
|
|
466
|
+
- **StaticWorkflowNode** (root): `workflowName`, `source`, `dependencies`, `children`, `description`, `markdown`, `jsdocDescription?`, `errorTypes`.
|
|
467
|
+
`description` and `markdown` are set only for `createWorkflow` / `createSagaWorkflow` (from options or deps). They are undefined for `run()` / `runSaga()` (no options object). `jsdocDescription` is extracted from JSDoc above the workflow variable when present.
|
|
468
|
+
|
|
469
|
+
- **StaticStepNode**: `stepId`, `callee`, `name`, `key`, `description`, `markdown`, `jsdocDescription?`, `retry`, `timeout`.
|
|
470
|
+
`stepId` is the required first argument for all step types: `step('id', fn, opts)`, `step.sleep('id', duration, opts?)`, `step.retry('id', operation, opts)`, `step.withTimeout('id', operation, opts)`, `step.try('id', operation, opts)`, `step.fromResult('id', operation, opts)`. Legacy `step(fn, opts)` is still parsed but yields `stepId: "<missing>"` and a warning. `description` and `markdown` come from step options; `jsdocDescription` is extracted from JSDoc above the step statement when present.
|
|
471
|
+
|
|
472
|
+
- **StaticSagaStepNode**: `callee`, `name`, `description`, `markdown`, `jsdocDescription?`, `hasCompensation`, `compensationCallee`, `isTryStep`.
|
|
473
|
+
`description` and `markdown` come from saga step options (e.g. `saga.step(fn, { description, markdown })`). `jsdocDescription` is extracted from JSDoc above the saga step statement when present.
|
|
474
|
+
|
|
475
|
+
- **DependencyInfo**: `name`, `typeSignature?`, `errorTypes`.
|
|
476
|
+
`typeSignature` is the TypeScript type of the dependency (e.g. the function type), when the type checker is available; it may be undefined. `errorTypes` is not yet inferred from types and is typically empty.
|
|
477
|
+
|
|
478
|
+
### JSDoc
|
|
479
|
+
|
|
480
|
+
The analyzer extracts JSDoc comments from workflow declarations (`createWorkflow` / `createSagaWorkflow` variable statements) and from step call sites (the statement containing `await step(...)`, `step.sleep(...)`, `saga.step(...)`, etc.). Extracted text is exposed as **`jsdocDescription`** on the root (`StaticWorkflowNode`) and on step nodes (`StaticStepNode`, `StaticSagaStepNode`). Only the main description (text before the first `@tag`) is extracted; `@param` / `@returns` are not parsed into separate fields. Option-based `description` and `markdown` remain the canonical documentation fields and take precedence for display; JSDoc is additive so consumers can use `description ?? jsdocDescription` for fallback.
|
|
481
|
+
|
|
482
|
+
### JSON output shape
|
|
483
|
+
|
|
484
|
+
The output of `renderStaticJSON(ir)` has this structure. Agents and doc generators can use it to parse or validate the JSON.
|
|
485
|
+
|
|
486
|
+
- **Top level**: `{ root, metadata?, references? }`
|
|
487
|
+
- `root`: StaticWorkflowNode (see below)
|
|
488
|
+
- `metadata`: `{ analyzedAt, filePath, tsVersion?, warnings?, stats? }`
|
|
489
|
+
- `references`: object mapping workflow name to `{ root, metadata? }` (when inlined)
|
|
490
|
+
|
|
491
|
+
- **StaticWorkflowNode** (`root`): `type: "workflow"`, `id`, `workflowName`, `source?`, `dependencies[]`, `errorTypes[]`, `children[]`, `description?`, `markdown?`, `jsdocDescription?`, `name?`, `key?`, `location?`
|
|
492
|
+
|
|
493
|
+
- **DependencyInfo** (each entry in `dependencies`): `name`, `typeSignature?`, `errorTypes[]`
|
|
494
|
+
|
|
495
|
+
- **Flow nodes** (`children` and nested): discriminated by `type`:
|
|
496
|
+
- `"step"`: `id`, `stepId`, `name?`, `key?`, `callee?`, `description?`, `markdown?`, `jsdocDescription?`, `retry?`, `timeout?`, `location?`
|
|
497
|
+
- `"saga-step"`: `id`, `name?`, `callee?`, `description?`, `markdown?`, `jsdocDescription?`, `hasCompensation`, `compensationCallee?`, `isTryStep?`, `location?`
|
|
498
|
+
- `"sequence"`: `id`, `children[]`
|
|
499
|
+
- `"parallel"`: `id`, `children[]`, `mode` ("all" | "allSettled"), `callee?`
|
|
500
|
+
- `"race"`: `id`, `children[]`, `callee?`
|
|
501
|
+
- `"conditional"`: `id`, `condition`, `consequent[]`, `alternate?`, `helper?`, `defaultValue?`
|
|
502
|
+
- `"switch"`: `id`, `expression`, `cases[]` (each: `value?`, `isDefault`, `body[]`)
|
|
503
|
+
- `"loop"`: `id`, `loopType`, `body[]`, `iterSource?`, `boundKnown`, `boundCount?`
|
|
504
|
+
- `"stream"`: `id`, `streamType`, `namespace?`, `options?`, `callee?`
|
|
505
|
+
- `"workflow-ref"`: `id`, `workflowName`, `resolved`, `resolvedPath?`, `inlinedIR?`
|
|
506
|
+
- `"unknown"`: `id`, `reason`, `sourceCode?`
|
|
507
|
+
|
|
508
|
+
A JSON Schema for this structure is available at `schema/static-workflow-ir.schema.json` in this package.
|
|
509
|
+
|
|
510
|
+
## Requirements
|
|
511
|
+
|
|
512
|
+
- Node.js >= 22
|
|
513
|
+
- TypeScript project with `ts-morph` >= 27.0.2
|
|
514
|
+
|
|
515
|
+
## License
|
|
516
|
+
|
|
517
|
+
MIT
|