@yigitahmetsahin/workflow-ts 2.0.0 → 2.1.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 +116 -4
- package/dist/index.cjs +23 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +47 -5
- package/dist/index.d.ts +47 -5
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@ A simple, extensible TypeScript workflow engine supporting serial and parallel w
|
|
|
11
11
|
|
|
12
12
|
- 🔄 **Serial & Parallel Execution** - Chain work items sequentially or run them concurrently
|
|
13
13
|
- 🎯 **Full Type Inference** - Work names and result types are automatically inferred
|
|
14
|
+
- 🧩 **Standalone Work Definitions** - Define works as reusable `Work` instances
|
|
14
15
|
- ⏭️ **Conditional Execution** - Skip work items based on runtime conditions
|
|
15
16
|
- 🛡️ **Error Handling** - Built-in error callbacks and workflow failure states
|
|
16
17
|
- 📊 **Execution Tracking** - Duration tracking for individual works and total workflow
|
|
@@ -33,8 +34,9 @@ pnpm add @yigitahmetsahin/workflow-ts
|
|
|
33
34
|
## Quick Start
|
|
34
35
|
|
|
35
36
|
```typescript
|
|
36
|
-
import { Workflow, WorkflowStatus } from '@yigitahmetsahin/workflow-ts';
|
|
37
|
+
import { Workflow, Work, WorkflowStatus } from '@yigitahmetsahin/workflow-ts';
|
|
37
38
|
|
|
39
|
+
// Option 1: Define works inline
|
|
38
40
|
const workflow = new Workflow<{ userId: string }>()
|
|
39
41
|
.serial({
|
|
40
42
|
name: 'validate',
|
|
@@ -54,17 +56,31 @@ const workflow = new Workflow<{ userId: string }>()
|
|
|
54
56
|
name: 'process',
|
|
55
57
|
execute: async (ctx) => {
|
|
56
58
|
// ✅ Types are automatically inferred!
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
+
// workResults.get() returns IWorkResult with status, result, duration
|
|
60
|
+
const orders = ctx.workResults.get('fetchOrders').result; // { id: number }[]
|
|
61
|
+
const profile = ctx.workResults.get('fetchProfile').result; // { name: string; email: string }
|
|
59
62
|
return { orderCount: orders?.length ?? 0, userName: profile?.name };
|
|
60
63
|
},
|
|
61
64
|
});
|
|
62
65
|
|
|
66
|
+
// Option 2: Define works as reusable Work instances
|
|
67
|
+
const validateUser = new Work({
|
|
68
|
+
name: 'validate',
|
|
69
|
+
execute: async (ctx) => ctx.data.userId.length > 0,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const fetchOrders = new Work({
|
|
73
|
+
name: 'fetchOrders',
|
|
74
|
+
execute: async (ctx) => [{ id: 1 }, { id: 2 }],
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const workflow2 = new Workflow<{ userId: string }>().serial(validateUser).parallel([fetchOrders]);
|
|
78
|
+
|
|
63
79
|
const result = await workflow.run({ userId: 'user-123' });
|
|
64
80
|
|
|
65
81
|
if (result.status === WorkflowStatus.COMPLETED) {
|
|
66
82
|
console.log('Workflow completed in', result.totalDuration, 'ms');
|
|
67
|
-
console.log('Final result:', result.context.workResults.get('process'));
|
|
83
|
+
console.log('Final result:', result.context.workResults.get('process').result);
|
|
68
84
|
}
|
|
69
85
|
```
|
|
70
86
|
|
|
@@ -109,6 +125,62 @@ workflow.parallel([
|
|
|
109
125
|
]);
|
|
110
126
|
```
|
|
111
127
|
|
|
128
|
+
### `Work` Class
|
|
129
|
+
|
|
130
|
+
Define standalone, reusable work units using the `Work` class:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { Work, Workflow } from '@yigitahmetsahin/workflow-ts';
|
|
134
|
+
|
|
135
|
+
// Define works as standalone units
|
|
136
|
+
const fetchUser = new Work({
|
|
137
|
+
name: 'fetchUser',
|
|
138
|
+
execute: async (ctx) => {
|
|
139
|
+
const response = await fetch(`/api/users/${ctx.data.userId}`);
|
|
140
|
+
return response.json();
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const fetchOrders = new Work({
|
|
145
|
+
name: 'fetchOrders',
|
|
146
|
+
execute: async (ctx) => {
|
|
147
|
+
const response = await fetch(`/api/orders?userId=${ctx.data.userId}`);
|
|
148
|
+
return response.json();
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Use them in workflows
|
|
153
|
+
const workflow = new Workflow<{ userId: string }>()
|
|
154
|
+
.serial(fetchUser)
|
|
155
|
+
.parallel([fetchOrders, anotherWork]);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Works can be mixed with inline definitions:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
workflow
|
|
162
|
+
.serial(fetchUser) // Work instance
|
|
163
|
+
.parallel([
|
|
164
|
+
fetchOrders, // Work instance
|
|
165
|
+
{
|
|
166
|
+
// Inline definition
|
|
167
|
+
name: 'fetchProfile',
|
|
168
|
+
execute: async (ctx) => ({ name: 'John' }),
|
|
169
|
+
},
|
|
170
|
+
]);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The `Work` class supports all the same options as inline definitions:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const conditionalWork = new Work({
|
|
177
|
+
name: 'conditionalTask',
|
|
178
|
+
execute: async (ctx) => 'result',
|
|
179
|
+
shouldRun: (ctx) => ctx.data.enabled, // Optional condition
|
|
180
|
+
onError: (error, ctx) => console.error(error), // Optional error handler
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
112
184
|
### `.run(initialData)`
|
|
113
185
|
|
|
114
186
|
Execute the workflow with initial data.
|
|
@@ -130,8 +202,37 @@ interface IWorkflowResult {
|
|
|
130
202
|
totalDuration: number; // Total execution time in ms
|
|
131
203
|
error?: Error; // Error if workflow failed
|
|
132
204
|
}
|
|
205
|
+
|
|
206
|
+
// Each work result contains execution details
|
|
207
|
+
interface IWorkResult<T> {
|
|
208
|
+
status: WorkStatus; // 'completed' | 'failed' | 'skipped'
|
|
209
|
+
result?: T; // The return value from execute()
|
|
210
|
+
error?: Error; // Error if work failed
|
|
211
|
+
duration: number; // Execution time in ms
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Accessing Work Results
|
|
216
|
+
|
|
217
|
+
`ctx.workResults.get()` returns an `IWorkResult` object, not the raw value:
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Get the full work result with metadata
|
|
221
|
+
const workResult = ctx.workResults.get('fetchUser');
|
|
222
|
+
console.log(workResult.status); // 'completed' | 'failed' | 'skipped'
|
|
223
|
+
console.log(workResult.duration); // execution time in ms
|
|
224
|
+
|
|
225
|
+
// Get just the return value
|
|
226
|
+
const user = ctx.workResults.get('fetchUser').result;
|
|
227
|
+
|
|
228
|
+
// Check status before accessing result
|
|
229
|
+
if (workResult.status === WorkStatus.COMPLETED) {
|
|
230
|
+
console.log('User:', workResult.result);
|
|
231
|
+
}
|
|
133
232
|
```
|
|
134
233
|
|
|
234
|
+
> **Note:** `workResults.get()` throws an error if called for a work that hasn't executed yet. Use `workResults.has()` to check if a result exists.
|
|
235
|
+
|
|
135
236
|
## Conditional Execution
|
|
136
237
|
|
|
137
238
|
Skip works based on runtime conditions:
|
|
@@ -147,6 +248,17 @@ workflow.serial({
|
|
|
147
248
|
});
|
|
148
249
|
```
|
|
149
250
|
|
|
251
|
+
Skipped works are still accessible via `workResults.get()` with `status: 'skipped'`:
|
|
252
|
+
|
|
253
|
+
```typescript
|
|
254
|
+
const emailResult = ctx.workResults.get('sendEmail');
|
|
255
|
+
if (emailResult.status === WorkStatus.SKIPPED) {
|
|
256
|
+
console.log('Email was skipped');
|
|
257
|
+
} else if (emailResult.status === WorkStatus.COMPLETED) {
|
|
258
|
+
console.log('Email sent:', emailResult.result);
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
150
262
|
## Error Handling
|
|
151
263
|
|
|
152
264
|
Handle errors at the work level:
|
package/dist/index.cjs
CHANGED
|
@@ -20,9 +20,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
Work: () => Work,
|
|
23
24
|
WorkStatus: () => WorkStatus,
|
|
24
25
|
Workflow: () => Workflow,
|
|
25
|
-
WorkflowStatus: () => WorkflowStatus
|
|
26
|
+
WorkflowStatus: () => WorkflowStatus,
|
|
27
|
+
getWorkDefinition: () => getWorkDefinition
|
|
26
28
|
});
|
|
27
29
|
module.exports = __toCommonJS(index_exports);
|
|
28
30
|
|
|
@@ -43,6 +45,19 @@ var WorkflowStatus = /* @__PURE__ */ ((WorkflowStatus2) => {
|
|
|
43
45
|
return WorkflowStatus2;
|
|
44
46
|
})(WorkflowStatus || {});
|
|
45
47
|
|
|
48
|
+
// src/work.ts
|
|
49
|
+
var Work = class {
|
|
50
|
+
constructor(definition) {
|
|
51
|
+
this.name = definition.name;
|
|
52
|
+
this.execute = definition.execute;
|
|
53
|
+
this.shouldRun = definition.shouldRun;
|
|
54
|
+
this.onError = definition.onError;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function getWorkDefinition(input) {
|
|
58
|
+
return input;
|
|
59
|
+
}
|
|
60
|
+
|
|
46
61
|
// src/workflow.ts
|
|
47
62
|
var WorkResultsMap = class {
|
|
48
63
|
constructor() {
|
|
@@ -70,23 +85,25 @@ var Workflow = class {
|
|
|
70
85
|
}
|
|
71
86
|
/**
|
|
72
87
|
* Add a serial work to the workflow.
|
|
88
|
+
* Accepts either an inline work definition or a Work instance.
|
|
73
89
|
* The work name and result type are automatically inferred.
|
|
74
90
|
*/
|
|
75
91
|
serial(work) {
|
|
76
92
|
this.works.push({
|
|
77
93
|
type: "serial",
|
|
78
|
-
works: [work]
|
|
94
|
+
works: [getWorkDefinition(work)]
|
|
79
95
|
});
|
|
80
96
|
return this;
|
|
81
97
|
}
|
|
82
98
|
/**
|
|
83
99
|
* Add parallel works to the workflow.
|
|
100
|
+
* Accepts either inline work definitions or Work instances.
|
|
84
101
|
* All work names and result types are automatically inferred.
|
|
85
102
|
*/
|
|
86
103
|
parallel(works) {
|
|
87
104
|
this.works.push({
|
|
88
105
|
type: "parallel",
|
|
89
|
-
works
|
|
106
|
+
works: works.map((w) => getWorkDefinition(w))
|
|
90
107
|
});
|
|
91
108
|
return this;
|
|
92
109
|
}
|
|
@@ -226,8 +243,10 @@ var Workflow = class {
|
|
|
226
243
|
};
|
|
227
244
|
// Annotate the CommonJS export names for ESM import in node:
|
|
228
245
|
0 && (module.exports = {
|
|
246
|
+
Work,
|
|
229
247
|
WorkStatus,
|
|
230
248
|
Workflow,
|
|
231
|
-
WorkflowStatus
|
|
249
|
+
WorkflowStatus,
|
|
250
|
+
getWorkDefinition
|
|
232
251
|
});
|
|
233
252
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/workflow.types.ts","../src/workflow.ts"],"sourcesContent":["export * from './workflow';\nexport * from './workflow.types';\n","/**\n * Work Status\n */\nexport enum WorkStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n SKIPPED = 'skipped',\n}\n\n/**\n * Workflow Status\n */\nexport enum WorkflowStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n}\n\n/**\n * Context passed between workflow works\n * TData is the type of shared data between works\n * TWorkResults is a record mapping work names to their result types\n */\nexport interface IWorkflowContext<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Shared data between works */\n data: TData;\n /** Work-specific results keyed by work name with inferred types */\n workResults: IWorkResultsMap<TWorkResults>;\n}\n\n/**\n * Type-safe map for work results with automatic type inference\n */\nexport interface IWorkResultsMap<\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]>;\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void;\n has(name: keyof TWorkResults): boolean;\n}\n\n/**\n * Result of a single work execution\n */\nexport interface IWorkResult<TResult = unknown> {\n status: WorkStatus;\n result?: TResult;\n error?: Error;\n duration: number;\n}\n\n/**\n * Definition of a work with inferred name and result type\n */\nexport interface IWorkDefinition<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Unique name for the work */\n name: TName;\n /** Execute function - receives context and returns result */\n execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n /** Optional: condition to determine if work should run */\n shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n /** Optional: called when work fails */\n onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n}\n\n/**\n * Internal work representation\n */\nexport interface IWorkflowWork {\n type: 'serial' | 'parallel';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, any, any, any>[];\n}\n\n/**\n * Result of workflow execution\n */\nexport interface IWorkflowResult<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n status: WorkflowStatus;\n context: IWorkflowContext<TData, TWorkResults>;\n workResults: Map<keyof TWorkResults, IWorkResult>;\n totalDuration: number;\n error?: Error;\n}\n","import {\n IWorkflowContext,\n IWorkflowResult,\n IWorkResultsMap,\n WorkflowStatus,\n IWorkflowWork,\n IWorkDefinition,\n IWorkResult,\n WorkStatus,\n} from './workflow.types';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, IWorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as IWorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has(name: keyof TWorkResults): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> {\n private works: IWorkflowWork[] = [];\n\n /**\n * Add a serial work to the workflow.\n * The work name and result type are automatically inferred.\n */\n serial<TName extends string, TResult>(\n work: IWorkDefinition<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n this.works.push({\n type: 'serial',\n works: [work],\n });\n return this as unknown as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * All work names and result types are automatically inferred.\n */\n parallel<\n const TParallelWorks extends readonly IWorkDefinition<string, TData, unknown, TWorkResults>[],\n >(works: TParallelWorks): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n this.works.push({\n type: 'parallel',\n works: works as unknown as IWorkDefinition<string, TData, unknown, TWorkResults>[],\n });\n return this as unknown as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, IWorkResult>();\n\n try {\n for (const workGroup of this.works) {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n }\n\n return {\n status: WorkflowStatus.COMPLETED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: WorkflowStatus.FAILED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n workResults.set(work.name as keyof TWorkResults, {\n status: WorkStatus.FAILED,\n error: err,\n duration: Date.now() - workStartTime,\n });\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution\n throw err;\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: IWorkResult = {\n status: WorkStatus.FAILED,\n error: result.error,\n duration,\n };\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n errors.push({ work: result.work, error: result.error });\n } else {\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Handle errors after all parallel works complete\n if (errors.length > 0) {\n // Call error handlers\n for (const { work, error } of errors) {\n if (work.onError) {\n await work.onError(error, context);\n }\n }\n\n // Throw the first error to stop workflow\n throw errors[0].error;\n }\n }\n}\n\n/**\n * Helper type to extract work results from parallel works array\n * Uses Extract to preserve the specific type for each work name\n */\ntype ParallelWorksToRecord<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends readonly IWorkDefinition<string, any, any, any>[],\n> = {\n [K in T[number]['name']]: Extract<\n T[number],\n { name: K }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n > extends IWorkDefinition<string, any, infer R, any>\n ? R\n : never;\n};\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/workflow.types.ts","../src/work.ts","../src/workflow.ts"],"sourcesContent":["export * from './workflow';\nexport * from './workflow.types';\nexport * from './work';\n","/**\n * Work Status\n */\nexport enum WorkStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n SKIPPED = 'skipped',\n}\n\n/**\n * Workflow Status\n */\nexport enum WorkflowStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n}\n\n/**\n * Context passed between workflow works\n * TData is the type of shared data between works\n * TWorkResults is a record mapping work names to their result types\n */\nexport interface IWorkflowContext<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Shared data between works */\n data: TData;\n /** Work-specific results keyed by work name with inferred types */\n workResults: IWorkResultsMap<TWorkResults>;\n}\n\n/**\n * Type-safe map for work results with automatic type inference\n */\nexport interface IWorkResultsMap<\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]>;\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void;\n has(name: keyof TWorkResults): boolean;\n}\n\n/**\n * Result of a single work execution\n */\nexport interface IWorkResult<TResult = unknown> {\n status: WorkStatus;\n result?: TResult;\n error?: Error;\n duration: number;\n}\n\n/**\n * Definition of a work with inferred name and result type\n */\nexport interface IWorkDefinition<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Unique name for the work */\n name: TName;\n /** Execute function - receives context and returns result */\n execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n /** Optional: condition to determine if work should run */\n shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n /** Optional: called when work fails */\n onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n}\n\n/**\n * Internal work representation\n */\nexport interface IWorkflowWork {\n type: 'serial' | 'parallel';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, any, any, any>[];\n}\n\n/**\n * Result of workflow execution\n */\nexport interface IWorkflowResult<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n status: WorkflowStatus;\n context: IWorkflowContext<TData, TWorkResults>;\n workResults: Map<keyof TWorkResults, IWorkResult>;\n totalDuration: number;\n error?: Error;\n}\n","import { IWorkDefinition, IWorkflowContext } from './workflow.types';\n\n/**\n * A standalone Work unit that can be added to workflows.\n * Implements IWorkDefinition so it can be used anywhere a work definition is expected.\n *\n * @example\n * ```typescript\n * const fetchUser = new Work({\n * name: 'fetchUser',\n * execute: async (ctx) => {\n * return { id: ctx.data.userId, name: 'John' };\n * },\n * });\n *\n * const workflow = new Workflow<{ userId: string }>()\n * .serial(fetchUser)\n * .parallel([work1, work2]);\n * ```\n */\nexport class Work<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n /** Unique name for the work */\n readonly name: TName;\n\n /** Execute function - receives context and returns result */\n readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n\n /** Optional: condition to determine if work should run */\n readonly shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n\n /** Optional: called when work fails */\n readonly onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n\n constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>) {\n this.name = definition.name;\n this.execute = definition.execute;\n this.shouldRun = definition.shouldRun;\n this.onError = definition.onError;\n }\n}\n\n/**\n * Type that accepts a work definition (either inline object or Work instance).\n * Since Work implements IWorkDefinition, this is simply IWorkDefinition.\n */\nexport type WorkInput<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;\n\n/**\n * Helper to get the work definition from a WorkInput.\n * Since Work implements IWorkDefinition, this simply returns the input.\n */\nexport function getWorkDefinition<\n TName extends string,\n TData,\n TResult,\n TAvailableWorkResults extends Record<string, unknown>,\n>(\n input: WorkInput<TName, TData, TResult, TAvailableWorkResults>\n): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n return input;\n}\n","import {\n IWorkflowContext,\n IWorkflowResult,\n IWorkResultsMap,\n WorkflowStatus,\n IWorkflowWork,\n IWorkDefinition,\n IWorkResult,\n WorkStatus,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, IWorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as IWorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has(name: keyof TWorkResults): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> {\n private works: IWorkflowWork[] = [];\n\n /**\n * Add a serial work to the workflow.\n * Accepts either an inline work definition or a Work instance.\n * The work name and result type are automatically inferred.\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n this.works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as unknown as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts either inline work definitions or Work instances.\n * All work names and result types are automatically inferred.\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n this.works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)) as unknown as IWorkDefinition<\n string,\n TData,\n unknown,\n TWorkResults\n >[],\n });\n return this as unknown as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, IWorkResult>();\n\n try {\n for (const workGroup of this.works) {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n }\n\n return {\n status: WorkflowStatus.COMPLETED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: WorkflowStatus.FAILED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n workResults.set(work.name as keyof TWorkResults, {\n status: WorkStatus.FAILED,\n error: err,\n duration: Date.now() - workStartTime,\n });\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution\n throw err;\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: IWorkResult = {\n status: WorkStatus.FAILED,\n error: result.error,\n duration,\n };\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n errors.push({ work: result.work, error: result.error });\n } else {\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Handle errors after all parallel works complete\n if (errors.length > 0) {\n // Call error handlers\n for (const { work, error } of errors) {\n if (work.onError) {\n await work.onError(error, context);\n }\n }\n\n // Throw the first error to stop workflow\n throw errors[0].error;\n }\n }\n}\n\n/**\n * Helper type to extract work results from parallel works array.\n * Since Work implements IWorkDefinition, we can use Extract directly.\n */\ntype ParallelWorksToRecord<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends readonly IWorkDefinition<string, any, any, any>[],\n> = {\n [K in T[number]['name']]: Extract<\n T[number],\n { name: K }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n > extends IWorkDefinition<string, any, infer R, any>\n ? R\n : never;\n};\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -76,6 +76,46 @@ interface IWorkflowResult<TData = Record<string, unknown>, TWorkResults extends
|
|
|
76
76
|
error?: Error;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* A standalone Work unit that can be added to workflows.
|
|
81
|
+
* Implements IWorkDefinition so it can be used anywhere a work definition is expected.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const fetchUser = new Work({
|
|
86
|
+
* name: 'fetchUser',
|
|
87
|
+
* execute: async (ctx) => {
|
|
88
|
+
* return { id: ctx.data.userId, name: 'John' };
|
|
89
|
+
* },
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* const workflow = new Workflow<{ userId: string }>()
|
|
93
|
+
* .serial(fetchUser)
|
|
94
|
+
* .parallel([work1, work2]);
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare class Work<TName extends string, TData = Record<string, unknown>, TResult = unknown, TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {
|
|
98
|
+
/** Unique name for the work */
|
|
99
|
+
readonly name: TName;
|
|
100
|
+
/** Execute function - receives context and returns result */
|
|
101
|
+
readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;
|
|
102
|
+
/** Optional: condition to determine if work should run */
|
|
103
|
+
readonly shouldRun?: (context: IWorkflowContext<TData, TAvailableWorkResults>) => boolean | Promise<boolean>;
|
|
104
|
+
/** Optional: called when work fails */
|
|
105
|
+
readonly onError?: (error: Error, context: IWorkflowContext<TData, TAvailableWorkResults>) => void | Promise<void>;
|
|
106
|
+
constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Type that accepts a work definition (either inline object or Work instance).
|
|
110
|
+
* Since Work implements IWorkDefinition, this is simply IWorkDefinition.
|
|
111
|
+
*/
|
|
112
|
+
type WorkInput<TName extends string, TData = Record<string, unknown>, TResult = unknown, TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;
|
|
113
|
+
/**
|
|
114
|
+
* Helper to get the work definition from a WorkInput.
|
|
115
|
+
* Since Work implements IWorkDefinition, this simply returns the input.
|
|
116
|
+
*/
|
|
117
|
+
declare function getWorkDefinition<TName extends string, TData, TResult, TAvailableWorkResults extends Record<string, unknown>>(input: WorkInput<TName, TData, TResult, TAvailableWorkResults>): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;
|
|
118
|
+
|
|
79
119
|
/**
|
|
80
120
|
* A simple, extensible workflow engine that supports serial and parallel work execution.
|
|
81
121
|
* Work names and result types are automatically inferred from the workflow definition.
|
|
@@ -115,16 +155,18 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
115
155
|
private works;
|
|
116
156
|
/**
|
|
117
157
|
* Add a serial work to the workflow.
|
|
158
|
+
* Accepts either an inline work definition or a Work instance.
|
|
118
159
|
* The work name and result type are automatically inferred.
|
|
119
160
|
*/
|
|
120
|
-
serial<TName extends string, TResult>(work:
|
|
161
|
+
serial<TName extends string, TResult>(work: WorkInput<TName, TData, TResult, TWorkResults>): Workflow<TData, TWorkResults & {
|
|
121
162
|
[K in TName]: TResult;
|
|
122
163
|
}>;
|
|
123
164
|
/**
|
|
124
165
|
* Add parallel works to the workflow.
|
|
166
|
+
* Accepts either inline work definitions or Work instances.
|
|
125
167
|
* All work names and result types are automatically inferred.
|
|
126
168
|
*/
|
|
127
|
-
parallel<const TParallelWorks extends readonly
|
|
169
|
+
parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(works: TParallelWorks): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;
|
|
128
170
|
/**
|
|
129
171
|
* Execute the workflow with initial data
|
|
130
172
|
*/
|
|
@@ -139,8 +181,8 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
139
181
|
private executeParallelWorks;
|
|
140
182
|
}
|
|
141
183
|
/**
|
|
142
|
-
* Helper type to extract work results from parallel works array
|
|
143
|
-
*
|
|
184
|
+
* Helper type to extract work results from parallel works array.
|
|
185
|
+
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
144
186
|
*/
|
|
145
187
|
type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any, any>[]> = {
|
|
146
188
|
[K in T[number]['name']]: Extract<T[number], {
|
|
@@ -148,4 +190,4 @@ type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any,
|
|
|
148
190
|
}> extends IWorkDefinition<string, any, infer R, any> ? R : never;
|
|
149
191
|
};
|
|
150
192
|
|
|
151
|
-
export { type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, WorkStatus, Workflow, WorkflowStatus };
|
|
193
|
+
export { type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, Work, type WorkInput, WorkStatus, Workflow, WorkflowStatus, getWorkDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -76,6 +76,46 @@ interface IWorkflowResult<TData = Record<string, unknown>, TWorkResults extends
|
|
|
76
76
|
error?: Error;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* A standalone Work unit that can be added to workflows.
|
|
81
|
+
* Implements IWorkDefinition so it can be used anywhere a work definition is expected.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* ```typescript
|
|
85
|
+
* const fetchUser = new Work({
|
|
86
|
+
* name: 'fetchUser',
|
|
87
|
+
* execute: async (ctx) => {
|
|
88
|
+
* return { id: ctx.data.userId, name: 'John' };
|
|
89
|
+
* },
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* const workflow = new Workflow<{ userId: string }>()
|
|
93
|
+
* .serial(fetchUser)
|
|
94
|
+
* .parallel([work1, work2]);
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
declare class Work<TName extends string, TData = Record<string, unknown>, TResult = unknown, TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {
|
|
98
|
+
/** Unique name for the work */
|
|
99
|
+
readonly name: TName;
|
|
100
|
+
/** Execute function - receives context and returns result */
|
|
101
|
+
readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;
|
|
102
|
+
/** Optional: condition to determine if work should run */
|
|
103
|
+
readonly shouldRun?: (context: IWorkflowContext<TData, TAvailableWorkResults>) => boolean | Promise<boolean>;
|
|
104
|
+
/** Optional: called when work fails */
|
|
105
|
+
readonly onError?: (error: Error, context: IWorkflowContext<TData, TAvailableWorkResults>) => void | Promise<void>;
|
|
106
|
+
constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Type that accepts a work definition (either inline object or Work instance).
|
|
110
|
+
* Since Work implements IWorkDefinition, this is simply IWorkDefinition.
|
|
111
|
+
*/
|
|
112
|
+
type WorkInput<TName extends string, TData = Record<string, unknown>, TResult = unknown, TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;
|
|
113
|
+
/**
|
|
114
|
+
* Helper to get the work definition from a WorkInput.
|
|
115
|
+
* Since Work implements IWorkDefinition, this simply returns the input.
|
|
116
|
+
*/
|
|
117
|
+
declare function getWorkDefinition<TName extends string, TData, TResult, TAvailableWorkResults extends Record<string, unknown>>(input: WorkInput<TName, TData, TResult, TAvailableWorkResults>): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;
|
|
118
|
+
|
|
79
119
|
/**
|
|
80
120
|
* A simple, extensible workflow engine that supports serial and parallel work execution.
|
|
81
121
|
* Work names and result types are automatically inferred from the workflow definition.
|
|
@@ -115,16 +155,18 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
115
155
|
private works;
|
|
116
156
|
/**
|
|
117
157
|
* Add a serial work to the workflow.
|
|
158
|
+
* Accepts either an inline work definition or a Work instance.
|
|
118
159
|
* The work name and result type are automatically inferred.
|
|
119
160
|
*/
|
|
120
|
-
serial<TName extends string, TResult>(work:
|
|
161
|
+
serial<TName extends string, TResult>(work: WorkInput<TName, TData, TResult, TWorkResults>): Workflow<TData, TWorkResults & {
|
|
121
162
|
[K in TName]: TResult;
|
|
122
163
|
}>;
|
|
123
164
|
/**
|
|
124
165
|
* Add parallel works to the workflow.
|
|
166
|
+
* Accepts either inline work definitions or Work instances.
|
|
125
167
|
* All work names and result types are automatically inferred.
|
|
126
168
|
*/
|
|
127
|
-
parallel<const TParallelWorks extends readonly
|
|
169
|
+
parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(works: TParallelWorks): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;
|
|
128
170
|
/**
|
|
129
171
|
* Execute the workflow with initial data
|
|
130
172
|
*/
|
|
@@ -139,8 +181,8 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
139
181
|
private executeParallelWorks;
|
|
140
182
|
}
|
|
141
183
|
/**
|
|
142
|
-
* Helper type to extract work results from parallel works array
|
|
143
|
-
*
|
|
184
|
+
* Helper type to extract work results from parallel works array.
|
|
185
|
+
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
144
186
|
*/
|
|
145
187
|
type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any, any>[]> = {
|
|
146
188
|
[K in T[number]['name']]: Extract<T[number], {
|
|
@@ -148,4 +190,4 @@ type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any,
|
|
|
148
190
|
}> extends IWorkDefinition<string, any, infer R, any> ? R : never;
|
|
149
191
|
};
|
|
150
192
|
|
|
151
|
-
export { type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, WorkStatus, Workflow, WorkflowStatus };
|
|
193
|
+
export { type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, Work, type WorkInput, WorkStatus, Workflow, WorkflowStatus, getWorkDefinition };
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,19 @@ var WorkflowStatus = /* @__PURE__ */ ((WorkflowStatus2) => {
|
|
|
15
15
|
return WorkflowStatus2;
|
|
16
16
|
})(WorkflowStatus || {});
|
|
17
17
|
|
|
18
|
+
// src/work.ts
|
|
19
|
+
var Work = class {
|
|
20
|
+
constructor(definition) {
|
|
21
|
+
this.name = definition.name;
|
|
22
|
+
this.execute = definition.execute;
|
|
23
|
+
this.shouldRun = definition.shouldRun;
|
|
24
|
+
this.onError = definition.onError;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function getWorkDefinition(input) {
|
|
28
|
+
return input;
|
|
29
|
+
}
|
|
30
|
+
|
|
18
31
|
// src/workflow.ts
|
|
19
32
|
var WorkResultsMap = class {
|
|
20
33
|
constructor() {
|
|
@@ -42,23 +55,25 @@ var Workflow = class {
|
|
|
42
55
|
}
|
|
43
56
|
/**
|
|
44
57
|
* Add a serial work to the workflow.
|
|
58
|
+
* Accepts either an inline work definition or a Work instance.
|
|
45
59
|
* The work name and result type are automatically inferred.
|
|
46
60
|
*/
|
|
47
61
|
serial(work) {
|
|
48
62
|
this.works.push({
|
|
49
63
|
type: "serial",
|
|
50
|
-
works: [work]
|
|
64
|
+
works: [getWorkDefinition(work)]
|
|
51
65
|
});
|
|
52
66
|
return this;
|
|
53
67
|
}
|
|
54
68
|
/**
|
|
55
69
|
* Add parallel works to the workflow.
|
|
70
|
+
* Accepts either inline work definitions or Work instances.
|
|
56
71
|
* All work names and result types are automatically inferred.
|
|
57
72
|
*/
|
|
58
73
|
parallel(works) {
|
|
59
74
|
this.works.push({
|
|
60
75
|
type: "parallel",
|
|
61
|
-
works
|
|
76
|
+
works: works.map((w) => getWorkDefinition(w))
|
|
62
77
|
});
|
|
63
78
|
return this;
|
|
64
79
|
}
|
|
@@ -197,8 +212,10 @@ var Workflow = class {
|
|
|
197
212
|
}
|
|
198
213
|
};
|
|
199
214
|
export {
|
|
215
|
+
Work,
|
|
200
216
|
WorkStatus,
|
|
201
217
|
Workflow,
|
|
202
|
-
WorkflowStatus
|
|
218
|
+
WorkflowStatus,
|
|
219
|
+
getWorkDefinition
|
|
203
220
|
};
|
|
204
221
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/workflow.types.ts","../src/workflow.ts"],"sourcesContent":["/**\n * Work Status\n */\nexport enum WorkStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n SKIPPED = 'skipped',\n}\n\n/**\n * Workflow Status\n */\nexport enum WorkflowStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n}\n\n/**\n * Context passed between workflow works\n * TData is the type of shared data between works\n * TWorkResults is a record mapping work names to their result types\n */\nexport interface IWorkflowContext<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Shared data between works */\n data: TData;\n /** Work-specific results keyed by work name with inferred types */\n workResults: IWorkResultsMap<TWorkResults>;\n}\n\n/**\n * Type-safe map for work results with automatic type inference\n */\nexport interface IWorkResultsMap<\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]>;\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void;\n has(name: keyof TWorkResults): boolean;\n}\n\n/**\n * Result of a single work execution\n */\nexport interface IWorkResult<TResult = unknown> {\n status: WorkStatus;\n result?: TResult;\n error?: Error;\n duration: number;\n}\n\n/**\n * Definition of a work with inferred name and result type\n */\nexport interface IWorkDefinition<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Unique name for the work */\n name: TName;\n /** Execute function - receives context and returns result */\n execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n /** Optional: condition to determine if work should run */\n shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n /** Optional: called when work fails */\n onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n}\n\n/**\n * Internal work representation\n */\nexport interface IWorkflowWork {\n type: 'serial' | 'parallel';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, any, any, any>[];\n}\n\n/**\n * Result of workflow execution\n */\nexport interface IWorkflowResult<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n status: WorkflowStatus;\n context: IWorkflowContext<TData, TWorkResults>;\n workResults: Map<keyof TWorkResults, IWorkResult>;\n totalDuration: number;\n error?: Error;\n}\n","import {\n IWorkflowContext,\n IWorkflowResult,\n IWorkResultsMap,\n WorkflowStatus,\n IWorkflowWork,\n IWorkDefinition,\n IWorkResult,\n WorkStatus,\n} from './workflow.types';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, IWorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as IWorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has(name: keyof TWorkResults): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> {\n private works: IWorkflowWork[] = [];\n\n /**\n * Add a serial work to the workflow.\n * The work name and result type are automatically inferred.\n */\n serial<TName extends string, TResult>(\n work: IWorkDefinition<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n this.works.push({\n type: 'serial',\n works: [work],\n });\n return this as unknown as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * All work names and result types are automatically inferred.\n */\n parallel<\n const TParallelWorks extends readonly IWorkDefinition<string, TData, unknown, TWorkResults>[],\n >(works: TParallelWorks): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n this.works.push({\n type: 'parallel',\n works: works as unknown as IWorkDefinition<string, TData, unknown, TWorkResults>[],\n });\n return this as unknown as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, IWorkResult>();\n\n try {\n for (const workGroup of this.works) {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n }\n\n return {\n status: WorkflowStatus.COMPLETED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: WorkflowStatus.FAILED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n workResults.set(work.name as keyof TWorkResults, {\n status: WorkStatus.FAILED,\n error: err,\n duration: Date.now() - workStartTime,\n });\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution\n throw err;\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: IWorkResult = {\n status: WorkStatus.FAILED,\n error: result.error,\n duration,\n };\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n errors.push({ work: result.work, error: result.error });\n } else {\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Handle errors after all parallel works complete\n if (errors.length > 0) {\n // Call error handlers\n for (const { work, error } of errors) {\n if (work.onError) {\n await work.onError(error, context);\n }\n }\n\n // Throw the first error to stop workflow\n throw errors[0].error;\n }\n }\n}\n\n/**\n * Helper type to extract work results from parallel works array\n * Uses Extract to preserve the specific type for each work name\n */\ntype ParallelWorksToRecord<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends readonly IWorkDefinition<string, any, any, any>[],\n> = {\n [K in T[number]['name']]: Extract<\n T[number],\n { name: K }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n > extends IWorkDefinition<string, any, infer R, any>\n ? R\n : never;\n};\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
|
1
|
+
{"version":3,"sources":["../src/workflow.types.ts","../src/work.ts","../src/workflow.ts"],"sourcesContent":["/**\n * Work Status\n */\nexport enum WorkStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n SKIPPED = 'skipped',\n}\n\n/**\n * Workflow Status\n */\nexport enum WorkflowStatus {\n PENDING = 'pending',\n RUNNING = 'running',\n COMPLETED = 'completed',\n FAILED = 'failed',\n}\n\n/**\n * Context passed between workflow works\n * TData is the type of shared data between works\n * TWorkResults is a record mapping work names to their result types\n */\nexport interface IWorkflowContext<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Shared data between works */\n data: TData;\n /** Work-specific results keyed by work name with inferred types */\n workResults: IWorkResultsMap<TWorkResults>;\n}\n\n/**\n * Type-safe map for work results with automatic type inference\n */\nexport interface IWorkResultsMap<\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]>;\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void;\n has(name: keyof TWorkResults): boolean;\n}\n\n/**\n * Result of a single work execution\n */\nexport interface IWorkResult<TResult = unknown> {\n status: WorkStatus;\n result?: TResult;\n error?: Error;\n duration: number;\n}\n\n/**\n * Definition of a work with inferred name and result type\n */\nexport interface IWorkDefinition<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /** Unique name for the work */\n name: TName;\n /** Execute function - receives context and returns result */\n execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n /** Optional: condition to determine if work should run */\n shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n /** Optional: called when work fails */\n onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n}\n\n/**\n * Internal work representation\n */\nexport interface IWorkflowWork {\n type: 'serial' | 'parallel';\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, any, any, any>[];\n}\n\n/**\n * Result of workflow execution\n */\nexport interface IWorkflowResult<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n status: WorkflowStatus;\n context: IWorkflowContext<TData, TWorkResults>;\n workResults: Map<keyof TWorkResults, IWorkResult>;\n totalDuration: number;\n error?: Error;\n}\n","import { IWorkDefinition, IWorkflowContext } from './workflow.types';\n\n/**\n * A standalone Work unit that can be added to workflows.\n * Implements IWorkDefinition so it can be used anywhere a work definition is expected.\n *\n * @example\n * ```typescript\n * const fetchUser = new Work({\n * name: 'fetchUser',\n * execute: async (ctx) => {\n * return { id: ctx.data.userId, name: 'John' };\n * },\n * });\n *\n * const workflow = new Workflow<{ userId: string }>()\n * .serial(fetchUser)\n * .parallel([work1, work2]);\n * ```\n */\nexport class Work<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n /** Unique name for the work */\n readonly name: TName;\n\n /** Execute function - receives context and returns result */\n readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n\n /** Optional: condition to determine if work should run */\n readonly shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n\n /** Optional: called when work fails */\n readonly onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n\n constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>) {\n this.name = definition.name;\n this.execute = definition.execute;\n this.shouldRun = definition.shouldRun;\n this.onError = definition.onError;\n }\n}\n\n/**\n * Type that accepts a work definition (either inline object or Work instance).\n * Since Work implements IWorkDefinition, this is simply IWorkDefinition.\n */\nexport type WorkInput<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;\n\n/**\n * Helper to get the work definition from a WorkInput.\n * Since Work implements IWorkDefinition, this simply returns the input.\n */\nexport function getWorkDefinition<\n TName extends string,\n TData,\n TResult,\n TAvailableWorkResults extends Record<string, unknown>,\n>(\n input: WorkInput<TName, TData, TResult, TAvailableWorkResults>\n): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n return input;\n}\n","import {\n IWorkflowContext,\n IWorkflowResult,\n IWorkResultsMap,\n WorkflowStatus,\n IWorkflowWork,\n IWorkDefinition,\n IWorkResult,\n WorkStatus,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, IWorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as IWorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has(name: keyof TWorkResults): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> {\n private works: IWorkflowWork[] = [];\n\n /**\n * Add a serial work to the workflow.\n * Accepts either an inline work definition or a Work instance.\n * The work name and result type are automatically inferred.\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n this.works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as unknown as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts either inline work definitions or Work instances.\n * All work names and result types are automatically inferred.\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n this.works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)) as unknown as IWorkDefinition<\n string,\n TData,\n unknown,\n TWorkResults\n >[],\n });\n return this as unknown as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, IWorkResult>();\n\n try {\n for (const workGroup of this.works) {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n }\n\n return {\n status: WorkflowStatus.COMPLETED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: WorkflowStatus.FAILED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n workResults.set(work.name as keyof TWorkResults, {\n status: WorkStatus.FAILED,\n error: err,\n duration: Date.now() - workStartTime,\n });\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution\n throw err;\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, IWorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: IWorkResult = {\n status: WorkStatus.SKIPPED,\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: IWorkResult = {\n status: WorkStatus.FAILED,\n error: result.error,\n duration,\n };\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n errors.push({ work: result.work, error: result.error });\n } else {\n const workResult: IWorkResult = {\n status: WorkStatus.COMPLETED,\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Handle errors after all parallel works complete\n if (errors.length > 0) {\n // Call error handlers\n for (const { work, error } of errors) {\n if (work.onError) {\n await work.onError(error, context);\n }\n }\n\n // Throw the first error to stop workflow\n throw errors[0].error;\n }\n }\n}\n\n/**\n * Helper type to extract work results from parallel works array.\n * Since Work implements IWorkDefinition, we can use Extract directly.\n */\ntype ParallelWorksToRecord<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends readonly IWorkDefinition<string, any, any, any>[],\n> = {\n [K in T[number]['name']]: Extract<\n T[number],\n { name: K }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n > extends IWorkDefinition<string, any, infer R, any>\n ? R\n : never;\n};\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yigitahmetsahin/workflow-ts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "A simple, extensible TypeScript workflow engine supporting serial and parallel work execution with full type inference",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|