@jagreehal/workflow 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +664 -350
- package/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +179 -1
- package/dist/core.d.ts +179 -1
- package/dist/core.js +1 -1
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/visualize.cjs +6 -6
- package/dist/visualize.cjs.map +1 -1
- package/dist/visualize.js +6 -6
- package/dist/visualize.js.map +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.js +1 -1
- package/dist/workflow.js.map +1 -1
- package/docs/coming-from-neverthrow.md +920 -0
- package/docs/visualize-examples.md +330 -0
- package/package.json +2 -1
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
# Visualization Examples
|
|
2
|
+
|
|
3
|
+
This document provides comprehensive examples of using the `@jagreehal/workflow/visualize` module.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
### Simple Sequential Workflow
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { createVisualizer } from '@jagreehal/workflow/visualize';
|
|
11
|
+
import { createWorkflow } from '@jagreehal/workflow';
|
|
12
|
+
|
|
13
|
+
const viz = createVisualizer({ workflowName: 'User Data Flow' });
|
|
14
|
+
const workflow = createWorkflow({ fetchUser, fetchPosts }, {
|
|
15
|
+
onEvent: viz.handleEvent,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await workflow(async (step) => {
|
|
19
|
+
const user = await step(() => fetchUser('1'), { name: 'Fetch user' });
|
|
20
|
+
const posts = await step(() => fetchPosts(user.id), { name: 'Fetch posts' });
|
|
21
|
+
return { user, posts };
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
console.log(viz.render());
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Output:
|
|
28
|
+
```
|
|
29
|
+
┌── User Data Flow ───────────────────────────────────────────┐
|
|
30
|
+
│ │
|
|
31
|
+
│ ✓ Fetch user [10ms] │
|
|
32
|
+
│ ✓ Fetch posts [15ms] │
|
|
33
|
+
│ │
|
|
34
|
+
│ Completed in 25ms │
|
|
35
|
+
│ │
|
|
36
|
+
└─────────────────────────────────────────────────────────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Parallel Operations
|
|
40
|
+
|
|
41
|
+
### Visualizing Individual Parallel Steps
|
|
42
|
+
|
|
43
|
+
To see individual steps in parallel operations, wrap each operation with `step()`:
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const viz = createVisualizer({ workflowName: 'Parallel Fetch' });
|
|
47
|
+
const workflow = createWorkflow({ fetchUser, fetchPosts }, {
|
|
48
|
+
onEvent: viz.handleEvent,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await workflow(async (step) => {
|
|
52
|
+
// Wrap each operation with step() to track individually
|
|
53
|
+
const [user, posts] = await step.parallel('Fetch all data', async () => {
|
|
54
|
+
const userResult = await step(() => fetchUser('1'), { name: 'Fetch user' });
|
|
55
|
+
const postsResult = await step(() => fetchPosts('1'), { name: 'Fetch posts' });
|
|
56
|
+
return [userResult, postsResult];
|
|
57
|
+
});
|
|
58
|
+
return { user, posts };
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log(viz.render());
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Output:
|
|
65
|
+
```
|
|
66
|
+
┌── Parallel Fetch ───────────────────────────────────────────┐
|
|
67
|
+
│ │
|
|
68
|
+
│ ├┬─ ✓ Fetch all data │
|
|
69
|
+
│ │ ├ ✓ Fetch user [10ms] │
|
|
70
|
+
│ │ └ ✓ Fetch posts [15ms] │
|
|
71
|
+
│ └── [15ms] │
|
|
72
|
+
│ │
|
|
73
|
+
│ Completed in 15ms │
|
|
74
|
+
│ │
|
|
75
|
+
└─────────────────────────────────────────────────────────────┘
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Without Individual Step Tracking
|
|
79
|
+
|
|
80
|
+
If you use `allAsync()` directly without wrapping operations:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
await workflow(async (step) => {
|
|
84
|
+
const [user, posts] = await step.parallel('Fetch all data', () =>
|
|
85
|
+
allAsync([fetchUser('1'), fetchPosts('1')])
|
|
86
|
+
);
|
|
87
|
+
return { user, posts };
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The visualization will show:
|
|
92
|
+
```
|
|
93
|
+
┌── Parallel Fetch ───────────────────────────────────────────┐
|
|
94
|
+
│ │
|
|
95
|
+
│ ├┬─ ✓ Fetch all data │
|
|
96
|
+
│ │ (operations not individually tracked) │
|
|
97
|
+
│ │ (wrap each operation with step() to see individual steps)
|
|
98
|
+
│ └── [15ms] │
|
|
99
|
+
│ │
|
|
100
|
+
└─────────────────────────────────────────────────────────────┘
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Decision Tracking
|
|
104
|
+
|
|
105
|
+
### If/Else Decisions
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { trackIf } from '@jagreehal/workflow/visualize';
|
|
109
|
+
|
|
110
|
+
const collector = createEventCollector({ workflowName: 'Role Check' });
|
|
111
|
+
const workflow = createWorkflow({ fetchUser, processAdmin, processUser }, {
|
|
112
|
+
onEvent: collector.handleEvent,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await workflow(async (step, deps) => {
|
|
116
|
+
const user = await step(() => deps.fetchUser('1'), { name: 'Fetch user' });
|
|
117
|
+
|
|
118
|
+
const decision = trackIf('check-role', user.role === 'admin', {
|
|
119
|
+
condition: "user.role === 'admin'",
|
|
120
|
+
value: user.role,
|
|
121
|
+
emit: collector.handleDecisionEvent,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
if (decision.condition) {
|
|
125
|
+
decision.then();
|
|
126
|
+
await step(() => deps.processAdmin(user), { name: 'Process admin' });
|
|
127
|
+
} else {
|
|
128
|
+
decision.else();
|
|
129
|
+
await step(() => deps.processUser(user), { name: 'Process user' });
|
|
130
|
+
}
|
|
131
|
+
decision.end();
|
|
132
|
+
|
|
133
|
+
return user;
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
console.log(collector.visualize());
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Switch Decisions
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { trackSwitch } from '@jagreehal/workflow/visualize';
|
|
143
|
+
|
|
144
|
+
const decision = trackSwitch('process-by-role', user.role, {
|
|
145
|
+
condition: 'switch(user.role)',
|
|
146
|
+
value: user.role,
|
|
147
|
+
emit: collector.handleDecisionEvent,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
switch (user.role) {
|
|
151
|
+
case 'admin':
|
|
152
|
+
decision.case('admin', true);
|
|
153
|
+
await step(() => processAdmin(user), { name: 'Process admin' });
|
|
154
|
+
break;
|
|
155
|
+
case 'user':
|
|
156
|
+
decision.case('user', true);
|
|
157
|
+
await step(() => processUser(user), { name: 'Process user' });
|
|
158
|
+
break;
|
|
159
|
+
default:
|
|
160
|
+
decision.default(false);
|
|
161
|
+
}
|
|
162
|
+
decision.end();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Output Formats
|
|
166
|
+
|
|
167
|
+
### ASCII (Default)
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const output = viz.render(); // or viz.renderAs('ascii')
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Best for:
|
|
174
|
+
- Terminal output
|
|
175
|
+
- CLI tools
|
|
176
|
+
- Logs
|
|
177
|
+
- Incident runbooks
|
|
178
|
+
|
|
179
|
+
### Mermaid
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const mermaid = viz.renderAs('mermaid');
|
|
183
|
+
console.log(mermaid);
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Best for:
|
|
187
|
+
- Markdown documentation
|
|
188
|
+
- GitHub README files
|
|
189
|
+
- Documentation sites
|
|
190
|
+
- PR descriptions
|
|
191
|
+
|
|
192
|
+
Example Mermaid output:
|
|
193
|
+
```mermaid
|
|
194
|
+
flowchart TD
|
|
195
|
+
start((Start))
|
|
196
|
+
step_user[Fetch user 10ms]:::success
|
|
197
|
+
start --> step_user
|
|
198
|
+
step_posts[Fetch posts 15ms]:::success
|
|
199
|
+
step_user --> step_posts
|
|
200
|
+
finish((Done)):::success
|
|
201
|
+
step_posts --> finish
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### JSON
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const json = viz.renderAs('json');
|
|
208
|
+
const ir = JSON.parse(json);
|
|
209
|
+
// Programmatically inspect the IR
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Best for:
|
|
213
|
+
- Programmatic analysis
|
|
214
|
+
- Custom renderers
|
|
215
|
+
- Debugging
|
|
216
|
+
- Integration with other tools
|
|
217
|
+
|
|
218
|
+
## Post-Execution Visualization
|
|
219
|
+
|
|
220
|
+
Collect events during execution and visualize later:
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
import { createEventCollector } from '@jagreehal/workflow/visualize';
|
|
224
|
+
|
|
225
|
+
const collector = createEventCollector({ workflowName: 'My Workflow' });
|
|
226
|
+
const workflow = createWorkflow(deps, {
|
|
227
|
+
onEvent: collector.handleEvent,
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await workflow(async (step) => {
|
|
231
|
+
// ... workflow execution
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Later, visualize collected events
|
|
235
|
+
console.log(collector.visualize());
|
|
236
|
+
console.log(collector.visualizeAs('mermaid'));
|
|
237
|
+
|
|
238
|
+
// Or access raw events
|
|
239
|
+
const events = collector.getEvents();
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## Options
|
|
243
|
+
|
|
244
|
+
### Customize Display
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const viz = createVisualizer({
|
|
248
|
+
workflowName: 'My Workflow',
|
|
249
|
+
showTimings: true, // Show duration for each step
|
|
250
|
+
showKeys: true, // Show cache keys
|
|
251
|
+
detectParallel: true, // Auto-detect parallel execution
|
|
252
|
+
colors: { // Customize colors
|
|
253
|
+
success: '\x1b[32m',
|
|
254
|
+
error: '\x1b[31m',
|
|
255
|
+
// ...
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Disable Parallel Detection
|
|
261
|
+
|
|
262
|
+
If you want to see steps in their original order without grouping:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
const viz = createVisualizer({
|
|
266
|
+
workflowName: 'Sequential View',
|
|
267
|
+
detectParallel: false, // Disable heuristic parallel detection
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Best Practices
|
|
272
|
+
|
|
273
|
+
1. **Always provide step names** for better visualization:
|
|
274
|
+
```typescript
|
|
275
|
+
await step(() => fetchUser(id), { name: 'Fetch user' });
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
2. **Use keys for cacheable steps** to enable visualization of cache hits:
|
|
279
|
+
```typescript
|
|
280
|
+
await step(() => fetchUser(id), {
|
|
281
|
+
name: 'Fetch user',
|
|
282
|
+
key: `user:${id}`
|
|
283
|
+
});
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
3. **Track decisions explicitly** for conditional logic:
|
|
287
|
+
```typescript
|
|
288
|
+
const decision = trackIf('check-condition', condition, {
|
|
289
|
+
emit: collector.handleDecisionEvent,
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
4. **Wrap parallel operations** with `step()` to see individual steps:
|
|
294
|
+
```typescript
|
|
295
|
+
await step.parallel('Parallel fetch', async () => {
|
|
296
|
+
const a = await step(() => fetchA(), { name: 'Fetch A' });
|
|
297
|
+
const b = await step(() => fetchB(), { name: 'Fetch B' });
|
|
298
|
+
return [a, b];
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Troubleshooting
|
|
303
|
+
|
|
304
|
+
### Empty Parallel/Race Scopes
|
|
305
|
+
|
|
306
|
+
If you see "(operations not individually tracked)" in parallel/race scopes, wrap each operation with `step()` to track them individually.
|
|
307
|
+
|
|
308
|
+
### Sequential Steps Grouped as Parallel
|
|
309
|
+
|
|
310
|
+
If sequential steps are incorrectly grouped as parallel, disable parallel detection:
|
|
311
|
+
```typescript
|
|
312
|
+
const viz = createVisualizer({ detectParallel: false });
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Or adjust the parallel detection options:
|
|
316
|
+
```typescript
|
|
317
|
+
const viz = createVisualizer({
|
|
318
|
+
detectParallel: true,
|
|
319
|
+
parallelDetection: {
|
|
320
|
+
minOverlapMs: 5, // Require 5ms overlap
|
|
321
|
+
maxGapMs: 0, // No gap tolerance
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Mermaid Parse Errors
|
|
327
|
+
|
|
328
|
+
The Mermaid renderer automatically escapes special characters. If you encounter parse errors, ensure step names and conditions don't contain unescaped brackets, parentheses, or quotes.
|
|
329
|
+
|
|
330
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jagreehal/workflow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed async workflows with automatic error inference. Build type-safe workflows with Result types, step caching, resume state, and human-in-the-loop support.",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -116,6 +116,7 @@
|
|
|
116
116
|
"tsd": "tsd --files 'src/**/*.test-d.ts'",
|
|
117
117
|
"clean": "rm -rf dist lib",
|
|
118
118
|
"prebuild": "pnpm clean",
|
|
119
|
+
"quality": "pnpm build:tsc && pnpm test && pnpm lint",
|
|
119
120
|
"changeset": "changeset",
|
|
120
121
|
"version-packages": "changeset version",
|
|
121
122
|
"release": "pnpm build && changeset publish"
|