@yigitahmetsahin/workflow-ts 3.4.2 → 3.4.4
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 +41 -15
- package/dist/index.cjs +16 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -33
- package/dist/index.d.ts +26 -33
- package/dist/index.js +16 -29
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ const workflow = new Workflow<{ userId: string }>()
|
|
|
58
58
|
name: 'process',
|
|
59
59
|
execute: async (ctx) => {
|
|
60
60
|
// ✅ Types are automatically inferred!
|
|
61
|
-
// workResults.get() returns
|
|
61
|
+
// workResults.get() returns WorkResult with status, result, duration
|
|
62
62
|
const orders = ctx.workResults.get('fetchOrders').result; // { id: number }[]
|
|
63
63
|
const profile = ctx.workResults.get('fetchProfile').result; // { name: string; email: string }
|
|
64
64
|
return { orderCount: orders?.length ?? 0, userName: profile?.name };
|
|
@@ -80,7 +80,7 @@ const workflow2 = new Workflow<{ userId: string }>().serial(validateUser).parall
|
|
|
80
80
|
|
|
81
81
|
const result = await workflow.run({ userId: 'user-123' });
|
|
82
82
|
|
|
83
|
-
if (result.status ===
|
|
83
|
+
if (result.status === 'completed') {
|
|
84
84
|
console.log('Workflow completed in', result.totalDuration, 'ms');
|
|
85
85
|
console.log('Final result:', result.context.workResults.get('process').result);
|
|
86
86
|
}
|
|
@@ -237,6 +237,32 @@ console.log(workflow.name); // 'seal'
|
|
|
237
237
|
const result = await workflow.run({ userId: '123' });
|
|
238
238
|
```
|
|
239
239
|
|
|
240
|
+
#### Seal with Final Work
|
|
241
|
+
|
|
242
|
+
You can pass an execute function to `seal()` that runs as a final serial work after all previous works:
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
const sealed = new Workflow<{ userId: string }>()
|
|
246
|
+
.serial({ name: 'validate', execute: async (ctx) => true })
|
|
247
|
+
.parallel([
|
|
248
|
+
{ name: 'fetchOrders', execute: async () => [{ id: 1 }] },
|
|
249
|
+
{ name: 'fetchProfile', execute: async () => ({ name: 'John' }) },
|
|
250
|
+
])
|
|
251
|
+
.seal({
|
|
252
|
+
name: 'finalize', // required
|
|
253
|
+
execute: async (ctx) => {
|
|
254
|
+
// Access results from previous works
|
|
255
|
+
const orders = ctx.workResults.get('fetchOrders').result;
|
|
256
|
+
const profile = ctx.workResults.get('fetchProfile').result;
|
|
257
|
+
return { orders, profile, summary: 'Done' };
|
|
258
|
+
},
|
|
259
|
+
// Optional: shouldRun, onError, silenceError work just like serial works
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const result = await sealed.run({ userId: '123' });
|
|
263
|
+
console.log(result.workResults.get('finalize')?.result); // { orders, profile, summary: 'Done' }
|
|
264
|
+
```
|
|
265
|
+
|
|
240
266
|
### Error Silencing
|
|
241
267
|
|
|
242
268
|
Use `silenceError: true` to allow a work to fail without stopping the workflow. The error is still recorded and accessible:
|
|
@@ -256,7 +282,7 @@ const workflow = new Workflow<{ userId: string }>()
|
|
|
256
282
|
execute: async (ctx) => {
|
|
257
283
|
// Check if previous work failed
|
|
258
284
|
const optionalResult = ctx.workResults.get('fetchOptionalData');
|
|
259
|
-
if (optionalResult.status ===
|
|
285
|
+
if (optionalResult.status === 'failed') {
|
|
260
286
|
return { data: null, error: optionalResult.error?.message };
|
|
261
287
|
}
|
|
262
288
|
return { data: optionalResult.result };
|
|
@@ -264,7 +290,7 @@ const workflow = new Workflow<{ userId: string }>()
|
|
|
264
290
|
});
|
|
265
291
|
|
|
266
292
|
const result = await workflow.run({ userId: '123' });
|
|
267
|
-
// result.status ===
|
|
293
|
+
// result.status === 'completed' (workflow continues despite error)
|
|
268
294
|
```
|
|
269
295
|
|
|
270
296
|
### Workflow Options
|
|
@@ -307,7 +333,7 @@ const workflow = new Workflow<{ userId: string }>({ failFast: false })
|
|
|
307
333
|
// work3 WILL execute, but workflow still fails at the end
|
|
308
334
|
|
|
309
335
|
const result = await workflow.run({ userId: '123' });
|
|
310
|
-
// result.status ===
|
|
336
|
+
// result.status === 'failed'
|
|
311
337
|
// result.error.message === 'Continue anyway' (first error)
|
|
312
338
|
// result.context.workResults.get('work3')?.result === 'still runs'
|
|
313
339
|
```
|
|
@@ -333,7 +359,7 @@ const workflow = new Workflow<{ userId: string }>({ failFast: false })
|
|
|
333
359
|
.serial({ name: 'final', execute: async () => 'done' });
|
|
334
360
|
|
|
335
361
|
const result = await workflow.run({ userId: '123' });
|
|
336
|
-
// result.status ===
|
|
362
|
+
// result.status === 'completed' (all errors silenced)
|
|
337
363
|
```
|
|
338
364
|
|
|
339
365
|
### `.run(initialData)`
|
|
@@ -347,29 +373,29 @@ const result = await workflow.run({ userId: '123' });
|
|
|
347
373
|
### Result Object
|
|
348
374
|
|
|
349
375
|
```typescript
|
|
350
|
-
|
|
376
|
+
type WorkflowResult = {
|
|
351
377
|
status: WorkflowStatus; // 'completed' | 'failed'
|
|
352
378
|
context: {
|
|
353
379
|
data: TData; // Initial data passed to run()
|
|
354
380
|
workResults: IWorkResultsMap; // Type-safe map of work results
|
|
355
381
|
};
|
|
356
|
-
workResults: Map<string,
|
|
382
|
+
workResults: Map<string, WorkResult>; // Detailed results per work
|
|
357
383
|
totalDuration: number; // Total execution time in ms
|
|
358
384
|
error?: Error; // Error if workflow failed
|
|
359
|
-
}
|
|
385
|
+
};
|
|
360
386
|
|
|
361
387
|
// Each work result contains execution details
|
|
362
|
-
|
|
388
|
+
type WorkResult<T> = {
|
|
363
389
|
status: WorkStatus; // 'completed' | 'failed' | 'skipped'
|
|
364
390
|
result?: T; // The return value from execute()
|
|
365
391
|
error?: Error; // Error if work failed
|
|
366
392
|
duration: number; // Execution time in ms
|
|
367
|
-
}
|
|
393
|
+
};
|
|
368
394
|
```
|
|
369
395
|
|
|
370
396
|
### Accessing Work Results
|
|
371
397
|
|
|
372
|
-
`ctx.workResults.get()` returns
|
|
398
|
+
`ctx.workResults.get()` returns a `WorkResult` object, not the raw value:
|
|
373
399
|
|
|
374
400
|
```typescript
|
|
375
401
|
// Get the full work result with metadata
|
|
@@ -381,7 +407,7 @@ console.log(workResult.duration); // execution time in ms
|
|
|
381
407
|
const user = ctx.workResults.get('fetchUser').result;
|
|
382
408
|
|
|
383
409
|
// Check status before accessing result
|
|
384
|
-
if (workResult.status ===
|
|
410
|
+
if (workResult.status === 'completed') {
|
|
385
411
|
console.log('User:', workResult.result);
|
|
386
412
|
}
|
|
387
413
|
```
|
|
@@ -407,9 +433,9 @@ Skipped works are still accessible via `workResults.get()` with `status: 'skippe
|
|
|
407
433
|
|
|
408
434
|
```typescript
|
|
409
435
|
const emailResult = ctx.workResults.get('sendEmail');
|
|
410
|
-
if (emailResult.status ===
|
|
436
|
+
if (emailResult.status === 'skipped') {
|
|
411
437
|
console.log('Email was skipped');
|
|
412
|
-
} else if (emailResult.status ===
|
|
438
|
+
} else if (emailResult.status === 'completed') {
|
|
413
439
|
console.log('Email sent:', emailResult.result);
|
|
414
440
|
}
|
|
415
441
|
```
|
package/dist/index.cjs
CHANGED
|
@@ -21,30 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
Work: () => Work,
|
|
24
|
-
WorkStatus: () => WorkStatus,
|
|
25
24
|
Workflow: () => Workflow,
|
|
26
|
-
WorkflowStatus: () => WorkflowStatus,
|
|
27
25
|
getWorkDefinition: () => getWorkDefinition
|
|
28
26
|
});
|
|
29
27
|
module.exports = __toCommonJS(index_exports);
|
|
30
28
|
|
|
31
|
-
// src/workflow.types.ts
|
|
32
|
-
var WorkStatus = /* @__PURE__ */ ((WorkStatus2) => {
|
|
33
|
-
WorkStatus2["PENDING"] = "pending";
|
|
34
|
-
WorkStatus2["RUNNING"] = "running";
|
|
35
|
-
WorkStatus2["COMPLETED"] = "completed";
|
|
36
|
-
WorkStatus2["FAILED"] = "failed";
|
|
37
|
-
WorkStatus2["SKIPPED"] = "skipped";
|
|
38
|
-
return WorkStatus2;
|
|
39
|
-
})(WorkStatus || {});
|
|
40
|
-
var WorkflowStatus = /* @__PURE__ */ ((WorkflowStatus2) => {
|
|
41
|
-
WorkflowStatus2["PENDING"] = "pending";
|
|
42
|
-
WorkflowStatus2["RUNNING"] = "running";
|
|
43
|
-
WorkflowStatus2["COMPLETED"] = "completed";
|
|
44
|
-
WorkflowStatus2["FAILED"] = "failed";
|
|
45
|
-
return WorkflowStatus2;
|
|
46
|
-
})(WorkflowStatus || {});
|
|
47
|
-
|
|
48
29
|
// src/work.ts
|
|
49
30
|
var Work = class {
|
|
50
31
|
constructor(definition) {
|
|
@@ -144,7 +125,13 @@ var Workflow = class {
|
|
|
144
125
|
});
|
|
145
126
|
return this;
|
|
146
127
|
}
|
|
147
|
-
seal(
|
|
128
|
+
seal(sealingWork) {
|
|
129
|
+
if (sealingWork) {
|
|
130
|
+
this._works.push({
|
|
131
|
+
type: "serial",
|
|
132
|
+
works: [sealingWork]
|
|
133
|
+
});
|
|
134
|
+
}
|
|
148
135
|
this._sealed = true;
|
|
149
136
|
return {
|
|
150
137
|
name: "seal",
|
|
@@ -183,7 +170,7 @@ var Workflow = class {
|
|
|
183
170
|
}
|
|
184
171
|
if (collectedErrors.length > 0) {
|
|
185
172
|
return {
|
|
186
|
-
status: "failed"
|
|
173
|
+
status: "failed",
|
|
187
174
|
context,
|
|
188
175
|
workResults,
|
|
189
176
|
totalDuration: Date.now() - startTime,
|
|
@@ -191,14 +178,14 @@ var Workflow = class {
|
|
|
191
178
|
};
|
|
192
179
|
}
|
|
193
180
|
return {
|
|
194
|
-
status: "completed"
|
|
181
|
+
status: "completed",
|
|
195
182
|
context,
|
|
196
183
|
workResults,
|
|
197
184
|
totalDuration: Date.now() - startTime
|
|
198
185
|
};
|
|
199
186
|
} catch (error) {
|
|
200
187
|
return {
|
|
201
|
-
status: "failed"
|
|
188
|
+
status: "failed",
|
|
202
189
|
context,
|
|
203
190
|
workResults,
|
|
204
191
|
totalDuration: Date.now() - startTime,
|
|
@@ -215,7 +202,7 @@ var Workflow = class {
|
|
|
215
202
|
const shouldRun = await work.shouldRun(context);
|
|
216
203
|
if (!shouldRun) {
|
|
217
204
|
const skippedResult = {
|
|
218
|
-
status: "skipped"
|
|
205
|
+
status: "skipped",
|
|
219
206
|
duration: Date.now() - workStartTime
|
|
220
207
|
};
|
|
221
208
|
context.workResults.set(work.name, skippedResult);
|
|
@@ -226,7 +213,7 @@ var Workflow = class {
|
|
|
226
213
|
try {
|
|
227
214
|
const result = await work.execute(context);
|
|
228
215
|
const workResult = {
|
|
229
|
-
status: "completed"
|
|
216
|
+
status: "completed",
|
|
230
217
|
result,
|
|
231
218
|
duration: Date.now() - workStartTime
|
|
232
219
|
};
|
|
@@ -235,7 +222,7 @@ var Workflow = class {
|
|
|
235
222
|
} catch (error) {
|
|
236
223
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
237
224
|
const failedResult = {
|
|
238
|
-
status: "failed"
|
|
225
|
+
status: "failed",
|
|
239
226
|
error: err,
|
|
240
227
|
duration: Date.now() - workStartTime
|
|
241
228
|
};
|
|
@@ -259,7 +246,7 @@ var Workflow = class {
|
|
|
259
246
|
const shouldRun = await work.shouldRun(context);
|
|
260
247
|
if (!shouldRun) {
|
|
261
248
|
const skippedResult = {
|
|
262
|
-
status: "skipped"
|
|
249
|
+
status: "skipped",
|
|
263
250
|
duration: Date.now() - workStartTime
|
|
264
251
|
};
|
|
265
252
|
context.workResults.set(work.name, skippedResult);
|
|
@@ -284,7 +271,7 @@ var Workflow = class {
|
|
|
284
271
|
const duration = Date.now() - result.startTime;
|
|
285
272
|
if ("error" in result && result.error) {
|
|
286
273
|
const workResult = {
|
|
287
|
-
status: "failed"
|
|
274
|
+
status: "failed",
|
|
288
275
|
error: result.error,
|
|
289
276
|
duration
|
|
290
277
|
};
|
|
@@ -295,7 +282,7 @@ var Workflow = class {
|
|
|
295
282
|
}
|
|
296
283
|
} else {
|
|
297
284
|
const workResult = {
|
|
298
|
-
status: "completed"
|
|
285
|
+
status: "completed",
|
|
299
286
|
result: result.result,
|
|
300
287
|
duration
|
|
301
288
|
};
|
|
@@ -316,9 +303,7 @@ var Workflow = class {
|
|
|
316
303
|
// Annotate the CommonJS export names for ESM import in node:
|
|
317
304
|
0 && (module.exports = {
|
|
318
305
|
Work,
|
|
319
|
-
WorkStatus,
|
|
320
306
|
Workflow,
|
|
321
|
-
WorkflowStatus,
|
|
322
307
|
getWorkDefinition
|
|
323
308
|
});
|
|
324
309
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 a work result with compile-time type checking */\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 /** Check if a work result exists */\n has(name: string): 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 /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n silenceError?: boolean;\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\n/**\n * Interface for the Workflow class.\n * Defines all methods available on a workflow before sealing.\n */\nexport interface IWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /**\n * The list of works in the workflow (readonly)\n */\n readonly works: readonly IWorkflowWork[];\n\n /**\n * The workflow options (readonly)\n */\n readonly options: Readonly<Required<WorkflowOptions>>;\n\n /**\n * Add a serial work to the workflow\n */\n serial<TName extends string, TResult>(\n work: IWorkDefinition<TName, TData, TResult, TWorkResults>\n ): IWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n\n /**\n * Add parallel works to the workflow\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parallel(works: readonly IWorkDefinition<string, TData, any, TWorkResults>[]): IWorkflow<\n TData,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n >;\n\n /**\n * Seal the workflow to prevent further modifications\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults>;\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean;\n\n /**\n * Execute the workflow with initial data\n */\n run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;\n}\n\n/**\n * A work definition for sealing a workflow.\n * Similar to IWorkDefinition but without 'name'.\n */\nexport type ISealingWorkDefinition<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n TResult = unknown,\n> = Omit<IWorkDefinition<'seal', TData, TResult, TWorkResults>, 'name'>;\n\n/**\n * A sealed workflow that can only be executed, not modified.\n * Use workflow.seal() to create a sealed workflow.\n * Picks `works`, `options`, `isSealed`, and `run` from IWorkflow, adds `name: 'seal'`.\n */\nexport type ISealedWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = Pick<IWorkflow<TData, TWorkResults>, 'works' | 'options' | 'isSealed' | 'run'> & {\n readonly name: 'seal';\n};\n\n/**\n * Options for configuring workflow behavior\n */\nexport interface WorkflowOptions {\n /**\n * Whether to stop execution immediately when a work fails.\n * - true: Stop on first failure (default)\n * - false: Continue executing remaining works, fail at the end if any work failed\n * @default true\n */\n failFast?: boolean;\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 */\nexport type 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","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 /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\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 this.silenceError = definition.silenceError;\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 IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\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<K extends keyof TWorkResults>(name: K): 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> implements IWorkflow<TData, TWorkResults> {\n private _works: IWorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly IWorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\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 * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n _sealingWork?: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults> {\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n };\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 const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\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 } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: WorkflowStatus.FAILED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\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 const failedResult: IWorkResult = {\n status: WorkStatus.FAILED,\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\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 (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\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 // 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 // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\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 // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/work.ts","../src/workflow.ts"],"sourcesContent":["export * from './workflow';\nexport * from './workflow.types';\nexport * from './work';\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 /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\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 this.silenceError = definition.silenceError;\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 WorkflowResult,\n IWorkResultsMap,\n WorkflowWork,\n IWorkDefinition,\n WorkResult,\n IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\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, WorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): WorkResult<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 WorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has<K extends keyof TWorkResults>(name: K): 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> implements IWorkflow<TData, TWorkResults> {\n private _works: WorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly WorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\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 * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TName extends string, TResult>(\n sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n seal<TName extends string, TResult>(\n sealingWork?: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }> {\n // If sealingWork is provided, add it as a final serial work\n if (sealingWork) {\n this._works.push({\n type: 'serial',\n works: [sealingWork],\n });\n }\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n } as ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<WorkflowResult<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, WorkResult>();\n const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\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 } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\n };\n }\n\n return {\n status: 'completed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: '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, WorkResult>\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: WorkResult = {\n status: '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: WorkResult = {\n status: '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 const failedResult: WorkResult = {\n status: 'failed',\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\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 (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\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, WorkResult>\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: WorkResult = {\n status: '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: WorkResult = {\n status: 'failed',\n error: result.error,\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 // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\n } else {\n const workResult: WorkResult = {\n status: '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 // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Work Status
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
PENDING = "pending",
|
|
6
|
-
RUNNING = "running",
|
|
7
|
-
COMPLETED = "completed",
|
|
8
|
-
FAILED = "failed",
|
|
9
|
-
SKIPPED = "skipped"
|
|
10
|
-
}
|
|
4
|
+
type WorkStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
11
5
|
/**
|
|
12
6
|
* Workflow Status
|
|
13
7
|
*/
|
|
14
|
-
|
|
15
|
-
PENDING = "pending",
|
|
16
|
-
RUNNING = "running",
|
|
17
|
-
COMPLETED = "completed",
|
|
18
|
-
FAILED = "failed"
|
|
19
|
-
}
|
|
8
|
+
type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
20
9
|
/**
|
|
21
10
|
* Context passed between workflow works
|
|
22
11
|
* TData is the type of shared data between works
|
|
@@ -33,20 +22,20 @@ interface IWorkflowContext<TData = Record<string, unknown>, TWorkResults extends
|
|
|
33
22
|
*/
|
|
34
23
|
interface IWorkResultsMap<TWorkResults extends Record<string, unknown> = Record<string, unknown>> {
|
|
35
24
|
/** Get a work result with compile-time type checking */
|
|
36
|
-
get<K extends keyof TWorkResults>(name: K):
|
|
37
|
-
set<K extends keyof TWorkResults>(name: K, value:
|
|
25
|
+
get<K extends keyof TWorkResults>(name: K): WorkResult<TWorkResults[K]>;
|
|
26
|
+
set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void;
|
|
38
27
|
/** Check if a work result exists */
|
|
39
28
|
has(name: string): boolean;
|
|
40
29
|
}
|
|
41
30
|
/**
|
|
42
31
|
* Result of a single work execution
|
|
43
32
|
*/
|
|
44
|
-
|
|
33
|
+
type WorkResult<TResult = unknown> = {
|
|
45
34
|
status: WorkStatus;
|
|
46
35
|
result?: TResult;
|
|
47
36
|
error?: Error;
|
|
48
37
|
duration: number;
|
|
49
|
-
}
|
|
38
|
+
};
|
|
50
39
|
/**
|
|
51
40
|
* Definition of a work with inferred name and result type
|
|
52
41
|
*/
|
|
@@ -65,20 +54,20 @@ interface IWorkDefinition<TName extends string, TData = Record<string, unknown>,
|
|
|
65
54
|
/**
|
|
66
55
|
* Internal work representation
|
|
67
56
|
*/
|
|
68
|
-
|
|
57
|
+
type WorkflowWork = {
|
|
69
58
|
type: 'serial' | 'parallel';
|
|
70
59
|
works: IWorkDefinition<string, any, any, any>[];
|
|
71
|
-
}
|
|
60
|
+
};
|
|
72
61
|
/**
|
|
73
62
|
* Result of workflow execution
|
|
74
63
|
*/
|
|
75
|
-
|
|
64
|
+
type WorkflowResult<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> = {
|
|
76
65
|
status: WorkflowStatus;
|
|
77
66
|
context: IWorkflowContext<TData, TWorkResults>;
|
|
78
|
-
workResults: Map<keyof TWorkResults,
|
|
67
|
+
workResults: Map<keyof TWorkResults, WorkResult>;
|
|
79
68
|
totalDuration: number;
|
|
80
69
|
error?: Error;
|
|
81
|
-
}
|
|
70
|
+
};
|
|
82
71
|
/**
|
|
83
72
|
* Interface for the Workflow class.
|
|
84
73
|
* Defines all methods available on a workflow before sealing.
|
|
@@ -87,7 +76,7 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
|
|
|
87
76
|
/**
|
|
88
77
|
* The list of works in the workflow (readonly)
|
|
89
78
|
*/
|
|
90
|
-
readonly works: readonly
|
|
79
|
+
readonly works: readonly WorkflowWork[];
|
|
91
80
|
/**
|
|
92
81
|
* The workflow options (readonly)
|
|
93
82
|
*/
|
|
@@ -106,7 +95,9 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
|
|
|
106
95
|
* Seal the workflow to prevent further modifications
|
|
107
96
|
*/
|
|
108
97
|
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
109
|
-
seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults
|
|
98
|
+
seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
|
|
99
|
+
[K in TName]: TResult;
|
|
100
|
+
}>;
|
|
110
101
|
/**
|
|
111
102
|
* Check if the workflow is sealed
|
|
112
103
|
*/
|
|
@@ -114,13 +105,13 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
|
|
|
114
105
|
/**
|
|
115
106
|
* Execute the workflow with initial data
|
|
116
107
|
*/
|
|
117
|
-
run(initialData: TData): Promise<
|
|
108
|
+
run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>>;
|
|
118
109
|
}
|
|
119
110
|
/**
|
|
120
111
|
* A work definition for sealing a workflow.
|
|
121
|
-
*
|
|
112
|
+
* Same as IWorkDefinition - reuses the interface for consistency.
|
|
122
113
|
*/
|
|
123
|
-
type ISealingWorkDefinition<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> =
|
|
114
|
+
type ISealingWorkDefinition<TName extends string, TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = IWorkDefinition<TName, TData, TResult, TWorkResults>;
|
|
124
115
|
/**
|
|
125
116
|
* A sealed workflow that can only be executed, not modified.
|
|
126
117
|
* Use workflow.seal() to create a sealed workflow.
|
|
@@ -132,7 +123,7 @@ type ISealedWorkflow<TData = Record<string, unknown>, TWorkResults extends Recor
|
|
|
132
123
|
/**
|
|
133
124
|
* Options for configuring workflow behavior
|
|
134
125
|
*/
|
|
135
|
-
|
|
126
|
+
type WorkflowOptions = {
|
|
136
127
|
/**
|
|
137
128
|
* Whether to stop execution immediately when a work fails.
|
|
138
129
|
* - true: Stop on first failure (default)
|
|
@@ -140,7 +131,7 @@ interface WorkflowOptions {
|
|
|
140
131
|
* @default true
|
|
141
132
|
*/
|
|
142
133
|
failFast?: boolean;
|
|
143
|
-
}
|
|
134
|
+
};
|
|
144
135
|
/**
|
|
145
136
|
* Helper type to extract work results from parallel works array.
|
|
146
137
|
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
@@ -236,7 +227,7 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
236
227
|
/**
|
|
237
228
|
* The list of works in the workflow (readonly)
|
|
238
229
|
*/
|
|
239
|
-
get works(): readonly
|
|
230
|
+
get works(): readonly WorkflowWork[];
|
|
240
231
|
/**
|
|
241
232
|
* The workflow options (readonly)
|
|
242
233
|
*/
|
|
@@ -288,11 +279,13 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
288
279
|
* ```
|
|
289
280
|
*/
|
|
290
281
|
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
291
|
-
seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults
|
|
282
|
+
seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
|
|
283
|
+
[K in TName]: TResult;
|
|
284
|
+
}>;
|
|
292
285
|
/**
|
|
293
286
|
* Execute the workflow with initial data
|
|
294
287
|
*/
|
|
295
|
-
run(initialData: TData): Promise<
|
|
288
|
+
run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>>;
|
|
296
289
|
/**
|
|
297
290
|
* Execute a single work
|
|
298
291
|
*/
|
|
@@ -303,4 +296,4 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
303
296
|
private executeParallelWorks;
|
|
304
297
|
}
|
|
305
298
|
|
|
306
|
-
export { type ISealedWorkflow, type ISealingWorkDefinition, type IWorkDefinition, type
|
|
299
|
+
export { type ISealedWorkflow, type ISealingWorkDefinition, type IWorkDefinition, type IWorkResultsMap, type IWorkflow, type IWorkflowContext, type ParallelWorksToRecord, Work, type WorkInput, type WorkResult, type WorkStatus, Workflow, type WorkflowOptions, type WorkflowResult, type WorkflowStatus, type WorkflowWork, getWorkDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Work Status
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
PENDING = "pending",
|
|
6
|
-
RUNNING = "running",
|
|
7
|
-
COMPLETED = "completed",
|
|
8
|
-
FAILED = "failed",
|
|
9
|
-
SKIPPED = "skipped"
|
|
10
|
-
}
|
|
4
|
+
type WorkStatus = 'pending' | 'running' | 'completed' | 'failed' | 'skipped';
|
|
11
5
|
/**
|
|
12
6
|
* Workflow Status
|
|
13
7
|
*/
|
|
14
|
-
|
|
15
|
-
PENDING = "pending",
|
|
16
|
-
RUNNING = "running",
|
|
17
|
-
COMPLETED = "completed",
|
|
18
|
-
FAILED = "failed"
|
|
19
|
-
}
|
|
8
|
+
type WorkflowStatus = 'pending' | 'running' | 'completed' | 'failed';
|
|
20
9
|
/**
|
|
21
10
|
* Context passed between workflow works
|
|
22
11
|
* TData is the type of shared data between works
|
|
@@ -33,20 +22,20 @@ interface IWorkflowContext<TData = Record<string, unknown>, TWorkResults extends
|
|
|
33
22
|
*/
|
|
34
23
|
interface IWorkResultsMap<TWorkResults extends Record<string, unknown> = Record<string, unknown>> {
|
|
35
24
|
/** Get a work result with compile-time type checking */
|
|
36
|
-
get<K extends keyof TWorkResults>(name: K):
|
|
37
|
-
set<K extends keyof TWorkResults>(name: K, value:
|
|
25
|
+
get<K extends keyof TWorkResults>(name: K): WorkResult<TWorkResults[K]>;
|
|
26
|
+
set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void;
|
|
38
27
|
/** Check if a work result exists */
|
|
39
28
|
has(name: string): boolean;
|
|
40
29
|
}
|
|
41
30
|
/**
|
|
42
31
|
* Result of a single work execution
|
|
43
32
|
*/
|
|
44
|
-
|
|
33
|
+
type WorkResult<TResult = unknown> = {
|
|
45
34
|
status: WorkStatus;
|
|
46
35
|
result?: TResult;
|
|
47
36
|
error?: Error;
|
|
48
37
|
duration: number;
|
|
49
|
-
}
|
|
38
|
+
};
|
|
50
39
|
/**
|
|
51
40
|
* Definition of a work with inferred name and result type
|
|
52
41
|
*/
|
|
@@ -65,20 +54,20 @@ interface IWorkDefinition<TName extends string, TData = Record<string, unknown>,
|
|
|
65
54
|
/**
|
|
66
55
|
* Internal work representation
|
|
67
56
|
*/
|
|
68
|
-
|
|
57
|
+
type WorkflowWork = {
|
|
69
58
|
type: 'serial' | 'parallel';
|
|
70
59
|
works: IWorkDefinition<string, any, any, any>[];
|
|
71
|
-
}
|
|
60
|
+
};
|
|
72
61
|
/**
|
|
73
62
|
* Result of workflow execution
|
|
74
63
|
*/
|
|
75
|
-
|
|
64
|
+
type WorkflowResult<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> = {
|
|
76
65
|
status: WorkflowStatus;
|
|
77
66
|
context: IWorkflowContext<TData, TWorkResults>;
|
|
78
|
-
workResults: Map<keyof TWorkResults,
|
|
67
|
+
workResults: Map<keyof TWorkResults, WorkResult>;
|
|
79
68
|
totalDuration: number;
|
|
80
69
|
error?: Error;
|
|
81
|
-
}
|
|
70
|
+
};
|
|
82
71
|
/**
|
|
83
72
|
* Interface for the Workflow class.
|
|
84
73
|
* Defines all methods available on a workflow before sealing.
|
|
@@ -87,7 +76,7 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
|
|
|
87
76
|
/**
|
|
88
77
|
* The list of works in the workflow (readonly)
|
|
89
78
|
*/
|
|
90
|
-
readonly works: readonly
|
|
79
|
+
readonly works: readonly WorkflowWork[];
|
|
91
80
|
/**
|
|
92
81
|
* The workflow options (readonly)
|
|
93
82
|
*/
|
|
@@ -106,7 +95,9 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
|
|
|
106
95
|
* Seal the workflow to prevent further modifications
|
|
107
96
|
*/
|
|
108
97
|
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
109
|
-
seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults
|
|
98
|
+
seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
|
|
99
|
+
[K in TName]: TResult;
|
|
100
|
+
}>;
|
|
110
101
|
/**
|
|
111
102
|
* Check if the workflow is sealed
|
|
112
103
|
*/
|
|
@@ -114,13 +105,13 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
|
|
|
114
105
|
/**
|
|
115
106
|
* Execute the workflow with initial data
|
|
116
107
|
*/
|
|
117
|
-
run(initialData: TData): Promise<
|
|
108
|
+
run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>>;
|
|
118
109
|
}
|
|
119
110
|
/**
|
|
120
111
|
* A work definition for sealing a workflow.
|
|
121
|
-
*
|
|
112
|
+
* Same as IWorkDefinition - reuses the interface for consistency.
|
|
122
113
|
*/
|
|
123
|
-
type ISealingWorkDefinition<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> =
|
|
114
|
+
type ISealingWorkDefinition<TName extends string, TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = IWorkDefinition<TName, TData, TResult, TWorkResults>;
|
|
124
115
|
/**
|
|
125
116
|
* A sealed workflow that can only be executed, not modified.
|
|
126
117
|
* Use workflow.seal() to create a sealed workflow.
|
|
@@ -132,7 +123,7 @@ type ISealedWorkflow<TData = Record<string, unknown>, TWorkResults extends Recor
|
|
|
132
123
|
/**
|
|
133
124
|
* Options for configuring workflow behavior
|
|
134
125
|
*/
|
|
135
|
-
|
|
126
|
+
type WorkflowOptions = {
|
|
136
127
|
/**
|
|
137
128
|
* Whether to stop execution immediately when a work fails.
|
|
138
129
|
* - true: Stop on first failure (default)
|
|
@@ -140,7 +131,7 @@ interface WorkflowOptions {
|
|
|
140
131
|
* @default true
|
|
141
132
|
*/
|
|
142
133
|
failFast?: boolean;
|
|
143
|
-
}
|
|
134
|
+
};
|
|
144
135
|
/**
|
|
145
136
|
* Helper type to extract work results from parallel works array.
|
|
146
137
|
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
@@ -236,7 +227,7 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
236
227
|
/**
|
|
237
228
|
* The list of works in the workflow (readonly)
|
|
238
229
|
*/
|
|
239
|
-
get works(): readonly
|
|
230
|
+
get works(): readonly WorkflowWork[];
|
|
240
231
|
/**
|
|
241
232
|
* The workflow options (readonly)
|
|
242
233
|
*/
|
|
@@ -288,11 +279,13 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
288
279
|
* ```
|
|
289
280
|
*/
|
|
290
281
|
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
291
|
-
seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults
|
|
282
|
+
seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
|
|
283
|
+
[K in TName]: TResult;
|
|
284
|
+
}>;
|
|
292
285
|
/**
|
|
293
286
|
* Execute the workflow with initial data
|
|
294
287
|
*/
|
|
295
|
-
run(initialData: TData): Promise<
|
|
288
|
+
run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>>;
|
|
296
289
|
/**
|
|
297
290
|
* Execute a single work
|
|
298
291
|
*/
|
|
@@ -303,4 +296,4 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
303
296
|
private executeParallelWorks;
|
|
304
297
|
}
|
|
305
298
|
|
|
306
|
-
export { type ISealedWorkflow, type ISealingWorkDefinition, type IWorkDefinition, type
|
|
299
|
+
export { type ISealedWorkflow, type ISealingWorkDefinition, type IWorkDefinition, type IWorkResultsMap, type IWorkflow, type IWorkflowContext, type ParallelWorksToRecord, Work, type WorkInput, type WorkResult, type WorkStatus, Workflow, type WorkflowOptions, type WorkflowResult, type WorkflowStatus, type WorkflowWork, getWorkDefinition };
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
// src/workflow.types.ts
|
|
2
|
-
var WorkStatus = /* @__PURE__ */ ((WorkStatus2) => {
|
|
3
|
-
WorkStatus2["PENDING"] = "pending";
|
|
4
|
-
WorkStatus2["RUNNING"] = "running";
|
|
5
|
-
WorkStatus2["COMPLETED"] = "completed";
|
|
6
|
-
WorkStatus2["FAILED"] = "failed";
|
|
7
|
-
WorkStatus2["SKIPPED"] = "skipped";
|
|
8
|
-
return WorkStatus2;
|
|
9
|
-
})(WorkStatus || {});
|
|
10
|
-
var WorkflowStatus = /* @__PURE__ */ ((WorkflowStatus2) => {
|
|
11
|
-
WorkflowStatus2["PENDING"] = "pending";
|
|
12
|
-
WorkflowStatus2["RUNNING"] = "running";
|
|
13
|
-
WorkflowStatus2["COMPLETED"] = "completed";
|
|
14
|
-
WorkflowStatus2["FAILED"] = "failed";
|
|
15
|
-
return WorkflowStatus2;
|
|
16
|
-
})(WorkflowStatus || {});
|
|
17
|
-
|
|
18
1
|
// src/work.ts
|
|
19
2
|
var Work = class {
|
|
20
3
|
constructor(definition) {
|
|
@@ -114,7 +97,13 @@ var Workflow = class {
|
|
|
114
97
|
});
|
|
115
98
|
return this;
|
|
116
99
|
}
|
|
117
|
-
seal(
|
|
100
|
+
seal(sealingWork) {
|
|
101
|
+
if (sealingWork) {
|
|
102
|
+
this._works.push({
|
|
103
|
+
type: "serial",
|
|
104
|
+
works: [sealingWork]
|
|
105
|
+
});
|
|
106
|
+
}
|
|
118
107
|
this._sealed = true;
|
|
119
108
|
return {
|
|
120
109
|
name: "seal",
|
|
@@ -153,7 +142,7 @@ var Workflow = class {
|
|
|
153
142
|
}
|
|
154
143
|
if (collectedErrors.length > 0) {
|
|
155
144
|
return {
|
|
156
|
-
status: "failed"
|
|
145
|
+
status: "failed",
|
|
157
146
|
context,
|
|
158
147
|
workResults,
|
|
159
148
|
totalDuration: Date.now() - startTime,
|
|
@@ -161,14 +150,14 @@ var Workflow = class {
|
|
|
161
150
|
};
|
|
162
151
|
}
|
|
163
152
|
return {
|
|
164
|
-
status: "completed"
|
|
153
|
+
status: "completed",
|
|
165
154
|
context,
|
|
166
155
|
workResults,
|
|
167
156
|
totalDuration: Date.now() - startTime
|
|
168
157
|
};
|
|
169
158
|
} catch (error) {
|
|
170
159
|
return {
|
|
171
|
-
status: "failed"
|
|
160
|
+
status: "failed",
|
|
172
161
|
context,
|
|
173
162
|
workResults,
|
|
174
163
|
totalDuration: Date.now() - startTime,
|
|
@@ -185,7 +174,7 @@ var Workflow = class {
|
|
|
185
174
|
const shouldRun = await work.shouldRun(context);
|
|
186
175
|
if (!shouldRun) {
|
|
187
176
|
const skippedResult = {
|
|
188
|
-
status: "skipped"
|
|
177
|
+
status: "skipped",
|
|
189
178
|
duration: Date.now() - workStartTime
|
|
190
179
|
};
|
|
191
180
|
context.workResults.set(work.name, skippedResult);
|
|
@@ -196,7 +185,7 @@ var Workflow = class {
|
|
|
196
185
|
try {
|
|
197
186
|
const result = await work.execute(context);
|
|
198
187
|
const workResult = {
|
|
199
|
-
status: "completed"
|
|
188
|
+
status: "completed",
|
|
200
189
|
result,
|
|
201
190
|
duration: Date.now() - workStartTime
|
|
202
191
|
};
|
|
@@ -205,7 +194,7 @@ var Workflow = class {
|
|
|
205
194
|
} catch (error) {
|
|
206
195
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
207
196
|
const failedResult = {
|
|
208
|
-
status: "failed"
|
|
197
|
+
status: "failed",
|
|
209
198
|
error: err,
|
|
210
199
|
duration: Date.now() - workStartTime
|
|
211
200
|
};
|
|
@@ -229,7 +218,7 @@ var Workflow = class {
|
|
|
229
218
|
const shouldRun = await work.shouldRun(context);
|
|
230
219
|
if (!shouldRun) {
|
|
231
220
|
const skippedResult = {
|
|
232
|
-
status: "skipped"
|
|
221
|
+
status: "skipped",
|
|
233
222
|
duration: Date.now() - workStartTime
|
|
234
223
|
};
|
|
235
224
|
context.workResults.set(work.name, skippedResult);
|
|
@@ -254,7 +243,7 @@ var Workflow = class {
|
|
|
254
243
|
const duration = Date.now() - result.startTime;
|
|
255
244
|
if ("error" in result && result.error) {
|
|
256
245
|
const workResult = {
|
|
257
|
-
status: "failed"
|
|
246
|
+
status: "failed",
|
|
258
247
|
error: result.error,
|
|
259
248
|
duration
|
|
260
249
|
};
|
|
@@ -265,7 +254,7 @@ var Workflow = class {
|
|
|
265
254
|
}
|
|
266
255
|
} else {
|
|
267
256
|
const workResult = {
|
|
268
|
-
status: "completed"
|
|
257
|
+
status: "completed",
|
|
269
258
|
result: result.result,
|
|
270
259
|
duration
|
|
271
260
|
};
|
|
@@ -285,9 +274,7 @@ var Workflow = class {
|
|
|
285
274
|
};
|
|
286
275
|
export {
|
|
287
276
|
Work,
|
|
288
|
-
WorkStatus,
|
|
289
277
|
Workflow,
|
|
290
|
-
WorkflowStatus,
|
|
291
278
|
getWorkDefinition
|
|
292
279
|
};
|
|
293
280
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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 a work result with compile-time type checking */\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 /** Check if a work result exists */\n has(name: string): 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 /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n silenceError?: boolean;\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\n/**\n * Interface for the Workflow class.\n * Defines all methods available on a workflow before sealing.\n */\nexport interface IWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /**\n * The list of works in the workflow (readonly)\n */\n readonly works: readonly IWorkflowWork[];\n\n /**\n * The workflow options (readonly)\n */\n readonly options: Readonly<Required<WorkflowOptions>>;\n\n /**\n * Add a serial work to the workflow\n */\n serial<TName extends string, TResult>(\n work: IWorkDefinition<TName, TData, TResult, TWorkResults>\n ): IWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n\n /**\n * Add parallel works to the workflow\n */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n parallel(works: readonly IWorkDefinition<string, TData, any, TWorkResults>[]): IWorkflow<\n TData,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n any\n >;\n\n /**\n * Seal the workflow to prevent further modifications\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults>;\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean;\n\n /**\n * Execute the workflow with initial data\n */\n run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;\n}\n\n/**\n * A work definition for sealing a workflow.\n * Similar to IWorkDefinition but without 'name'.\n */\nexport type ISealingWorkDefinition<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n TResult = unknown,\n> = Omit<IWorkDefinition<'seal', TData, TResult, TWorkResults>, 'name'>;\n\n/**\n * A sealed workflow that can only be executed, not modified.\n * Use workflow.seal() to create a sealed workflow.\n * Picks `works`, `options`, `isSealed`, and `run` from IWorkflow, adds `name: 'seal'`.\n */\nexport type ISealedWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = Pick<IWorkflow<TData, TWorkResults>, 'works' | 'options' | 'isSealed' | 'run'> & {\n readonly name: 'seal';\n};\n\n/**\n * Options for configuring workflow behavior\n */\nexport interface WorkflowOptions {\n /**\n * Whether to stop execution immediately when a work fails.\n * - true: Stop on first failure (default)\n * - false: Continue executing remaining works, fail at the end if any work failed\n * @default true\n */\n failFast?: boolean;\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 */\nexport type 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","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 /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\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 this.silenceError = definition.silenceError;\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 IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\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<K extends keyof TWorkResults>(name: K): 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> implements IWorkflow<TData, TWorkResults> {\n private _works: IWorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly IWorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\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 * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n _sealingWork?: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults> {\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n };\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 const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\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 } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: WorkflowStatus.FAILED,\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\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 const failedResult: IWorkResult = {\n status: WorkStatus.FAILED,\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\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 (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\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 // 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 // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\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 // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
|
1
|
+
{"version":3,"sources":["../src/work.ts","../src/workflow.ts"],"sourcesContent":["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 /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\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 this.silenceError = definition.silenceError;\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 WorkflowResult,\n IWorkResultsMap,\n WorkflowWork,\n IWorkDefinition,\n WorkResult,\n IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\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, WorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): WorkResult<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 WorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has<K extends keyof TWorkResults>(name: K): 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> implements IWorkflow<TData, TWorkResults> {\n private _works: WorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly WorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\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 * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TName extends string, TResult>(\n sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n seal<TName extends string, TResult>(\n sealingWork?: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }> {\n // If sealingWork is provided, add it as a final serial work\n if (sealingWork) {\n this._works.push({\n type: 'serial',\n works: [sealingWork],\n });\n }\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n } as ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<WorkflowResult<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, WorkResult>();\n const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\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 } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\n };\n }\n\n return {\n status: 'completed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: '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, WorkResult>\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: WorkResult = {\n status: '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: WorkResult = {\n status: '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 const failedResult: WorkResult = {\n status: 'failed',\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\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 (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\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, WorkResult>\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: WorkResult = {\n status: '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: WorkResult = {\n status: 'failed',\n error: result.error,\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 // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\n } else {\n const workResult: WorkResult = {\n status: '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 // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yigitahmetsahin/workflow-ts",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.4",
|
|
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",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@eslint/js": "^9.39.2",
|
|
51
51
|
"@types/node": "^25.0.10",
|
|
52
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
52
53
|
"eslint": "^9.39.2",
|
|
53
54
|
"eslint-config-prettier": "^10.1.8",
|
|
54
55
|
"prettier": "^3.8.1",
|