@yigitahmetsahin/workflow-ts 3.3.0 → 3.4.1
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 +35 -4
- package/dist/index.cjs +27 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +101 -25
- package/dist/index.d.ts +101 -25
- package/dist/index.js +27 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -4
package/README.md
CHANGED
|
@@ -184,9 +184,9 @@ const conditionalWork = new Work({
|
|
|
184
184
|
});
|
|
185
185
|
```
|
|
186
186
|
|
|
187
|
-
### `.seal()`
|
|
187
|
+
### `.seal(options?)`
|
|
188
188
|
|
|
189
|
-
Seal the workflow to prevent further modifications. Returns an `ISealedWorkflow` that only exposes the `run()`
|
|
189
|
+
Seal the workflow to prevent further modifications. Returns an `ISealedWorkflow` that only exposes the `run()` and `isSealed()` methods.
|
|
190
190
|
|
|
191
191
|
```typescript
|
|
192
192
|
const sealed = new Workflow<{ userId: string }>()
|
|
@@ -204,19 +204,50 @@ const sealed = new Workflow<{ userId: string }>()
|
|
|
204
204
|
// sealed.serial(...) // ❌ Error: Property 'serial' does not exist
|
|
205
205
|
// sealed.parallel(...) // ❌ Error: Property 'parallel' does not exist
|
|
206
206
|
|
|
207
|
+
// Check if sealed
|
|
208
|
+
console.log(sealed.isSealed()); // true
|
|
209
|
+
|
|
207
210
|
// Only run() is available:
|
|
208
211
|
const result = await sealed.run({ userId: '123' }); // ✅ OK
|
|
209
212
|
```
|
|
210
213
|
|
|
214
|
+
#### With Sealing Work
|
|
215
|
+
|
|
216
|
+
You can pass an `ISealingWorkDefinition` (similar to `IWorkDefinition` but without `name`) to wrap the workflow execution:
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
const workflow = new Workflow<{ userId: string }>()
|
|
220
|
+
.serial({ name: 'fetchUser', execute: async (ctx) => ({ id: ctx.data.userId }) });
|
|
221
|
+
|
|
222
|
+
const sealed = workflow.seal({
|
|
223
|
+
execute: async (ctx) => {
|
|
224
|
+
console.log('Before:', ctx.data.userId);
|
|
225
|
+
const result = await workflow.run(ctx.data);
|
|
226
|
+
console.log('After');
|
|
227
|
+
return result;
|
|
228
|
+
},
|
|
229
|
+
// Optional: supports same options as IWorkDefinition
|
|
230
|
+
// shouldRun: (ctx) => ctx.data.enabled,
|
|
231
|
+
// onError: (error, ctx) => console.error(error),
|
|
232
|
+
// silenceError: true,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Has name 'seal' and custom execute function
|
|
236
|
+
console.log(sealed.name); // 'seal'
|
|
237
|
+
await sealed.execute({ data: { userId: '123' }, workResults: ... }); // Uses custom execute
|
|
238
|
+
await sealed.run({ userId: '123' }); // Bypasses custom execute
|
|
239
|
+
```
|
|
240
|
+
|
|
211
241
|
This is useful when you want to:
|
|
212
242
|
|
|
213
243
|
- **Enforce immutability** - Ensure the workflow definition cannot be accidentally modified after construction
|
|
214
244
|
- **Expose a clean API** - Pass a sealed workflow to other parts of your code that should only execute it, not modify it
|
|
215
245
|
- **Type safety** - Get compile-time errors if someone tries to add more works to a finalized workflow
|
|
246
|
+
- **Wrap execution** - Add logging, metrics, or other cross-cutting concerns via custom execute
|
|
216
247
|
|
|
217
248
|
```typescript
|
|
218
249
|
// Example: Factory function that returns a sealed workflow
|
|
219
|
-
function
|
|
250
|
+
function buildUserWorkflow(): ISealedWorkflow<{ userId: string }, { user: User }> {
|
|
220
251
|
return new Workflow<{ userId: string }>()
|
|
221
252
|
.serial({
|
|
222
253
|
name: 'user',
|
|
@@ -226,7 +257,7 @@ function createUserWorkflow(): ISealedWorkflow<{ userId: string }, { user: User
|
|
|
226
257
|
}
|
|
227
258
|
|
|
228
259
|
// Consumers can only run the workflow
|
|
229
|
-
const workflow =
|
|
260
|
+
const workflow = buildUserWorkflow();
|
|
230
261
|
const result = await workflow.run({ userId: '123' });
|
|
231
262
|
```
|
|
232
263
|
|
package/dist/index.cjs
CHANGED
|
@@ -83,14 +83,25 @@ var WorkResultsMap = class {
|
|
|
83
83
|
var Workflow = class {
|
|
84
84
|
constructor(options = {}) {
|
|
85
85
|
this.works = [];
|
|
86
|
+
this._sealed = false;
|
|
86
87
|
this.options = { failFast: true, ...options };
|
|
87
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if the workflow is sealed
|
|
91
|
+
*/
|
|
92
|
+
isSealed() {
|
|
93
|
+
return this._sealed;
|
|
94
|
+
}
|
|
88
95
|
/**
|
|
89
96
|
* Add a serial work to the workflow.
|
|
90
97
|
* Accepts either an inline work definition or a Work instance.
|
|
91
98
|
* The work name and result type are automatically inferred.
|
|
99
|
+
* @throws Error if the workflow is sealed
|
|
92
100
|
*/
|
|
93
101
|
serial(work) {
|
|
102
|
+
if (this._sealed) {
|
|
103
|
+
throw new Error("Cannot add work to a sealed workflow");
|
|
104
|
+
}
|
|
94
105
|
this.works.push({
|
|
95
106
|
type: "serial",
|
|
96
107
|
works: [getWorkDefinition(work)]
|
|
@@ -101,6 +112,7 @@ var Workflow = class {
|
|
|
101
112
|
* Add parallel works to the workflow.
|
|
102
113
|
* Accepts an array of work definitions or Work instances.
|
|
103
114
|
* All work names and result types are automatically inferred.
|
|
115
|
+
* @throws Error if the workflow is sealed
|
|
104
116
|
*
|
|
105
117
|
* @example
|
|
106
118
|
* ```typescript
|
|
@@ -111,28 +123,25 @@ var Workflow = class {
|
|
|
111
123
|
* ```
|
|
112
124
|
*/
|
|
113
125
|
parallel(works) {
|
|
126
|
+
if (this._sealed) {
|
|
127
|
+
throw new Error("Cannot add work to a sealed workflow");
|
|
128
|
+
}
|
|
114
129
|
this.works.push({
|
|
115
130
|
type: "parallel",
|
|
116
131
|
works: works.map((w) => getWorkDefinition(w))
|
|
117
132
|
});
|
|
118
133
|
return this;
|
|
119
134
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
* // sealed.serial() - TypeScript error! Method doesn't exist
|
|
131
|
-
* // sealed.parallel() - TypeScript error! Method doesn't exist
|
|
132
|
-
* await sealed.run({ userId: '123' }); // OK
|
|
133
|
-
* ```
|
|
134
|
-
*/
|
|
135
|
-
seal() {
|
|
135
|
+
seal(sealingWork) {
|
|
136
|
+
this._sealed = true;
|
|
137
|
+
if (sealingWork?.execute) {
|
|
138
|
+
return {
|
|
139
|
+
name: "seal",
|
|
140
|
+
isSealed: () => this._sealed,
|
|
141
|
+
run: this.run.bind(this),
|
|
142
|
+
execute: sealingWork.execute
|
|
143
|
+
};
|
|
144
|
+
}
|
|
136
145
|
return this;
|
|
137
146
|
}
|
|
138
147
|
/**
|
|
@@ -150,19 +159,9 @@ var Workflow = class {
|
|
|
150
159
|
for (const workGroup of this.works) {
|
|
151
160
|
try {
|
|
152
161
|
if (workGroup.type === "serial") {
|
|
153
|
-
await this.executeWork(
|
|
154
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
155
|
-
workGroup.works[0],
|
|
156
|
-
context,
|
|
157
|
-
workResults
|
|
158
|
-
);
|
|
162
|
+
await this.executeWork(workGroup.works[0], context, workResults);
|
|
159
163
|
} else {
|
|
160
|
-
await this.executeParallelWorks(
|
|
161
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
162
|
-
workGroup.works,
|
|
163
|
-
context,
|
|
164
|
-
workResults
|
|
165
|
-
);
|
|
164
|
+
await this.executeParallelWorks(workGroup.works, context, workResults);
|
|
166
165
|
}
|
|
167
166
|
} catch (error) {
|
|
168
167
|
const err = error instanceof Error ? error : new Error(String(error));
|
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 * A sealed workflow that can only be executed, not modified.\n * Use workflow.seal() to create a sealed workflow.\n */\nexport interface ISealedWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /**\n * Execute the workflow with initial data\n */\n run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;\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 ISealedWorkflow,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, IWorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as IWorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has(name: string): boolean {\n return this.map.has(name as keyof TWorkResults);\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 */\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\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> {\n private works: IWorkflowWork[] = [];\n private options: Required<WorkflowOptions>;\n\n constructor(options: WorkflowOptions = {}) {\n this.options = { failFast: true, ...options };\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 */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n this.works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as unknown as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\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 this.works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)) as unknown as IWorkDefinition<\n string,\n TData,\n unknown,\n TWorkResults\n >[],\n });\n return this as unknown as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * 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.serial() - TypeScript error! Method doesn't exist\n * // sealed.parallel() - TypeScript error! Method doesn't exist\n * await sealed.run({ userId: '123' }); // OK\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults> {\n return this;\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(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n workGroup.works[0] as IWorkDefinition<string, TData, any, any>,\n context,\n workResults\n );\n } else {\n await this.executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n workGroup.works as IWorkDefinition<string, TData, any, any>[],\n context,\n workResults\n );\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\n/**\n * Helper type to extract work results from parallel works array.\n * Since Work implements IWorkDefinition, we can use Extract directly.\n */\ntype ParallelWorksToRecord<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends readonly IWorkDefinition<string, any, any, any>[],\n> = {\n [K in T[number]['name']]: Extract<\n T[number],\n { name: K }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n > extends IWorkDefinition<string, any, infer R, any>\n ? R\n : never;\n};\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
|
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 * 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\n parallel(\n works: readonly IWorkDefinition<string, TData, any, TWorkResults>[]\n ): IWorkflow<TData, any>;\n\n /**\n * Seal the workflow to prevent further modifications\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults>\n ): ISealedWorkflowWithExecute<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 limited work definition for sealing a workflow.\n * Similar to IWorkDefinition but without 'name' (hardcoded as 'seal').\n * The execute function returns IWorkflowResult instead of a custom result type.\n */\nexport type ISealingWorkDefinition<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = Omit<\n IWorkDefinition<'seal', TData, IWorkflowResult<TData, TWorkResults>, TWorkResults>,\n 'name'\n>;\n\n/**\n * A sealed workflow that can only be executed, not modified.\n * Use workflow.seal() to create a sealed workflow.\n */\nexport interface ISealedWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\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 sealed workflow with a custom execute function.\n * Created when seal() is called with an execute option.\n */\nexport interface ISealedWorkflowWithExecute<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> extends ISealedWorkflow<TData, TWorkResults> {\n /**\n * Hardcoded name for the sealed workflow\n */\n readonly name: 'seal';\n\n /**\n * Custom execute function provided during sealing.\n * Receives context (like a Work).\n */\n execute(\n context: IWorkflowContext<TData, TWorkResults>\n ): Promise<IWorkflowResult<TData, TWorkResults>>;\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 ISealedWorkflowWithExecute,\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 * 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 * // Without options - returns ISealedWorkflow\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // With sealing work - returns ISealedWorkflowWithExecute\n * const sealedWithExecute = workflow.seal({\n * execute: async (ctx) => {\n * console.log('Before:', ctx.data);\n * const result = await workflow.run(ctx.data);\n * console.log('After');\n * return result;\n * },\n * // Optional: shouldRun, onError, silenceError (like IWorkDefinition)\n * });\n *\n * sealedWithExecute.name; // 'seal'\n * await sealedWithExecute.execute({ data: initialData, workResults: ... });\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults>\n ): ISealedWorkflowWithExecute<TData, TWorkResults>;\n seal(\n sealingWork?: ISealingWorkDefinition<TData, TWorkResults>\n ): ISealedWorkflow<TData, TWorkResults> | ISealedWorkflowWithExecute<TData, TWorkResults> {\n this._sealed = true;\n if (sealingWork?.execute) {\n return {\n name: 'seal',\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n execute: sealingWork.execute,\n };\n }\n return this;\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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -79,16 +79,91 @@ interface IWorkflowResult<TData = Record<string, unknown>, TWorkResults extends
|
|
|
79
79
|
totalDuration: number;
|
|
80
80
|
error?: Error;
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Interface for the Workflow class.
|
|
84
|
+
* Defines all methods available on a workflow before sealing.
|
|
85
|
+
*/
|
|
86
|
+
interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> {
|
|
87
|
+
/**
|
|
88
|
+
* Add a serial work to the workflow
|
|
89
|
+
*/
|
|
90
|
+
serial<TName extends string, TResult>(work: IWorkDefinition<TName, TData, TResult, TWorkResults>): IWorkflow<TData, TWorkResults & {
|
|
91
|
+
[K in TName]: TResult;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Add parallel works to the workflow
|
|
95
|
+
*/
|
|
96
|
+
parallel(works: readonly IWorkDefinition<string, TData, any, TWorkResults>[]): IWorkflow<TData, any>;
|
|
97
|
+
/**
|
|
98
|
+
* Seal the workflow to prevent further modifications
|
|
99
|
+
*/
|
|
100
|
+
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
101
|
+
seal(sealingWork: ISealingWorkDefinition<TData, TWorkResults>): ISealedWorkflowWithExecute<TData, TWorkResults>;
|
|
102
|
+
/**
|
|
103
|
+
* Check if the workflow is sealed
|
|
104
|
+
*/
|
|
105
|
+
isSealed(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Execute the workflow with initial data
|
|
108
|
+
*/
|
|
109
|
+
run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A limited work definition for sealing a workflow.
|
|
113
|
+
* Similar to IWorkDefinition but without 'name' (hardcoded as 'seal').
|
|
114
|
+
* The execute function returns IWorkflowResult instead of a custom result type.
|
|
115
|
+
*/
|
|
116
|
+
type ISealingWorkDefinition<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> = Omit<IWorkDefinition<'seal', TData, IWorkflowResult<TData, TWorkResults>, TWorkResults>, 'name'>;
|
|
82
117
|
/**
|
|
83
118
|
* A sealed workflow that can only be executed, not modified.
|
|
84
119
|
* Use workflow.seal() to create a sealed workflow.
|
|
85
120
|
*/
|
|
86
121
|
interface ISealedWorkflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> {
|
|
122
|
+
/**
|
|
123
|
+
* Check if the workflow is sealed
|
|
124
|
+
*/
|
|
125
|
+
isSealed(): boolean;
|
|
87
126
|
/**
|
|
88
127
|
* Execute the workflow with initial data
|
|
89
128
|
*/
|
|
90
129
|
run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;
|
|
91
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* A sealed workflow with a custom execute function.
|
|
133
|
+
* Created when seal() is called with an execute option.
|
|
134
|
+
*/
|
|
135
|
+
interface ISealedWorkflowWithExecute<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> extends ISealedWorkflow<TData, TWorkResults> {
|
|
136
|
+
/**
|
|
137
|
+
* Hardcoded name for the sealed workflow
|
|
138
|
+
*/
|
|
139
|
+
readonly name: 'seal';
|
|
140
|
+
/**
|
|
141
|
+
* Custom execute function provided during sealing.
|
|
142
|
+
* Receives context (like a Work).
|
|
143
|
+
*/
|
|
144
|
+
execute(context: IWorkflowContext<TData, TWorkResults>): Promise<IWorkflowResult<TData, TWorkResults>>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Options for configuring workflow behavior
|
|
148
|
+
*/
|
|
149
|
+
interface WorkflowOptions {
|
|
150
|
+
/**
|
|
151
|
+
* Whether to stop execution immediately when a work fails.
|
|
152
|
+
* - true: Stop on first failure (default)
|
|
153
|
+
* - false: Continue executing remaining works, fail at the end if any work failed
|
|
154
|
+
* @default true
|
|
155
|
+
*/
|
|
156
|
+
failFast?: boolean;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Helper type to extract work results from parallel works array.
|
|
160
|
+
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
161
|
+
*/
|
|
162
|
+
type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any, any>[]> = {
|
|
163
|
+
[K in T[number]['name']]: Extract<T[number], {
|
|
164
|
+
name: K;
|
|
165
|
+
}> extends IWorkDefinition<string, any, infer R, any> ? R : never;
|
|
166
|
+
};
|
|
92
167
|
|
|
93
168
|
/**
|
|
94
169
|
* A standalone Work unit that can be added to workflows.
|
|
@@ -167,26 +242,20 @@ declare function getWorkDefinition<TName extends string, TData, TResult, TAvaila
|
|
|
167
242
|
* const result = await workflow.run({ userId: '123' });
|
|
168
243
|
* ```
|
|
169
244
|
*/
|
|
170
|
-
|
|
171
|
-
* Options for configuring workflow behavior
|
|
172
|
-
*/
|
|
173
|
-
interface WorkflowOptions {
|
|
174
|
-
/**
|
|
175
|
-
* Whether to stop execution immediately when a work fails.
|
|
176
|
-
* - true: Stop on first failure (default)
|
|
177
|
-
* - false: Continue executing remaining works, fail at the end if any work failed
|
|
178
|
-
* @default true
|
|
179
|
-
*/
|
|
180
|
-
failFast?: boolean;
|
|
181
|
-
}
|
|
182
|
-
declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = NonNullable<unknown>> {
|
|
245
|
+
declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = NonNullable<unknown>> implements IWorkflow<TData, TWorkResults> {
|
|
183
246
|
private works;
|
|
184
247
|
private options;
|
|
248
|
+
private _sealed;
|
|
185
249
|
constructor(options?: WorkflowOptions);
|
|
250
|
+
/**
|
|
251
|
+
* Check if the workflow is sealed
|
|
252
|
+
*/
|
|
253
|
+
isSealed(): boolean;
|
|
186
254
|
/**
|
|
187
255
|
* Add a serial work to the workflow.
|
|
188
256
|
* Accepts either an inline work definition or a Work instance.
|
|
189
257
|
* The work name and result type are automatically inferred.
|
|
258
|
+
* @throws Error if the workflow is sealed
|
|
190
259
|
*/
|
|
191
260
|
serial<TName extends string, TResult>(work: WorkInput<TName, TData, TResult, TWorkResults>): Workflow<TData, TWorkResults & {
|
|
192
261
|
[K in TName]: TResult;
|
|
@@ -195,6 +264,7 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
195
264
|
* Add parallel works to the workflow.
|
|
196
265
|
* Accepts an array of work definitions or Work instances.
|
|
197
266
|
* All work names and result types are automatically inferred.
|
|
267
|
+
* @throws Error if the workflow is sealed
|
|
198
268
|
*
|
|
199
269
|
* @example
|
|
200
270
|
* ```typescript
|
|
@@ -211,16 +281,31 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
211
281
|
*
|
|
212
282
|
* @example
|
|
213
283
|
* ```typescript
|
|
284
|
+
* // Without options - returns ISealedWorkflow
|
|
214
285
|
* const sealed = new Workflow<{ userId: string }>()
|
|
215
286
|
* .serial({ name: 'step1', execute: async () => 'result' })
|
|
216
287
|
* .seal();
|
|
217
288
|
*
|
|
218
|
-
*
|
|
219
|
-
* // sealed.parallel() - TypeScript error! Method doesn't exist
|
|
289
|
+
* sealed.isSealed(); // true
|
|
220
290
|
* await sealed.run({ userId: '123' }); // OK
|
|
291
|
+
*
|
|
292
|
+
* // With sealing work - returns ISealedWorkflowWithExecute
|
|
293
|
+
* const sealedWithExecute = workflow.seal({
|
|
294
|
+
* execute: async (ctx) => {
|
|
295
|
+
* console.log('Before:', ctx.data);
|
|
296
|
+
* const result = await workflow.run(ctx.data);
|
|
297
|
+
* console.log('After');
|
|
298
|
+
* return result;
|
|
299
|
+
* },
|
|
300
|
+
* // Optional: shouldRun, onError, silenceError (like IWorkDefinition)
|
|
301
|
+
* });
|
|
302
|
+
*
|
|
303
|
+
* sealedWithExecute.name; // 'seal'
|
|
304
|
+
* await sealedWithExecute.execute({ data: initialData, workResults: ... });
|
|
221
305
|
* ```
|
|
222
306
|
*/
|
|
223
307
|
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
308
|
+
seal(sealingWork: ISealingWorkDefinition<TData, TWorkResults>): ISealedWorkflowWithExecute<TData, TWorkResults>;
|
|
224
309
|
/**
|
|
225
310
|
* Execute the workflow with initial data
|
|
226
311
|
*/
|
|
@@ -234,14 +319,5 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
234
319
|
*/
|
|
235
320
|
private executeParallelWorks;
|
|
236
321
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Helper type to extract work results from parallel works array.
|
|
239
|
-
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
240
|
-
*/
|
|
241
|
-
type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any, any>[]> = {
|
|
242
|
-
[K in T[number]['name']]: Extract<T[number], {
|
|
243
|
-
name: K;
|
|
244
|
-
}> extends IWorkDefinition<string, any, infer R, any> ? R : never;
|
|
245
|
-
};
|
|
246
322
|
|
|
247
|
-
export { type ISealedWorkflow, type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, Work, type WorkInput, WorkStatus, Workflow, type WorkflowOptions, WorkflowStatus, getWorkDefinition };
|
|
323
|
+
export { type ISealedWorkflow, type ISealedWorkflowWithExecute, type ISealingWorkDefinition, type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflow, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, type ParallelWorksToRecord, Work, type WorkInput, WorkStatus, Workflow, type WorkflowOptions, WorkflowStatus, getWorkDefinition };
|
package/dist/index.d.ts
CHANGED
|
@@ -79,16 +79,91 @@ interface IWorkflowResult<TData = Record<string, unknown>, TWorkResults extends
|
|
|
79
79
|
totalDuration: number;
|
|
80
80
|
error?: Error;
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Interface for the Workflow class.
|
|
84
|
+
* Defines all methods available on a workflow before sealing.
|
|
85
|
+
*/
|
|
86
|
+
interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> {
|
|
87
|
+
/**
|
|
88
|
+
* Add a serial work to the workflow
|
|
89
|
+
*/
|
|
90
|
+
serial<TName extends string, TResult>(work: IWorkDefinition<TName, TData, TResult, TWorkResults>): IWorkflow<TData, TWorkResults & {
|
|
91
|
+
[K in TName]: TResult;
|
|
92
|
+
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Add parallel works to the workflow
|
|
95
|
+
*/
|
|
96
|
+
parallel(works: readonly IWorkDefinition<string, TData, any, TWorkResults>[]): IWorkflow<TData, any>;
|
|
97
|
+
/**
|
|
98
|
+
* Seal the workflow to prevent further modifications
|
|
99
|
+
*/
|
|
100
|
+
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
101
|
+
seal(sealingWork: ISealingWorkDefinition<TData, TWorkResults>): ISealedWorkflowWithExecute<TData, TWorkResults>;
|
|
102
|
+
/**
|
|
103
|
+
* Check if the workflow is sealed
|
|
104
|
+
*/
|
|
105
|
+
isSealed(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Execute the workflow with initial data
|
|
108
|
+
*/
|
|
109
|
+
run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* A limited work definition for sealing a workflow.
|
|
113
|
+
* Similar to IWorkDefinition but without 'name' (hardcoded as 'seal').
|
|
114
|
+
* The execute function returns IWorkflowResult instead of a custom result type.
|
|
115
|
+
*/
|
|
116
|
+
type ISealingWorkDefinition<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> = Omit<IWorkDefinition<'seal', TData, IWorkflowResult<TData, TWorkResults>, TWorkResults>, 'name'>;
|
|
82
117
|
/**
|
|
83
118
|
* A sealed workflow that can only be executed, not modified.
|
|
84
119
|
* Use workflow.seal() to create a sealed workflow.
|
|
85
120
|
*/
|
|
86
121
|
interface ISealedWorkflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> {
|
|
122
|
+
/**
|
|
123
|
+
* Check if the workflow is sealed
|
|
124
|
+
*/
|
|
125
|
+
isSealed(): boolean;
|
|
87
126
|
/**
|
|
88
127
|
* Execute the workflow with initial data
|
|
89
128
|
*/
|
|
90
129
|
run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;
|
|
91
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* A sealed workflow with a custom execute function.
|
|
133
|
+
* Created when seal() is called with an execute option.
|
|
134
|
+
*/
|
|
135
|
+
interface ISealedWorkflowWithExecute<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>> extends ISealedWorkflow<TData, TWorkResults> {
|
|
136
|
+
/**
|
|
137
|
+
* Hardcoded name for the sealed workflow
|
|
138
|
+
*/
|
|
139
|
+
readonly name: 'seal';
|
|
140
|
+
/**
|
|
141
|
+
* Custom execute function provided during sealing.
|
|
142
|
+
* Receives context (like a Work).
|
|
143
|
+
*/
|
|
144
|
+
execute(context: IWorkflowContext<TData, TWorkResults>): Promise<IWorkflowResult<TData, TWorkResults>>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Options for configuring workflow behavior
|
|
148
|
+
*/
|
|
149
|
+
interface WorkflowOptions {
|
|
150
|
+
/**
|
|
151
|
+
* Whether to stop execution immediately when a work fails.
|
|
152
|
+
* - true: Stop on first failure (default)
|
|
153
|
+
* - false: Continue executing remaining works, fail at the end if any work failed
|
|
154
|
+
* @default true
|
|
155
|
+
*/
|
|
156
|
+
failFast?: boolean;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Helper type to extract work results from parallel works array.
|
|
160
|
+
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
161
|
+
*/
|
|
162
|
+
type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any, any>[]> = {
|
|
163
|
+
[K in T[number]['name']]: Extract<T[number], {
|
|
164
|
+
name: K;
|
|
165
|
+
}> extends IWorkDefinition<string, any, infer R, any> ? R : never;
|
|
166
|
+
};
|
|
92
167
|
|
|
93
168
|
/**
|
|
94
169
|
* A standalone Work unit that can be added to workflows.
|
|
@@ -167,26 +242,20 @@ declare function getWorkDefinition<TName extends string, TData, TResult, TAvaila
|
|
|
167
242
|
* const result = await workflow.run({ userId: '123' });
|
|
168
243
|
* ```
|
|
169
244
|
*/
|
|
170
|
-
|
|
171
|
-
* Options for configuring workflow behavior
|
|
172
|
-
*/
|
|
173
|
-
interface WorkflowOptions {
|
|
174
|
-
/**
|
|
175
|
-
* Whether to stop execution immediately when a work fails.
|
|
176
|
-
* - true: Stop on first failure (default)
|
|
177
|
-
* - false: Continue executing remaining works, fail at the end if any work failed
|
|
178
|
-
* @default true
|
|
179
|
-
*/
|
|
180
|
-
failFast?: boolean;
|
|
181
|
-
}
|
|
182
|
-
declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = NonNullable<unknown>> {
|
|
245
|
+
declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = NonNullable<unknown>> implements IWorkflow<TData, TWorkResults> {
|
|
183
246
|
private works;
|
|
184
247
|
private options;
|
|
248
|
+
private _sealed;
|
|
185
249
|
constructor(options?: WorkflowOptions);
|
|
250
|
+
/**
|
|
251
|
+
* Check if the workflow is sealed
|
|
252
|
+
*/
|
|
253
|
+
isSealed(): boolean;
|
|
186
254
|
/**
|
|
187
255
|
* Add a serial work to the workflow.
|
|
188
256
|
* Accepts either an inline work definition or a Work instance.
|
|
189
257
|
* The work name and result type are automatically inferred.
|
|
258
|
+
* @throws Error if the workflow is sealed
|
|
190
259
|
*/
|
|
191
260
|
serial<TName extends string, TResult>(work: WorkInput<TName, TData, TResult, TWorkResults>): Workflow<TData, TWorkResults & {
|
|
192
261
|
[K in TName]: TResult;
|
|
@@ -195,6 +264,7 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
195
264
|
* Add parallel works to the workflow.
|
|
196
265
|
* Accepts an array of work definitions or Work instances.
|
|
197
266
|
* All work names and result types are automatically inferred.
|
|
267
|
+
* @throws Error if the workflow is sealed
|
|
198
268
|
*
|
|
199
269
|
* @example
|
|
200
270
|
* ```typescript
|
|
@@ -211,16 +281,31 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
211
281
|
*
|
|
212
282
|
* @example
|
|
213
283
|
* ```typescript
|
|
284
|
+
* // Without options - returns ISealedWorkflow
|
|
214
285
|
* const sealed = new Workflow<{ userId: string }>()
|
|
215
286
|
* .serial({ name: 'step1', execute: async () => 'result' })
|
|
216
287
|
* .seal();
|
|
217
288
|
*
|
|
218
|
-
*
|
|
219
|
-
* // sealed.parallel() - TypeScript error! Method doesn't exist
|
|
289
|
+
* sealed.isSealed(); // true
|
|
220
290
|
* await sealed.run({ userId: '123' }); // OK
|
|
291
|
+
*
|
|
292
|
+
* // With sealing work - returns ISealedWorkflowWithExecute
|
|
293
|
+
* const sealedWithExecute = workflow.seal({
|
|
294
|
+
* execute: async (ctx) => {
|
|
295
|
+
* console.log('Before:', ctx.data);
|
|
296
|
+
* const result = await workflow.run(ctx.data);
|
|
297
|
+
* console.log('After');
|
|
298
|
+
* return result;
|
|
299
|
+
* },
|
|
300
|
+
* // Optional: shouldRun, onError, silenceError (like IWorkDefinition)
|
|
301
|
+
* });
|
|
302
|
+
*
|
|
303
|
+
* sealedWithExecute.name; // 'seal'
|
|
304
|
+
* await sealedWithExecute.execute({ data: initialData, workResults: ... });
|
|
221
305
|
* ```
|
|
222
306
|
*/
|
|
223
307
|
seal(): ISealedWorkflow<TData, TWorkResults>;
|
|
308
|
+
seal(sealingWork: ISealingWorkDefinition<TData, TWorkResults>): ISealedWorkflowWithExecute<TData, TWorkResults>;
|
|
224
309
|
/**
|
|
225
310
|
* Execute the workflow with initial data
|
|
226
311
|
*/
|
|
@@ -234,14 +319,5 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
|
|
|
234
319
|
*/
|
|
235
320
|
private executeParallelWorks;
|
|
236
321
|
}
|
|
237
|
-
/**
|
|
238
|
-
* Helper type to extract work results from parallel works array.
|
|
239
|
-
* Since Work implements IWorkDefinition, we can use Extract directly.
|
|
240
|
-
*/
|
|
241
|
-
type ParallelWorksToRecord<T extends readonly IWorkDefinition<string, any, any, any>[]> = {
|
|
242
|
-
[K in T[number]['name']]: Extract<T[number], {
|
|
243
|
-
name: K;
|
|
244
|
-
}> extends IWorkDefinition<string, any, infer R, any> ? R : never;
|
|
245
|
-
};
|
|
246
322
|
|
|
247
|
-
export { type ISealedWorkflow, type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, Work, type WorkInput, WorkStatus, Workflow, type WorkflowOptions, WorkflowStatus, getWorkDefinition };
|
|
323
|
+
export { type ISealedWorkflow, type ISealedWorkflowWithExecute, type ISealingWorkDefinition, type IWorkDefinition, type IWorkResult, type IWorkResultsMap, type IWorkflow, type IWorkflowContext, type IWorkflowResult, type IWorkflowWork, type ParallelWorksToRecord, Work, type WorkInput, WorkStatus, Workflow, type WorkflowOptions, WorkflowStatus, getWorkDefinition };
|
package/dist/index.js
CHANGED
|
@@ -53,14 +53,25 @@ var WorkResultsMap = class {
|
|
|
53
53
|
var Workflow = class {
|
|
54
54
|
constructor(options = {}) {
|
|
55
55
|
this.works = [];
|
|
56
|
+
this._sealed = false;
|
|
56
57
|
this.options = { failFast: true, ...options };
|
|
57
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Check if the workflow is sealed
|
|
61
|
+
*/
|
|
62
|
+
isSealed() {
|
|
63
|
+
return this._sealed;
|
|
64
|
+
}
|
|
58
65
|
/**
|
|
59
66
|
* Add a serial work to the workflow.
|
|
60
67
|
* Accepts either an inline work definition or a Work instance.
|
|
61
68
|
* The work name and result type are automatically inferred.
|
|
69
|
+
* @throws Error if the workflow is sealed
|
|
62
70
|
*/
|
|
63
71
|
serial(work) {
|
|
72
|
+
if (this._sealed) {
|
|
73
|
+
throw new Error("Cannot add work to a sealed workflow");
|
|
74
|
+
}
|
|
64
75
|
this.works.push({
|
|
65
76
|
type: "serial",
|
|
66
77
|
works: [getWorkDefinition(work)]
|
|
@@ -71,6 +82,7 @@ var Workflow = class {
|
|
|
71
82
|
* Add parallel works to the workflow.
|
|
72
83
|
* Accepts an array of work definitions or Work instances.
|
|
73
84
|
* All work names and result types are automatically inferred.
|
|
85
|
+
* @throws Error if the workflow is sealed
|
|
74
86
|
*
|
|
75
87
|
* @example
|
|
76
88
|
* ```typescript
|
|
@@ -81,28 +93,25 @@ var Workflow = class {
|
|
|
81
93
|
* ```
|
|
82
94
|
*/
|
|
83
95
|
parallel(works) {
|
|
96
|
+
if (this._sealed) {
|
|
97
|
+
throw new Error("Cannot add work to a sealed workflow");
|
|
98
|
+
}
|
|
84
99
|
this.works.push({
|
|
85
100
|
type: "parallel",
|
|
86
101
|
works: works.map((w) => getWorkDefinition(w))
|
|
87
102
|
});
|
|
88
103
|
return this;
|
|
89
104
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
* // sealed.serial() - TypeScript error! Method doesn't exist
|
|
101
|
-
* // sealed.parallel() - TypeScript error! Method doesn't exist
|
|
102
|
-
* await sealed.run({ userId: '123' }); // OK
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
|
-
seal() {
|
|
105
|
+
seal(sealingWork) {
|
|
106
|
+
this._sealed = true;
|
|
107
|
+
if (sealingWork?.execute) {
|
|
108
|
+
return {
|
|
109
|
+
name: "seal",
|
|
110
|
+
isSealed: () => this._sealed,
|
|
111
|
+
run: this.run.bind(this),
|
|
112
|
+
execute: sealingWork.execute
|
|
113
|
+
};
|
|
114
|
+
}
|
|
106
115
|
return this;
|
|
107
116
|
}
|
|
108
117
|
/**
|
|
@@ -120,19 +129,9 @@ var Workflow = class {
|
|
|
120
129
|
for (const workGroup of this.works) {
|
|
121
130
|
try {
|
|
122
131
|
if (workGroup.type === "serial") {
|
|
123
|
-
await this.executeWork(
|
|
124
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
125
|
-
workGroup.works[0],
|
|
126
|
-
context,
|
|
127
|
-
workResults
|
|
128
|
-
);
|
|
132
|
+
await this.executeWork(workGroup.works[0], context, workResults);
|
|
129
133
|
} else {
|
|
130
|
-
await this.executeParallelWorks(
|
|
131
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
132
|
-
workGroup.works,
|
|
133
|
-
context,
|
|
134
|
-
workResults
|
|
135
|
-
);
|
|
134
|
+
await this.executeParallelWorks(workGroup.works, context, workResults);
|
|
136
135
|
}
|
|
137
136
|
} catch (error) {
|
|
138
137
|
const err = error instanceof Error ? error : new Error(String(error));
|
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 * A sealed workflow that can only be executed, not modified.\n * Use workflow.seal() to create a sealed workflow.\n */\nexport interface ISealedWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> {\n /**\n * Execute the workflow with initial data\n */\n run(initialData: TData): Promise<IWorkflowResult<TData, TWorkResults>>;\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 ISealedWorkflow,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, IWorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): IWorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as IWorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: IWorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has(name: string): boolean {\n return this.map.has(name as keyof TWorkResults);\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 */\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\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> {\n private works: IWorkflowWork[] = [];\n private options: Required<WorkflowOptions>;\n\n constructor(options: WorkflowOptions = {}) {\n this.options = { failFast: true, ...options };\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 */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n this.works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as unknown as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\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 this.works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)) as unknown as IWorkDefinition<\n string,\n TData,\n unknown,\n TWorkResults\n >[],\n });\n return this as unknown as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * 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.serial() - TypeScript error! Method doesn't exist\n * // sealed.parallel() - TypeScript error! Method doesn't exist\n * await sealed.run({ userId: '123' }); // OK\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults> {\n return this;\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(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n workGroup.works[0] as IWorkDefinition<string, TData, any, any>,\n context,\n workResults\n );\n } else {\n await this.executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n workGroup.works as IWorkDefinition<string, TData, any, any>[],\n context,\n workResults\n );\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\n/**\n * Helper type to extract work results from parallel works array.\n * Since Work implements IWorkDefinition, we can use Extract directly.\n */\ntype ParallelWorksToRecord<\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends readonly IWorkDefinition<string, any, any, any>[],\n> = {\n [K in T[number]['name']]: Extract<\n T[number],\n { name: K }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n > extends IWorkDefinition<string, any, infer R, any>\n ? R\n : never;\n};\n"],"mappings":"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","names":["WorkStatus","WorkflowStatus"]}
|
|
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 * 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\n parallel(\n works: readonly IWorkDefinition<string, TData, any, TWorkResults>[]\n ): IWorkflow<TData, any>;\n\n /**\n * Seal the workflow to prevent further modifications\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults>\n ): ISealedWorkflowWithExecute<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 limited work definition for sealing a workflow.\n * Similar to IWorkDefinition but without 'name' (hardcoded as 'seal').\n * The execute function returns IWorkflowResult instead of a custom result type.\n */\nexport type ISealingWorkDefinition<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = Omit<\n IWorkDefinition<'seal', TData, IWorkflowResult<TData, TWorkResults>, TWorkResults>,\n 'name'\n>;\n\n/**\n * A sealed workflow that can only be executed, not modified.\n * Use workflow.seal() to create a sealed workflow.\n */\nexport interface ISealedWorkflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\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 sealed workflow with a custom execute function.\n * Created when seal() is called with an execute option.\n */\nexport interface ISealedWorkflowWithExecute<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> extends ISealedWorkflow<TData, TWorkResults> {\n /**\n * Hardcoded name for the sealed workflow\n */\n readonly name: 'seal';\n\n /**\n * Custom execute function provided during sealing.\n * Receives context (like a Work).\n */\n execute(\n context: IWorkflowContext<TData, TWorkResults>\n ): Promise<IWorkflowResult<TData, TWorkResults>>;\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 ISealedWorkflowWithExecute,\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 * 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 * // Without options - returns ISealedWorkflow\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // With sealing work - returns ISealedWorkflowWithExecute\n * const sealedWithExecute = workflow.seal({\n * execute: async (ctx) => {\n * console.log('Before:', ctx.data);\n * const result = await workflow.run(ctx.data);\n * console.log('After');\n * return result;\n * },\n * // Optional: shouldRun, onError, silenceError (like IWorkDefinition)\n * });\n *\n * sealedWithExecute.name; // 'seal'\n * await sealedWithExecute.execute({ data: initialData, workResults: ... });\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults>\n ): ISealedWorkflowWithExecute<TData, TWorkResults>;\n seal(\n sealingWork?: ISealingWorkDefinition<TData, TWorkResults>\n ): ISealedWorkflow<TData, TWorkResults> | ISealedWorkflowWithExecute<TData, TWorkResults> {\n this._sealed = true;\n if (sealingWork?.execute) {\n return {\n name: 'seal',\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n execute: sealingWork.execute,\n };\n }\n return this;\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":";AAGO,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,aAAU;AACV,EAAAA,YAAA,aAAU;AACV,EAAAA,YAAA,eAAY;AACZ,EAAAA,YAAA,YAAS;AACT,EAAAA,YAAA,aAAU;AALA,SAAAA;AAAA,GAAA;AAWL,IAAK,iBAAL,kBAAKC,oBAAL;AACL,EAAAA,gBAAA,aAAU;AACV,EAAAA,gBAAA,aAAU;AACV,EAAAA,gBAAA,eAAY;AACZ,EAAAA,gBAAA,YAAS;AAJC,SAAAA;AAAA,GAAA;;;ACML,IAAM,OAAN,MAKoE;AAAA,EAqBzE,YAAY,YAA2E;AACrF,SAAK,OAAO,WAAW;AACvB,SAAK,UAAU,WAAW;AAC1B,SAAK,YAAY,WAAW;AAC5B,SAAK,UAAU,WAAW;AAC1B,SAAK,eAAe,WAAW;AAAA,EACjC;AACF;AAiBO,SAAS,kBAMd,OAC+D;AAC/D,SAAO;AACT;;;AC1DA,IAAM,iBAAN,MAE2C;AAAA,EAF3C;AAGE,SAAQ,MAAM,oBAAI,IAA8C;AAAA;AAAA,EAEhE,IAAkC,MAAuC;AACvE,UAAM,SAAS,KAAK,IAAI,IAAI,IAAI;AAChC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR,gBAAgB,OAAO,IAAI,CAAC;AAAA,MAC9B;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,IAAkC,MAAS,OAA2C;AACpF,SAAK,IAAI,IAAI,MAAM,KAAK;AAAA,EAC1B;AAAA,EAEA,IAAkC,MAAkB;AAClD,WAAO,KAAK,IAAI,IAAI,IAAI;AAAA,EAC1B;AACF;AAqCO,IAAM,WAAN,MAGqC;AAAA,EAK1C,YAAY,UAA2B,CAAC,GAAG;AAJ3C,SAAQ,QAAyB,CAAC;AAElC,SAAQ,UAAU;AAGhB,SAAK,UAAU,EAAE,UAAU,MAAM,GAAG,QAAQ;AAAA,EAC9C;AAAA;AAAA;AAAA;AAAA,EAKA,WAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,OACE,MAC2D;AAC3D,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,SAAK,MAAM,KAAK;AAAA,MACd,MAAM;AAAA,MACN,OAAO,CAAC,kBAAkB,IAAI,CAAC;AAAA,IACjC,CAAC;AACD,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,SACE,OACuE;AACvE,QAAI,KAAK,SAAS;AAChB,YAAM,IAAI,MAAM,sCAAsC;AAAA,IACxD;AACA,SAAK,MAAM,KAAK;AAAA,MACd,MAAM;AAAA,MACN,OAAO,MAAM,IAAI,CAAC,MAAM,kBAAkB,CAAC,CAAC;AAAA,IAC9C,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAmCA,KACE,aACwF;AACxF,SAAK,UAAU;AACf,QAAI,aAAa,SAAS;AACxB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,UAAU,MAAM,KAAK;AAAA,QACrB,KAAK,KAAK,IAAI,KAAK,IAAI;AAAA,QACvB,SAAS,YAAY;AAAA,MACvB;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,IAAI,aAAmE;AAC3E,UAAM,YAAY,KAAK,IAAI;AAC3B,UAAM,UAAiD;AAAA,MACrD,MAAM;AAAA,MACN,aAAa,IAAI,eAA6B;AAAA,IAChD;AACA,UAAM,cAAc,oBAAI,IAAqC;AAC7D,UAAM,kBAA2B,CAAC;AAElC,QAAI;AACF,iBAAW,aAAa,KAAK,OAAO;AAClC,YAAI;AACF,cAAI,UAAU,SAAS,UAAU;AAC/B,kBAAM,KAAK,YAAY,UAAU,MAAM,CAAC,GAAG,SAAS,WAAW;AAAA,UACjE,OAAO;AACL,kBAAM,KAAK,qBAAqB,UAAU,OAAO,SAAS,WAAW;AAAA,UACvE;AAAA,QACF,SAAS,OAAO;AACd,gBAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,cAAI,KAAK,QAAQ,UAAU;AACzB,kBAAM;AAAA,UACR;AACA,0BAAgB,KAAK,GAAG;AAAA,QAC1B;AAAA,MACF;AAGA,UAAI,gBAAgB,SAAS,GAAG;AAC9B,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe,KAAK,IAAI,IAAI;AAAA,UAC5B,OAAO,gBAAgB,CAAC;AAAA,QAC1B;AAAA,MACF;AAEA,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,eAAe,KAAK,IAAI,IAAI;AAAA,MAC9B;AAAA,IACF,SAAS,OAAO;AACd,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,QACA,eAAe,KAAK,IAAI,IAAI;AAAA,QAC5B,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,YAEZ,MACA,SACA,aACe;AACf,UAAM,gBAAgB,KAAK,IAAI;AAG/B,QAAI,KAAK,WAAW;AAClB,YAAM,YAAY,MAAM,KAAK,UAAU,OAAO;AAC9C,UAAI,CAAC,WAAW;AACd,cAAM,gBAA6B;AAAA,UACjC;AAAA,UACA,UAAU,KAAK,IAAI,IAAI;AAAA,QACzB;AAEA,gBAAQ,YAAY,IAAI,KAAK,MAA4B,aAAoB;AAC7E,oBAAY,IAAI,KAAK,MAA4B,aAAa;AAC9D;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,QAAQ,OAAO;AAEzC,YAAM,aAA0B;AAAA,QAC9B;AAAA,QACA;AAAA,QACA,UAAU,KAAK,IAAI,IAAI;AAAA,MACzB;AAIA,cAAQ,YAAY,IAAI,KAAK,MAA4B,UAAiB;AAC1E,kBAAY,IAAI,KAAK,MAA4B,UAAU;AAAA,IAC7D,SAAS,OAAO;AACd,YAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAEpE,YAAM,eAA4B;AAAA,QAChC;AAAA,QACA,OAAO;AAAA,QACP,UAAU,KAAK,IAAI,IAAI;AAAA,MACzB;AAGA,cAAQ,YAAY,IAAI,KAAK,MAA4B,YAAmB;AAC5E,kBAAY,IAAI,KAAK,MAA4B,YAAY;AAG7D,UAAI,KAAK,SAAS;AAChB,cAAM,KAAK,QAAQ,KAAK,OAAO;AAAA,MACjC;AAGA,UAAI,CAAC,KAAK,cAAc;AACtB,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBAEZ,OACA,SACA,aACe;AACf,UAAM,WAAW,MAAM,IAAI,OAAO,SAAS;AACzC,YAAM,gBAAgB,KAAK,IAAI;AAG/B,UAAI,KAAK,WAAW;AAClB,cAAM,YAAY,MAAM,KAAK,UAAU,OAAO;AAC9C,YAAI,CAAC,WAAW;AACd,gBAAM,gBAA6B;AAAA,YACjC;AAAA,YACA,UAAU,KAAK,IAAI,IAAI;AAAA,UACzB;AAEA,kBAAQ,YAAY,IAAI,KAAK,MAA4B,aAAoB;AAC7E,sBAAY,IAAI,KAAK,MAA4B,aAAa;AAC9D,iBAAO,EAAE,MAAM,SAAS,KAAK;AAAA,QAC/B;AAAA,MACF;AAEA,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,QAAQ,OAAO;AACzC,eAAO,EAAE,MAAM,QAAQ,WAAW,cAAc;AAAA,MAClD,SAAS,OAAO;AACd,cAAM,MAAM,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AACpE,eAAO,EAAE,MAAM,OAAO,KAAK,WAAW,cAAc;AAAA,MACtD;AAAA,IACF,CAAC;AAED,UAAM,UAAU,MAAM,QAAQ,IAAI,QAAQ;AAI1C,UAAM,SAA6E,CAAC;AAEpF,eAAW,UAAU,SAAS;AAC5B,UAAI,aAAa,UAAU,OAAO,SAAS;AACzC;AAAA,MACF;AAEA,YAAM,WAAW,KAAK,IAAI,IAAI,OAAO;AAErC,UAAI,WAAW,UAAU,OAAO,OAAO;AACrC,cAAM,aAA0B;AAAA,UAC9B;AAAA,UACA,OAAO,OAAO;AAAA,UACd;AAAA,QACF;AAEA,gBAAQ,YAAY,IAAI,OAAO,KAAK,MAA4B,UAAiB;AACjF,oBAAY,IAAI,OAAO,KAAK,MAA4B,UAAU;AAElE,YAAI,CAAC,OAAO,KAAK,cAAc;AAC7B,iBAAO,KAAK,EAAE,MAAM,OAAO,MAAM,OAAO,OAAO,MAAM,CAAC;AAAA,QACxD;AAAA,MACF,OAAO;AACL,cAAM,aAA0B;AAAA,UAC9B;AAAA,UACA,QAAQ,OAAO;AAAA,UACf;AAAA,QACF;AAEA,gBAAQ,YAAY,IAAI,OAAO,KAAK,MAA4B,UAAiB;AACjF,oBAAY,IAAI,OAAO,KAAK,MAA4B,UAAU;AAAA,MACpE;AAAA,IACF;AAGA,eAAW,UAAU,SAAS;AAC5B,UAAI,WAAW,UAAU,OAAO,SAAS,OAAO,KAAK,SAAS;AAC5D,cAAM,OAAO,KAAK,QAAQ,OAAO,OAAO,OAAO;AAAA,MACjD;AAAA,IACF;AAGA,QAAI,OAAO,SAAS,GAAG;AACrB,YAAM,OAAO,CAAC,EAAE;AAAA,IAClB;AAAA,EACF;AACF;","names":["WorkStatus","WorkflowStatus"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yigitahmetsahin/workflow-ts",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.1",
|
|
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",
|
|
@@ -21,10 +21,9 @@
|
|
|
21
21
|
"test": "vitest run",
|
|
22
22
|
"test:watch": "vitest",
|
|
23
23
|
"test:coverage": "vitest run --coverage",
|
|
24
|
-
"lint": "eslint src examples && tsc --noEmit",
|
|
25
|
-
"lint:fix": "eslint src examples --fix",
|
|
26
24
|
"format": "prettier --write .",
|
|
27
|
-
"
|
|
25
|
+
"lint": "npm run format && eslint src examples --fix && tsc --noEmit",
|
|
26
|
+
"lint:check": "prettier --check . && eslint src examples && tsc --noEmit",
|
|
28
27
|
"prepublishOnly": "npm run build"
|
|
29
28
|
},
|
|
30
29
|
"keywords": [
|