@elsium-ai/workflows 0.2.1 → 0.2.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.
Files changed (2) hide show
  1. package/README.md +473 -21
  2. package/package.json +3 -3
package/README.md CHANGED
@@ -13,33 +13,485 @@ npm install @elsium-ai/workflows @elsium-ai/core
13
13
 
14
14
  ## What's Inside
15
15
 
16
- - **Sequential Steps** Chain operations with automatic data passing
17
- - **Parallel Execution** Run independent steps concurrently
18
- - **Branching** Conditional workflow paths based on runtime results
19
- - **Retries** Per-step retry policies with backoff
16
+ | Category | Export | Kind |
17
+ | --- | --- | --- |
18
+ | **Types** | `StepConfig` | interface |
19
+ | | `StepContext` | interface |
20
+ | | `StepResult` | interface |
21
+ | | `StepStatus` | type alias |
22
+ | | `RetryConfig` | interface |
23
+ | | `WorkflowConfig` | interface |
24
+ | | `WorkflowResult` | interface |
25
+ | | `WorkflowStatus` | type alias |
26
+ | | `WorkflowRunOptions` | interface |
27
+ | **Steps** | `step` | function |
28
+ | | `executeStep` | function |
29
+ | **Workflow** | `defineWorkflow` | function |
30
+ | | `defineParallelWorkflow` | function |
31
+ | | `defineBranchWorkflow` | function |
32
+ | | `Workflow` | interface |
33
+ | | `ParallelWorkflowConfig` | interface |
34
+ | | `BranchConfig` | interface |
20
35
 
21
- ## Usage
36
+ ---
22
37
 
23
- ```typescript
38
+ ## Types
39
+
40
+ ### `StepStatus`
41
+
42
+ Union type representing the possible states of a step during execution.
43
+
44
+ ```ts
45
+ type StepStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped'
46
+ ```
47
+
48
+ ### `StepConfig<TInput, TOutput>`
49
+
50
+ Configuration object that defines a single step's behavior, including its handler, optional input validation, retry policy, conditional execution, fallback logic, and timeout.
51
+
52
+ ```ts
53
+ interface StepConfig<TInput = unknown, TOutput = unknown> {
54
+ name: string
55
+ input?: z.ZodType<TInput>
56
+ handler: (input: TInput, context: StepContext) => Promise<TOutput>
57
+ retry?: RetryConfig
58
+ condition?: (input: TInput, context: StepContext) => boolean
59
+ fallback?: (error: Error, input: TInput) => Promise<TOutput>
60
+ timeoutMs?: number
61
+ }
62
+ ```
63
+
64
+ | Field | Type | Description |
65
+ | --- | --- | --- |
66
+ | `name` | `string` | Unique identifier for the step within a workflow. |
67
+ | `input` | `z.ZodType<TInput>` | Optional Zod schema used to validate the step's input before the handler runs. |
68
+ | `handler` | `(input: TInput, context: StepContext) => Promise<TOutput>` | Async function that performs the step's work. |
69
+ | `retry` | `RetryConfig` | Optional retry policy applied when the handler throws. |
70
+ | `condition` | `(input: TInput, context: StepContext) => boolean` | Optional guard; when it returns `false` the step is skipped. |
71
+ | `fallback` | `(error: Error, input: TInput) => Promise<TOutput>` | Optional async function invoked when all retries are exhausted. |
72
+ | `timeoutMs` | `number` | Optional per-step timeout in milliseconds. |
73
+
74
+ ### `StepContext`
75
+
76
+ Runtime context passed to every step handler and condition function.
77
+
78
+ ```ts
79
+ interface StepContext {
80
+ workflowName: string
81
+ stepIndex: number
82
+ previousOutputs: Record<string, unknown>
83
+ signal?: AbortSignal
84
+ }
85
+ ```
86
+
87
+ | Field | Type | Description |
88
+ | --- | --- | --- |
89
+ | `workflowName` | `string` | Name of the workflow that owns this step. |
90
+ | `stepIndex` | `number` | Zero-based position of the step within the workflow. |
91
+ | `previousOutputs` | `Record<string, unknown>` | Map of step name to output for all previously completed steps. |
92
+ | `signal` | `AbortSignal` | Optional abort signal forwarded from `WorkflowRunOptions`. |
93
+
94
+ ### `StepResult<T>`
95
+
96
+ Outcome returned after a step finishes (or is skipped/fails).
97
+
98
+ ```ts
99
+ interface StepResult<T = unknown> {
100
+ name: string
101
+ status: StepStatus
102
+ data?: T
103
+ error?: string
104
+ durationMs: number
105
+ retryCount: number
106
+ }
107
+ ```
108
+
109
+ | Field | Type | Description |
110
+ | --- | --- | --- |
111
+ | `name` | `string` | Name of the step that produced this result. |
112
+ | `status` | `StepStatus` | Final status of the step. |
113
+ | `data` | `T` | Output data, present when `status` is `'completed'`. |
114
+ | `error` | `string` | Error message, present when `status` is `'failed'`. |
115
+ | `durationMs` | `number` | Wall-clock time spent on the step in milliseconds. |
116
+ | `retryCount` | `number` | Number of retries that were attempted before the final outcome. |
117
+
118
+ ### `RetryConfig`
119
+
120
+ Per-step retry policy with exponential backoff and jitter.
121
+
122
+ ```ts
123
+ interface RetryConfig {
124
+ maxRetries: number
125
+ baseDelayMs?: number
126
+ maxDelayMs?: number
127
+ shouldRetry?: (error: Error) => boolean
128
+ }
129
+ ```
130
+
131
+ | Field | Type | Default | Description |
132
+ | --- | --- | --- | --- |
133
+ | `maxRetries` | `number` | -- | Maximum number of retry attempts. |
134
+ | `baseDelayMs` | `number` | `1000` | Base delay for exponential backoff in milliseconds. |
135
+ | `maxDelayMs` | `number` | `30000` | Upper bound for the computed delay in milliseconds. |
136
+ | `shouldRetry` | `(error: Error) => boolean` | -- | Optional predicate; when omitted, all errors except non-retryable `ElsiumError` instances are retried. |
137
+
138
+ Backoff formula: `min(baseDelayMs * 2^(attempt-1), maxDelayMs) * random(0.5, 1.0)`.
139
+
140
+ ### `WorkflowStatus`
141
+
142
+ Union type representing the possible states of a workflow.
143
+
144
+ ```ts
145
+ type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed'
146
+ ```
147
+
148
+ ### `WorkflowConfig`
149
+
150
+ Configuration for a sequential workflow created via `defineWorkflow`.
151
+
152
+ ```ts
153
+ interface WorkflowConfig {
154
+ name: string
155
+ steps: StepConfig[]
156
+ onStepComplete?: (result: StepResult) => void | Promise<void>
157
+ onStepError?: (error: Error, stepName: string) => void | Promise<void>
158
+ onComplete?: (result: WorkflowResult) => void | Promise<void>
159
+ }
160
+ ```
161
+
162
+ | Field | Type | Description |
163
+ | --- | --- | --- |
164
+ | `name` | `string` | Identifier for the workflow. |
165
+ | `steps` | `StepConfig[]` | Ordered list of steps to execute sequentially. |
166
+ | `onStepComplete` | `(result: StepResult) => void \| Promise<void>` | Optional callback fired after each step completes. |
167
+ | `onStepError` | `(error: Error, stepName: string) => void \| Promise<void>` | Optional callback fired when a step fails. |
168
+ | `onComplete` | `(result: WorkflowResult) => void \| Promise<void>` | Optional callback fired when the workflow finishes (success or failure). |
169
+
170
+ ### `WorkflowResult`
171
+
172
+ Final output returned by `workflow.run()`.
173
+
174
+ ```ts
175
+ interface WorkflowResult {
176
+ name: string
177
+ status: WorkflowStatus
178
+ steps: StepResult[]
179
+ totalDurationMs: number
180
+ outputs: Record<string, unknown>
181
+ }
182
+ ```
183
+
184
+ | Field | Type | Description |
185
+ | --- | --- | --- |
186
+ | `name` | `string` | Name of the workflow. |
187
+ | `status` | `WorkflowStatus` | Overall status of the workflow. |
188
+ | `steps` | `StepResult[]` | Results for each step in execution order. |
189
+ | `totalDurationMs` | `number` | Total wall-clock time for the entire workflow in milliseconds. |
190
+ | `outputs` | `Record<string, unknown>` | Map of step name to output for every completed step. |
191
+
192
+ ### `WorkflowRunOptions`
193
+
194
+ Options passed to `workflow.run()`.
195
+
196
+ ```ts
197
+ interface WorkflowRunOptions {
198
+ signal?: AbortSignal
199
+ }
200
+ ```
201
+
202
+ | Field | Type | Description |
203
+ | --- | --- | --- |
204
+ | `signal` | `AbortSignal` | Optional abort signal; forwarded to each step's `StepContext`. |
205
+
206
+ ---
207
+
208
+ ## Steps
209
+
210
+ ### `step`
211
+
212
+ Factory function that creates a `StepConfig` by combining a name with the rest of the configuration, providing a concise shorthand for inline step definitions.
213
+
214
+ ```ts
215
+ function step<TInput, TOutput>(
216
+ name: string,
217
+ config: Omit<StepConfig<TInput, TOutput>, 'name'>,
218
+ ): StepConfig<TInput, TOutput>
219
+ ```
220
+
221
+ | Parameter | Type | Description |
222
+ | --- | --- | --- |
223
+ | `name` | `string` | Unique name for the step. |
224
+ | `config` | `Omit<StepConfig<TInput, TOutput>, 'name'>` | Step configuration without the `name` field. |
225
+
226
+ **Returns:** `StepConfig<TInput, TOutput>`
227
+
228
+ ```ts
229
+ import { step } from '@elsium-ai/workflows'
230
+ import { z } from 'zod'
231
+
232
+ const fetchPage = step('fetch-page', {
233
+ input: z.object({ url: z.string().url() }),
234
+ handler: async (input) => {
235
+ const res = await fetch(input.url)
236
+ return res.text()
237
+ },
238
+ retry: { maxRetries: 3, baseDelayMs: 500 },
239
+ timeoutMs: 10_000,
240
+ })
241
+ ```
242
+
243
+ ### `executeStep`
244
+
245
+ Runs a single step to completion, handling input validation, condition checks, retries with exponential backoff, timeout enforcement, and fallback execution.
246
+
247
+ ```ts
248
+ function executeStep<TInput, TOutput>(
249
+ stepConfig: StepConfig<TInput, TOutput>,
250
+ rawInput: unknown,
251
+ context: StepContext,
252
+ ): Promise<StepResult<TOutput>>
253
+ ```
254
+
255
+ | Parameter | Type | Description |
256
+ | --- | --- | --- |
257
+ | `stepConfig` | `StepConfig<TInput, TOutput>` | The step definition to execute. |
258
+ | `rawInput` | `unknown` | Raw input value; validated against `stepConfig.input` if a schema is provided. |
259
+ | `context` | `StepContext` | Runtime context for the step. |
260
+
261
+ **Returns:** `Promise<StepResult<TOutput>>`
262
+
263
+ The execution order is:
264
+ 1. Validate `rawInput` against the Zod schema (if provided). Return `'failed'` on validation error.
265
+ 2. Evaluate the `condition` guard (if provided). Return `'skipped'` when `false`.
266
+ 3. Run the `handler`, retrying on failure up to `retry.maxRetries` times with backoff.
267
+ 4. On final failure, invoke `fallback` (if provided). If the fallback also fails, return `'failed'`.
268
+
269
+ ```ts
270
+ import { step, executeStep } from '@elsium-ai/workflows'
271
+ import type { StepContext } from '@elsium-ai/workflows'
272
+
273
+ const myStep = step('greet', {
274
+ handler: async (input: { name: string }) => `Hello, ${input.name}!`,
275
+ })
276
+
277
+ const context: StepContext = {
278
+ workflowName: 'demo',
279
+ stepIndex: 0,
280
+ previousOutputs: {},
281
+ }
282
+
283
+ const result = await executeStep(myStep, { name: 'World' }, context)
284
+ console.log(result.data) // "Hello, World!"
285
+ ```
286
+
287
+ ---
288
+
289
+ ## Workflow
290
+
291
+ ### `Workflow`
292
+
293
+ Interface implemented by all workflow variants (sequential, parallel, and branch).
294
+
295
+ ```ts
296
+ interface Workflow {
297
+ readonly name: string
298
+ run(input: unknown, options?: WorkflowRunOptions): Promise<WorkflowResult>
299
+ }
300
+ ```
301
+
302
+ | Member | Type | Description |
303
+ | --- | --- | --- |
304
+ | `name` | `string` (readonly) | The workflow identifier. |
305
+ | `run` | `(input: unknown, options?: WorkflowRunOptions) => Promise<WorkflowResult>` | Executes the workflow with the given input and returns the aggregated result. |
306
+
307
+ ### `defineWorkflow`
308
+
309
+ Creates a sequential workflow that executes steps one after another, piping each step's output as the next step's input.
310
+
311
+ ```ts
312
+ function defineWorkflow(config: WorkflowConfig): Workflow
313
+ ```
314
+
315
+ | Parameter | Type | Description |
316
+ | --- | --- | --- |
317
+ | `config` | `WorkflowConfig` | Workflow configuration including steps and lifecycle callbacks. |
318
+
319
+ **Returns:** `Workflow`
320
+
321
+ When a step completes, its output is stored in `outputs` under the step's name and becomes the input for the next step. If any step fails, the workflow short-circuits and returns with `status: 'failed'`.
322
+
323
+ ```ts
24
324
  import { defineWorkflow, step } from '@elsium-ai/workflows'
25
325
 
26
- const pipeline = defineWorkflow('analyze', [
27
- step('fetch', async (input) => {
28
- return await fetchData(input.url)
29
- }),
30
- step('process', async (data) => {
31
- return await processData(data)
32
- }),
33
- step('summarize', async (processed) => {
34
- return await llm.complete({
35
- messages: [{ role: 'user', content: `Summarize: ${processed}` }],
36
- })
37
- }),
38
- ])
39
-
40
- const result = await pipeline.run({ url: 'https://example.com' })
326
+ const pipeline = defineWorkflow({
327
+ name: 'etl-pipeline',
328
+ steps: [
329
+ step('extract', {
330
+ handler: async (input: { source: string }) => {
331
+ return await extractData(input.source)
332
+ },
333
+ }),
334
+ step('transform', {
335
+ handler: async (rawData: RawData) => {
336
+ return transformData(rawData)
337
+ },
338
+ }),
339
+ step('load', {
340
+ handler: async (transformed: TransformedData) => {
341
+ await loadData(transformed)
342
+ return { loaded: true }
343
+ },
344
+ }),
345
+ ],
346
+ onStepComplete: (result) => {
347
+ console.log(`Step "${result.name}" finished in ${result.durationMs}ms`)
348
+ },
349
+ onComplete: (result) => {
350
+ console.log(`Workflow "${result.name}" ${result.status}`)
351
+ },
352
+ })
353
+
354
+ const result = await pipeline.run({ source: 'database' })
41
355
  ```
42
356
 
357
+ ### `ParallelWorkflowConfig`
358
+
359
+ Configuration for a parallel workflow created via `defineParallelWorkflow`.
360
+
361
+ ```ts
362
+ interface ParallelWorkflowConfig {
363
+ name: string
364
+ steps: StepConfig[]
365
+ onComplete?: (result: WorkflowResult) => void | Promise<void>
366
+ }
367
+ ```
368
+
369
+ | Field | Type | Description |
370
+ | --- | --- | --- |
371
+ | `name` | `string` | Identifier for the parallel workflow. |
372
+ | `steps` | `StepConfig[]` | Steps to execute concurrently; all receive the same input. |
373
+ | `onComplete` | `(result: WorkflowResult) => void \| Promise<void>` | Optional callback fired when all steps have settled. |
374
+
375
+ ### `defineParallelWorkflow`
376
+
377
+ Creates a parallel workflow that executes all steps concurrently using `Promise.all`, where every step receives the same input.
378
+
379
+ ```ts
380
+ function defineParallelWorkflow(config: ParallelWorkflowConfig): Workflow
381
+ ```
382
+
383
+ | Parameter | Type | Description |
384
+ | --- | --- | --- |
385
+ | `config` | `ParallelWorkflowConfig` | Parallel workflow configuration. |
386
+
387
+ **Returns:** `Workflow`
388
+
389
+ Each step's output is stored in `outputs` under its name. The workflow status is `'failed'` if any step fails, `'completed'` otherwise.
390
+
391
+ ```ts
392
+ import { defineParallelWorkflow, step } from '@elsium-ai/workflows'
393
+
394
+ const enrichment = defineParallelWorkflow({
395
+ name: 'enrich-profile',
396
+ steps: [
397
+ step('fetch-social', {
398
+ handler: async (input: { userId: string }) => {
399
+ return await fetchSocialProfile(input.userId)
400
+ },
401
+ }),
402
+ step('fetch-activity', {
403
+ handler: async (input: { userId: string }) => {
404
+ return await fetchActivityLog(input.userId)
405
+ },
406
+ }),
407
+ step('fetch-preferences', {
408
+ handler: async (input: { userId: string }) => {
409
+ return await fetchPreferences(input.userId)
410
+ },
411
+ }),
412
+ ],
413
+ })
414
+
415
+ const result = await enrichment.run({ userId: 'u_123' })
416
+ // result.outputs['fetch-social'], result.outputs['fetch-activity'], etc.
417
+ ```
418
+
419
+ ### `BranchConfig`
420
+
421
+ Defines a single branch in a branching workflow, pairing a condition with the workflow to execute when the condition is met.
422
+
423
+ ```ts
424
+ interface BranchConfig {
425
+ condition: (input: unknown) => boolean
426
+ workflow: Workflow
427
+ }
428
+ ```
429
+
430
+ | Field | Type | Description |
431
+ | --- | --- | --- |
432
+ | `condition` | `(input: unknown) => boolean` | Predicate evaluated against the workflow input. |
433
+ | `workflow` | `Workflow` | Workflow to run when `condition` returns `true`. |
434
+
435
+ ### `defineBranchWorkflow`
436
+
437
+ Creates a branching workflow that evaluates conditions in order and delegates to the first matching branch's workflow, with an optional fallback.
438
+
439
+ ```ts
440
+ function defineBranchWorkflow(
441
+ name: string,
442
+ branches: BranchConfig[],
443
+ fallback?: Workflow,
444
+ ): Workflow
445
+ ```
446
+
447
+ | Parameter | Type | Description |
448
+ | --- | --- | --- |
449
+ | `name` | `string` | Identifier for the branch workflow. |
450
+ | `branches` | `BranchConfig[]` | Ordered list of condition/workflow pairs; the first match wins. |
451
+ | `fallback` | `Workflow` | Optional workflow to run when no branch condition matches. |
452
+
453
+ **Returns:** `Workflow`
454
+
455
+ If no branch matches and no fallback is provided, the workflow returns immediately with `status: 'completed'`, an empty `steps` array, and empty `outputs`.
456
+
457
+ ```ts
458
+ import { defineBranchWorkflow, defineWorkflow, step } from '@elsium-ai/workflows'
459
+
460
+ const textWorkflow = defineWorkflow({
461
+ name: 'process-text',
462
+ steps: [
463
+ step('analyze-text', {
464
+ handler: async (input: { content: string }) => {
465
+ return await analyzeText(input.content)
466
+ },
467
+ }),
468
+ ],
469
+ })
470
+
471
+ const imageWorkflow = defineWorkflow({
472
+ name: 'process-image',
473
+ steps: [
474
+ step('analyze-image', {
475
+ handler: async (input: { content: string }) => {
476
+ return await analyzeImage(input.content)
477
+ },
478
+ }),
479
+ ],
480
+ })
481
+
482
+ const router = defineBranchWorkflow(
483
+ 'content-router',
484
+ [
485
+ { condition: (input: any) => input.type === 'text', workflow: textWorkflow },
486
+ { condition: (input: any) => input.type === 'image', workflow: imageWorkflow },
487
+ ],
488
+ )
489
+
490
+ const result = await router.run({ type: 'text', content: 'Hello world' })
491
+ ```
492
+
493
+ ---
494
+
43
495
  ## Part of ElsiumAI
44
496
 
45
497
  This package is the workflow layer of the [ElsiumAI](https://github.com/elsium-ai/elsium-ai) framework. See the [full documentation](https://github.com/elsium-ai/elsium-ai) for guides and examples.
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@elsium-ai/workflows",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Multi-step workflow pipelines and DAG execution for ElsiumAI",
5
5
  "license": "MIT",
6
6
  "author": "Eric Utrera <ebutrera9103@gmail.com>",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/elsium-ai/elsium-ai",
9
+ "url": "git+https://github.com/elsium-ai/elsium-ai.git",
10
10
  "directory": "packages/workflows"
11
11
  },
12
12
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "dev": "bun --watch src/index.ts"
27
27
  },
28
28
  "dependencies": {
29
- "@elsium-ai/core": "^0.2.1",
29
+ "@elsium-ai/core": "^0.2.3",
30
30
  "zod": "^3.24.0"
31
31
  },
32
32
  "devDependencies": {