@wooksjs/event-wf 0.6.6 → 0.7.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 +7 -65
- package/dist/index.cjs +119 -59
- package/dist/index.d.ts +74 -27
- package/dist/index.mjs +102 -55
- package/package.json +6 -6
- package/skills/wooksjs-event-wf/SKILL.md +7 -7
- package/skills/wooksjs-event-wf/core.md +133 -67
- package/skills/wooksjs-event-wf/event-core.md +251 -372
- package/skills/wooksjs-event-wf/workflows.md +39 -48
|
@@ -15,7 +15,9 @@ const app = createWfApp<{ result: number }>()
|
|
|
15
15
|
|
|
16
16
|
// Function handler
|
|
17
17
|
app.step('double', {
|
|
18
|
-
handler: (ctx) => {
|
|
18
|
+
handler: (ctx) => {
|
|
19
|
+
ctx.result *= 2
|
|
20
|
+
},
|
|
19
21
|
})
|
|
20
22
|
|
|
21
23
|
// String handler (storable, runs in restricted env)
|
|
@@ -26,6 +28,7 @@ app.step('add', {
|
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
**Parameters:**
|
|
31
|
+
|
|
29
32
|
- `id` — Step identifier (used in flow schemas to reference this step). Supports router syntax: `'add/:n'`, `'process/*'`, etc.
|
|
30
33
|
- `opts.handler` — Either a function `(ctx: T, input?: I) => void | IR` or a JavaScript string.
|
|
31
34
|
- `opts.input` — Optional: input type description (string). When present and no input is provided at runtime, the workflow pauses to request input.
|
|
@@ -90,14 +93,11 @@ String handlers are useful when workflow definitions are stored in a database
|
|
|
90
93
|
Registers a flow (workflow schema) — an ordered sequence of steps:
|
|
91
94
|
|
|
92
95
|
```ts
|
|
93
|
-
app.flow('calculate', [
|
|
94
|
-
{ id: 'add', input: 5 },
|
|
95
|
-
{ id: 'add', input: 2 },
|
|
96
|
-
{ id: 'double' },
|
|
97
|
-
])
|
|
96
|
+
app.flow('calculate', [{ id: 'add', input: 5 }, { id: 'add', input: 2 }, { id: 'double' }])
|
|
98
97
|
```
|
|
99
98
|
|
|
100
99
|
**Parameters:**
|
|
100
|
+
|
|
101
101
|
- `id` — Flow identifier. Supports router syntax (e.g., `'process/:type'`, `'batch/*'`).
|
|
102
102
|
- `schema` — Array of step references, conditions, and loops (see Schema Syntax below).
|
|
103
103
|
- `prefix` — Optional prefix prepended to step IDs during resolution.
|
|
@@ -135,11 +135,7 @@ app.flow('f2', [
|
|
|
135
135
|
])
|
|
136
136
|
|
|
137
137
|
// 3. Mixed
|
|
138
|
-
app.flow('f3', [
|
|
139
|
-
'step1',
|
|
140
|
-
{ id: 'add', input: 5 },
|
|
141
|
-
'step2',
|
|
142
|
-
])
|
|
138
|
+
app.flow('f3', ['step1', { id: 'add', input: 5 }, 'step2'])
|
|
143
139
|
```
|
|
144
140
|
|
|
145
141
|
### Conditional execution
|
|
@@ -178,13 +174,14 @@ app.flow('retry-flow', [
|
|
|
178
174
|
while: 'attempts < 5 && !success',
|
|
179
175
|
steps: [
|
|
180
176
|
{ id: 'attempt' },
|
|
181
|
-
{ break: 'success' },
|
|
177
|
+
{ break: 'success' }, // break when success is truthy
|
|
182
178
|
],
|
|
183
179
|
},
|
|
184
180
|
])
|
|
185
181
|
```
|
|
186
182
|
|
|
187
183
|
Loop constructs:
|
|
184
|
+
|
|
188
185
|
- `while` — Condition string evaluated before each iteration
|
|
189
186
|
- `break` — Condition string; if truthy, exits the loop
|
|
190
187
|
- `continue` — Condition string; if truthy, skips to next iteration
|
|
@@ -295,12 +292,12 @@ app.step('my-step', {
|
|
|
295
292
|
handler: () => {
|
|
296
293
|
const { ctx, input, schemaId, stepId, indexes, resume } = useWfState()
|
|
297
294
|
|
|
298
|
-
ctx<MyContext>()
|
|
299
|
-
input<MyInput>()
|
|
300
|
-
schemaId
|
|
301
|
-
stepId()
|
|
302
|
-
indexes()
|
|
303
|
-
resume
|
|
295
|
+
ctx<MyContext>() // the workflow context object (type T)
|
|
296
|
+
input<MyInput>() // the current step's input (or undefined)
|
|
297
|
+
schemaId // the flow ID being executed
|
|
298
|
+
stepId() // the current step ID
|
|
299
|
+
indexes() // position in schema (for resume tracking)
|
|
300
|
+
resume // boolean: true if this is a resumed execution
|
|
304
301
|
},
|
|
305
302
|
})
|
|
306
303
|
```
|
|
@@ -314,7 +311,7 @@ app.step('transform', {
|
|
|
314
311
|
handler: () => {
|
|
315
312
|
const { ctx } = useWfState()
|
|
316
313
|
const context = ctx<{ items: string[]; processed: boolean }>()
|
|
317
|
-
context.items = context.items.map(s => s.toUpperCase())
|
|
314
|
+
context.items = context.items.map((s) => s.toUpperCase())
|
|
318
315
|
context.processed = true
|
|
319
316
|
},
|
|
320
317
|
})
|
|
@@ -361,7 +358,7 @@ When a step declares an `input` type but no input is provided in the schema, the
|
|
|
361
358
|
|
|
362
359
|
```ts
|
|
363
360
|
app.step('get-email', {
|
|
364
|
-
input: 'string',
|
|
361
|
+
input: 'string', // declares expected input type
|
|
365
362
|
handler: 'ctx.email = input',
|
|
366
363
|
})
|
|
367
364
|
|
|
@@ -370,7 +367,7 @@ app.step('send-welcome', {
|
|
|
370
367
|
})
|
|
371
368
|
|
|
372
369
|
app.flow('onboarding', [
|
|
373
|
-
{ id: 'get-email' },
|
|
370
|
+
{ id: 'get-email' }, // no input provided → workflow pauses
|
|
374
371
|
{ id: 'send-welcome' },
|
|
375
372
|
])
|
|
376
373
|
|
|
@@ -380,7 +377,7 @@ const output = await app.start('onboarding', {})
|
|
|
380
377
|
// output.inputRequired.type === 'string'
|
|
381
378
|
|
|
382
379
|
// Resume with user's email
|
|
383
|
-
const final = await app.resume(output.state, 'user@example.com')
|
|
380
|
+
const final = await app.resume(output.state, { input: 'user@example.com' })
|
|
384
381
|
// final.finished === true
|
|
385
382
|
```
|
|
386
383
|
|
|
@@ -390,7 +387,7 @@ When input is provided in the schema, the step executes immediately:
|
|
|
390
387
|
|
|
391
388
|
```ts
|
|
392
389
|
app.flow('auto-onboarding', [
|
|
393
|
-
{ id: 'get-email', input: 'default@example.com' },
|
|
390
|
+
{ id: 'get-email', input: 'default@example.com' }, // input provided → no pause
|
|
394
391
|
{ id: 'send-welcome' },
|
|
395
392
|
])
|
|
396
393
|
```
|
|
@@ -405,7 +402,7 @@ await db.save('workflow:123', JSON.stringify(output.state))
|
|
|
405
402
|
|
|
406
403
|
// Load and resume
|
|
407
404
|
const saved = JSON.parse(await db.load('workflow:123'))
|
|
408
|
-
const result = await app.resume(saved, userInput)
|
|
405
|
+
const result = await app.resume(saved, { input: userInput })
|
|
409
406
|
```
|
|
410
407
|
|
|
411
408
|
## StepRetriableError
|
|
@@ -432,7 +429,7 @@ try {
|
|
|
432
429
|
if (error instanceof StepRetriableError) {
|
|
433
430
|
// Wait and retry
|
|
434
431
|
await sleep(5000)
|
|
435
|
-
await app.resume(error.state, retryInput)
|
|
432
|
+
await app.resume(error.state, { input: retryInput })
|
|
436
433
|
}
|
|
437
434
|
}
|
|
438
435
|
```
|
|
@@ -453,11 +450,7 @@ app.step('add/:n', {
|
|
|
453
450
|
},
|
|
454
451
|
})
|
|
455
452
|
|
|
456
|
-
app.flow('calculate', [
|
|
457
|
-
{ id: 'add', input: 10 },
|
|
458
|
-
{ id: 'multiply', input: 2 },
|
|
459
|
-
'add/5',
|
|
460
|
-
])
|
|
453
|
+
app.flow('calculate', [{ id: 'add', input: 10 }, { id: 'multiply', input: 2 }, 'add/5'])
|
|
461
454
|
|
|
462
455
|
const output = await app.start('calculate', { result: 0 })
|
|
463
456
|
// result: (0 + 10) * 2 + 5 = 25
|
|
@@ -474,20 +467,26 @@ const app = createWfApp<{
|
|
|
474
467
|
}>()
|
|
475
468
|
|
|
476
469
|
app.step('validate', {
|
|
477
|
-
handler: (ctx) => {
|
|
470
|
+
handler: (ctx) => {
|
|
471
|
+
ctx.validated = isValid(ctx.data)
|
|
472
|
+
},
|
|
478
473
|
})
|
|
479
474
|
|
|
480
475
|
app.step('to-json', {
|
|
481
|
-
handler: (ctx) => {
|
|
476
|
+
handler: (ctx) => {
|
|
477
|
+
ctx.output = JSON.stringify(ctx.data)
|
|
478
|
+
},
|
|
482
479
|
})
|
|
483
480
|
|
|
484
481
|
app.step('to-csv', {
|
|
485
|
-
handler: (ctx) => {
|
|
482
|
+
handler: (ctx) => {
|
|
483
|
+
ctx.output = toCsv(ctx.data)
|
|
484
|
+
},
|
|
486
485
|
})
|
|
487
486
|
|
|
488
487
|
app.flow('export', [
|
|
489
488
|
{ id: 'validate' },
|
|
490
|
-
{ condition: '!validated', steps: [] },
|
|
489
|
+
{ condition: '!validated', steps: [] }, // early exit if invalid
|
|
491
490
|
{ condition: 'format === "json"', steps: ['to-json'] },
|
|
492
491
|
{ condition: 'format === "csv"', steps: ['to-csv'] },
|
|
493
492
|
])
|
|
@@ -511,18 +510,13 @@ app.step('confirm', {
|
|
|
511
510
|
},
|
|
512
511
|
})
|
|
513
512
|
|
|
514
|
-
app.flow('signup', [
|
|
515
|
-
{ id: 'get-name' },
|
|
516
|
-
{ id: 'get-email' },
|
|
517
|
-
{ id: 'get-plan' },
|
|
518
|
-
{ id: 'confirm' },
|
|
519
|
-
])
|
|
513
|
+
app.flow('signup', [{ id: 'get-name' }, { id: 'get-email' }, { id: 'get-plan' }, { id: 'confirm' }])
|
|
520
514
|
|
|
521
515
|
// Each step pauses for user input
|
|
522
516
|
let output = await app.start('signup', {})
|
|
523
|
-
output = await app.resume(output.state, 'Alice')
|
|
524
|
-
output = await app.resume(output.state, 'a@b.com')
|
|
525
|
-
output = await app.resume(output.state, 'pro')
|
|
517
|
+
output = await app.resume(output.state, { input: 'Alice' }) // name
|
|
518
|
+
output = await app.resume(output.state, { input: 'a@b.com' }) // email
|
|
519
|
+
output = await app.resume(output.state, { input: 'pro' }) // plan
|
|
526
520
|
// output.finished === true
|
|
527
521
|
```
|
|
528
522
|
|
|
@@ -546,10 +540,7 @@ app.step('attempt', {
|
|
|
546
540
|
app.flow('resilient-fetch', [
|
|
547
541
|
{
|
|
548
542
|
while: 'attempts < 5 && !success',
|
|
549
|
-
steps: [
|
|
550
|
-
{ id: 'attempt' },
|
|
551
|
-
{ break: 'success' },
|
|
552
|
-
],
|
|
543
|
+
steps: [{ id: 'attempt' }, { break: 'success' }],
|
|
553
544
|
},
|
|
554
545
|
])
|
|
555
546
|
|
|
@@ -572,7 +563,7 @@ const output = await app.start('resilient-fetch', {
|
|
|
572
563
|
## Gotchas
|
|
573
564
|
|
|
574
565
|
- **Conditions access context properties directly** — The condition `'result > 10'` checks `context.result`, not a local variable. The entire context is the evaluation scope.
|
|
575
|
-
- **Input is only for the first step on start** — When calling `app.start(id, ctx, input)`, the `input` is consumed by the first step. After that, `input` is cleared. Subsequent steps only get input if the workflow pauses and resumes.
|
|
566
|
+
- **Input is only for the first step on start** — When calling `app.start(id, ctx, { input })`, the `input` is consumed by the first step. After that, `input` is cleared. Subsequent steps only get input if the workflow pauses and resumes.
|
|
576
567
|
- **String handlers are sandboxed** — No access to Node.js APIs, `require`, `import`, `console`, etc. Only `ctx` and `input` are available.
|
|
577
568
|
- **Step IDs are router paths** — A step ID `'process/items'` is treated as two path segments. Use `'process-items'` if you want a flat ID.
|
|
578
569
|
- **Flow `init` runs in context** — The init function has access to composables (`useWfState()`, `useRouteParams()`, etc.) because it runs inside the async event context.
|