@yigitahmetsahin/workflow-ts 3.4.3 → 3.4.5

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 CHANGED
@@ -237,6 +237,32 @@ console.log(workflow.name); // 'seal'
237
237
  const result = await workflow.run({ userId: '123' });
238
238
  ```
239
239
 
240
+ #### Seal with Final Work
241
+
242
+ You can pass an execute function to `seal()` that runs as a final serial work after all previous works:
243
+
244
+ ```typescript
245
+ const sealed = new Workflow<{ userId: string }>()
246
+ .serial({ name: 'validate', execute: async (ctx) => true })
247
+ .parallel([
248
+ { name: 'fetchOrders', execute: async () => [{ id: 1 }] },
249
+ { name: 'fetchProfile', execute: async () => ({ name: 'John' }) },
250
+ ])
251
+ .seal({
252
+ name: 'finalize', // required
253
+ execute: async (ctx) => {
254
+ // Access results from previous works
255
+ const orders = ctx.workResults.get('fetchOrders').result;
256
+ const profile = ctx.workResults.get('fetchProfile').result;
257
+ return { orders, profile, summary: 'Done' };
258
+ },
259
+ // Optional: shouldRun, onError, silenceError work just like serial works
260
+ });
261
+
262
+ const result = await sealed.run({ userId: '123' });
263
+ console.log(result.workResults.get('finalize')?.result); // { orders, profile, summary: 'Done' }
264
+ ```
265
+
240
266
  ### Error Silencing
241
267
 
242
268
  Use `silenceError: true` to allow a work to fail without stopping the workflow. The error is still recorded and accessible:
package/dist/index.cjs CHANGED
@@ -125,7 +125,13 @@ var Workflow = class {
125
125
  });
126
126
  return this;
127
127
  }
128
- seal(_sealingWork) {
128
+ seal(sealingWork) {
129
+ if (sealingWork) {
130
+ this._works.push({
131
+ type: "serial",
132
+ works: [sealingWork]
133
+ });
134
+ }
129
135
  this._sealed = true;
130
136
  return {
131
137
  name: "seal",
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/work.ts","../src/workflow.ts"],"sourcesContent":["export * from './workflow';\nexport * from './workflow.types';\nexport * from './work';\n","import { IWorkDefinition, IWorkflowContext } from './workflow.types';\n\n/**\n * A standalone Work unit that can be added to workflows.\n * Implements IWorkDefinition so it can be used anywhere a work definition is expected.\n *\n * @example\n * ```typescript\n * const fetchUser = new Work({\n * name: 'fetchUser',\n * execute: async (ctx) => {\n * return { id: ctx.data.userId, name: 'John' };\n * },\n * });\n *\n * const workflow = new Workflow<{ userId: string }>()\n * .serial(fetchUser)\n * .parallel([work1, work2]);\n * ```\n */\nexport class Work<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n /** Unique name for the work */\n readonly name: TName;\n\n /** Execute function - receives context and returns result */\n readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n\n /** Optional: condition to determine if work should run */\n readonly shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n\n /** Optional: called when work fails */\n readonly onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n\n /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\n\n constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>) {\n this.name = definition.name;\n this.execute = definition.execute;\n this.shouldRun = definition.shouldRun;\n this.onError = definition.onError;\n this.silenceError = definition.silenceError;\n }\n}\n\n/**\n * Type that accepts a work definition (either inline object or Work instance).\n * Since Work implements IWorkDefinition, this is simply IWorkDefinition.\n */\nexport type WorkInput<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;\n\n/**\n * Helper to get the work definition from a WorkInput.\n * Since Work implements IWorkDefinition, this simply returns the input.\n */\nexport function getWorkDefinition<\n TName extends string,\n TData,\n TResult,\n TAvailableWorkResults extends Record<string, unknown>,\n>(\n input: WorkInput<TName, TData, TResult, TAvailableWorkResults>\n): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n return input;\n}\n","import {\n IWorkflowContext,\n WorkflowResult,\n IWorkResultsMap,\n WorkflowWork,\n IWorkDefinition,\n WorkResult,\n IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, WorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): WorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as WorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has<K extends keyof TWorkResults>(name: K): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> implements IWorkflow<TData, TWorkResults> {\n private _works: WorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly WorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\n\n /**\n * Add a serial work to the workflow.\n * Accepts either an inline work definition or a Work instance.\n * The work name and result type are automatically inferred.\n * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n _sealingWork?: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults> {\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n };\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, WorkResult>();\n const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\n };\n }\n\n return {\n status: 'completed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: WorkResult = {\n status: 'completed',\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n const failedResult: WorkResult = {\n status: 'failed',\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: WorkResult = {\n status: 'failed',\n error: result.error,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\n } else {\n const workResult: WorkResult = {\n status: 'completed',\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/work.ts","../src/workflow.ts"],"sourcesContent":["export * from './workflow';\nexport * from './workflow.types';\nexport * from './work';\n","import { IWorkDefinition, IWorkflowContext } from './workflow.types';\n\n/**\n * A standalone Work unit that can be added to workflows.\n * Implements IWorkDefinition so it can be used anywhere a work definition is expected.\n *\n * @example\n * ```typescript\n * const fetchUser = new Work({\n * name: 'fetchUser',\n * execute: async (ctx) => {\n * return { id: ctx.data.userId, name: 'John' };\n * },\n * });\n *\n * const workflow = new Workflow<{ userId: string }>()\n * .serial(fetchUser)\n * .parallel([work1, work2]);\n * ```\n */\nexport class Work<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n /** Unique name for the work */\n readonly name: TName;\n\n /** Execute function - receives context and returns result */\n readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n\n /** Optional: condition to determine if work should run */\n readonly shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n\n /** Optional: called when work fails */\n readonly onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n\n /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\n\n constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>) {\n this.name = definition.name;\n this.execute = definition.execute;\n this.shouldRun = definition.shouldRun;\n this.onError = definition.onError;\n this.silenceError = definition.silenceError;\n }\n}\n\n/**\n * Type that accepts a work definition (either inline object or Work instance).\n * Since Work implements IWorkDefinition, this is simply IWorkDefinition.\n */\nexport type WorkInput<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;\n\n/**\n * Helper to get the work definition from a WorkInput.\n * Since Work implements IWorkDefinition, this simply returns the input.\n */\nexport function getWorkDefinition<\n TName extends string,\n TData,\n TResult,\n TAvailableWorkResults extends Record<string, unknown>,\n>(\n input: WorkInput<TName, TData, TResult, TAvailableWorkResults>\n): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n return input;\n}\n","import {\n IWorkflowContext,\n WorkflowResult,\n IWorkResultsMap,\n WorkflowWork,\n IWorkDefinition,\n WorkResult,\n IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, WorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): WorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as WorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has<K extends keyof TWorkResults>(name: K): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> implements IWorkflow<TData, TWorkResults> {\n private _works: WorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly WorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\n\n /**\n * Add a serial work to the workflow.\n * Accepts either an inline work definition or a Work instance.\n * The work name and result type are automatically inferred.\n * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TName extends string, TResult>(\n sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n seal<TName extends string, TResult>(\n sealingWork?: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }> {\n // If sealingWork is provided, add it as a final serial work\n if (sealingWork) {\n this._works.push({\n type: 'serial',\n works: [sealingWork],\n });\n }\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n } as ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, WorkResult>();\n const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\n };\n }\n\n return {\n status: 'completed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: WorkResult = {\n status: 'completed',\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n const failedResult: WorkResult = {\n status: 'failed',\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: WorkResult = {\n status: 'failed',\n error: result.error,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\n } else {\n const workResult: WorkResult = {\n status: 'completed',\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -95,7 +95,9 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
95
95
  * Seal the workflow to prevent further modifications
96
96
  */
97
97
  seal(): ISealedWorkflow<TData, TWorkResults>;
98
- seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults>;
98
+ seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
99
+ [K in TName]: TResult;
100
+ }>;
99
101
  /**
100
102
  * Check if the workflow is sealed
101
103
  */
@@ -107,9 +109,9 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
107
109
  }
108
110
  /**
109
111
  * A work definition for sealing a workflow.
110
- * Similar to IWorkDefinition but without 'name'.
112
+ * Same as IWorkDefinition - reuses the interface for consistency.
111
113
  */
112
- type ISealingWorkDefinition<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = Omit<IWorkDefinition<'seal', TData, TResult, TWorkResults>, 'name'>;
114
+ type ISealingWorkDefinition<TName extends string, TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = IWorkDefinition<TName, TData, TResult, TWorkResults>;
113
115
  /**
114
116
  * A sealed workflow that can only be executed, not modified.
115
117
  * Use workflow.seal() to create a sealed workflow.
@@ -277,7 +279,9 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
277
279
  * ```
278
280
  */
279
281
  seal(): ISealedWorkflow<TData, TWorkResults>;
280
- seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults>;
282
+ seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
283
+ [K in TName]: TResult;
284
+ }>;
281
285
  /**
282
286
  * Execute the workflow with initial data
283
287
  */
package/dist/index.d.ts CHANGED
@@ -95,7 +95,9 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
95
95
  * Seal the workflow to prevent further modifications
96
96
  */
97
97
  seal(): ISealedWorkflow<TData, TWorkResults>;
98
- seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults>;
98
+ seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
99
+ [K in TName]: TResult;
100
+ }>;
99
101
  /**
100
102
  * Check if the workflow is sealed
101
103
  */
@@ -107,9 +109,9 @@ interface IWorkflow<TData = Record<string, unknown>, TWorkResults extends Record
107
109
  }
108
110
  /**
109
111
  * A work definition for sealing a workflow.
110
- * Similar to IWorkDefinition but without 'name'.
112
+ * Same as IWorkDefinition - reuses the interface for consistency.
111
113
  */
112
- type ISealingWorkDefinition<TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = Omit<IWorkDefinition<'seal', TData, TResult, TWorkResults>, 'name'>;
114
+ type ISealingWorkDefinition<TName extends string, TData = Record<string, unknown>, TWorkResults extends Record<string, unknown> = Record<string, unknown>, TResult = unknown> = IWorkDefinition<TName, TData, TResult, TWorkResults>;
113
115
  /**
114
116
  * A sealed workflow that can only be executed, not modified.
115
117
  * Use workflow.seal() to create a sealed workflow.
@@ -277,7 +279,9 @@ declare class Workflow<TData = Record<string, unknown>, TWorkResults extends Rec
277
279
  * ```
278
280
  */
279
281
  seal(): ISealedWorkflow<TData, TWorkResults>;
280
- seal<TResult>(sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults>;
282
+ seal<TName extends string, TResult>(sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>): ISealedWorkflow<TData, TWorkResults & {
283
+ [K in TName]: TResult;
284
+ }>;
281
285
  /**
282
286
  * Execute the workflow with initial data
283
287
  */
package/dist/index.js CHANGED
@@ -97,7 +97,13 @@ var Workflow = class {
97
97
  });
98
98
  return this;
99
99
  }
100
- seal(_sealingWork) {
100
+ seal(sealingWork) {
101
+ if (sealingWork) {
102
+ this._works.push({
103
+ type: "serial",
104
+ works: [sealingWork]
105
+ });
106
+ }
101
107
  this._sealed = true;
102
108
  return {
103
109
  name: "seal",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/work.ts","../src/workflow.ts"],"sourcesContent":["import { IWorkDefinition, IWorkflowContext } from './workflow.types';\n\n/**\n * A standalone Work unit that can be added to workflows.\n * Implements IWorkDefinition so it can be used anywhere a work definition is expected.\n *\n * @example\n * ```typescript\n * const fetchUser = new Work({\n * name: 'fetchUser',\n * execute: async (ctx) => {\n * return { id: ctx.data.userId, name: 'John' };\n * },\n * });\n *\n * const workflow = new Workflow<{ userId: string }>()\n * .serial(fetchUser)\n * .parallel([work1, work2]);\n * ```\n */\nexport class Work<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n /** Unique name for the work */\n readonly name: TName;\n\n /** Execute function - receives context and returns result */\n readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n\n /** Optional: condition to determine if work should run */\n readonly shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n\n /** Optional: called when work fails */\n readonly onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n\n /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\n\n constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>) {\n this.name = definition.name;\n this.execute = definition.execute;\n this.shouldRun = definition.shouldRun;\n this.onError = definition.onError;\n this.silenceError = definition.silenceError;\n }\n}\n\n/**\n * Type that accepts a work definition (either inline object or Work instance).\n * Since Work implements IWorkDefinition, this is simply IWorkDefinition.\n */\nexport type WorkInput<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;\n\n/**\n * Helper to get the work definition from a WorkInput.\n * Since Work implements IWorkDefinition, this simply returns the input.\n */\nexport function getWorkDefinition<\n TName extends string,\n TData,\n TResult,\n TAvailableWorkResults extends Record<string, unknown>,\n>(\n input: WorkInput<TName, TData, TResult, TAvailableWorkResults>\n): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n return input;\n}\n","import {\n IWorkflowContext,\n WorkflowResult,\n IWorkResultsMap,\n WorkflowWork,\n IWorkDefinition,\n WorkResult,\n IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, WorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): WorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as WorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has<K extends keyof TWorkResults>(name: K): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> implements IWorkflow<TData, TWorkResults> {\n private _works: WorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly WorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\n\n /**\n * Add a serial work to the workflow.\n * Accepts either an inline work definition or a Work instance.\n * The work name and result type are automatically inferred.\n * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n sealingWork: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults>;\n seal<TResult>(\n _sealingWork?: ISealingWorkDefinition<TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults> {\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n };\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, WorkResult>();\n const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\n };\n }\n\n return {\n status: 'completed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: WorkResult = {\n status: 'completed',\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n const failedResult: WorkResult = {\n status: 'failed',\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: WorkResult = {\n status: 'failed',\n error: result.error,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\n } else {\n const workResult: WorkResult = {\n status: 'completed',\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/work.ts","../src/workflow.ts"],"sourcesContent":["import { IWorkDefinition, IWorkflowContext } from './workflow.types';\n\n/**\n * A standalone Work unit that can be added to workflows.\n * Implements IWorkDefinition so it can be used anywhere a work definition is expected.\n *\n * @example\n * ```typescript\n * const fetchUser = new Work({\n * name: 'fetchUser',\n * execute: async (ctx) => {\n * return { id: ctx.data.userId, name: 'John' };\n * },\n * });\n *\n * const workflow = new Workflow<{ userId: string }>()\n * .serial(fetchUser)\n * .parallel([work1, work2]);\n * ```\n */\nexport class Work<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> implements IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n /** Unique name for the work */\n readonly name: TName;\n\n /** Execute function - receives context and returns result */\n readonly execute: (context: IWorkflowContext<TData, TAvailableWorkResults>) => Promise<TResult>;\n\n /** Optional: condition to determine if work should run */\n readonly shouldRun?: (\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => boolean | Promise<boolean>;\n\n /** Optional: called when work fails */\n readonly onError?: (\n error: Error,\n context: IWorkflowContext<TData, TAvailableWorkResults>\n ) => void | Promise<void>;\n\n /** Optional: if true, errors won't stop the workflow (result will be undefined) */\n readonly silenceError?: boolean;\n\n constructor(definition: IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>) {\n this.name = definition.name;\n this.execute = definition.execute;\n this.shouldRun = definition.shouldRun;\n this.onError = definition.onError;\n this.silenceError = definition.silenceError;\n }\n}\n\n/**\n * Type that accepts a work definition (either inline object or Work instance).\n * Since Work implements IWorkDefinition, this is simply IWorkDefinition.\n */\nexport type WorkInput<\n TName extends string,\n TData = Record<string, unknown>,\n TResult = unknown,\n TAvailableWorkResults extends Record<string, unknown> = Record<string, unknown>,\n> = IWorkDefinition<TName, TData, TResult, TAvailableWorkResults>;\n\n/**\n * Helper to get the work definition from a WorkInput.\n * Since Work implements IWorkDefinition, this simply returns the input.\n */\nexport function getWorkDefinition<\n TName extends string,\n TData,\n TResult,\n TAvailableWorkResults extends Record<string, unknown>,\n>(\n input: WorkInput<TName, TData, TResult, TAvailableWorkResults>\n): IWorkDefinition<TName, TData, TResult, TAvailableWorkResults> {\n return input;\n}\n","import {\n IWorkflowContext,\n WorkflowResult,\n IWorkResultsMap,\n WorkflowWork,\n IWorkDefinition,\n WorkResult,\n IWorkflow,\n ISealedWorkflow,\n ISealingWorkDefinition,\n WorkflowOptions,\n ParallelWorksToRecord,\n} from './workflow.types';\nimport { WorkInput, getWorkDefinition } from './work';\n\n/**\n * Internal implementation of IWorkResultsMap using a Map\n */\nclass WorkResultsMap<\n TWorkResults extends Record<string, unknown>,\n> implements IWorkResultsMap<TWorkResults> {\n private map = new Map<keyof TWorkResults, WorkResult<unknown>>();\n\n get<K extends keyof TWorkResults>(name: K): WorkResult<TWorkResults[K]> {\n const result = this.map.get(name);\n if (!result) {\n throw new Error(\n `Work result \"${String(name)}\" not found. This work may not have executed yet.`\n );\n }\n return result as WorkResult<TWorkResults[K]>;\n }\n\n set<K extends keyof TWorkResults>(name: K, value: WorkResult<TWorkResults[K]>): void {\n this.map.set(name, value);\n }\n\n has<K extends keyof TWorkResults>(name: K): boolean {\n return this.map.has(name);\n }\n}\n\n/**\n * A simple, extensible workflow engine that supports serial and parallel work execution.\n * Work names and result types are automatically inferred from the workflow definition.\n *\n * @example\n * ```typescript\n * const workflow = new Workflow<{ userId: string }>()\n * .serial({\n * name: 'validate',\n * execute: async (ctx) => true, // returns boolean\n * })\n * .parallel([\n * {\n * name: 'fetchOrders',\n * execute: async (ctx) => [{ id: 1 }], // returns Order[]\n * },\n * {\n * name: 'fetchProfile',\n * execute: async (ctx) => ({ name: 'John' }), // returns Profile\n * },\n * ])\n * .serial({\n * name: 'process',\n * execute: async (ctx) => {\n * // ✅ Autocomplete for names AND types are inferred!\n * const isValid = ctx.workResults.get('validate').result; // boolean | undefined\n * const orders = ctx.workResults.get('fetchOrders').result; // Order[] | undefined\n * const profile = ctx.workResults.get('fetchProfile').result; // Profile | undefined\n * return { orders, profile };\n * },\n * });\n *\n * const result = await workflow.run({ userId: '123' });\n * ```\n */\nexport class Workflow<\n TData = Record<string, unknown>,\n TWorkResults extends Record<string, unknown> = NonNullable<unknown>,\n> implements IWorkflow<TData, TWorkResults> {\n private _works: WorkflowWork[] = [];\n private _options: Required<WorkflowOptions>;\n private _sealed = false;\n\n constructor(options: WorkflowOptions = {}) {\n this._options = { failFast: true, ...options };\n }\n\n /**\n * The list of works in the workflow (readonly)\n */\n get works(): readonly WorkflowWork[] {\n return this._works;\n }\n\n /**\n * The workflow options (readonly)\n */\n get options(): Readonly<Required<WorkflowOptions>> {\n return this._options;\n }\n\n /**\n * Check if the workflow is sealed\n */\n isSealed(): boolean {\n return this._sealed;\n }\n\n /**\n * Add a serial work to the workflow.\n * Accepts either an inline work definition or a Work instance.\n * The work name and result type are automatically inferred.\n * @throws Error if the workflow is sealed\n */\n serial<TName extends string, TResult>(\n work: WorkInput<TName, TData, TResult, TWorkResults>\n ): Workflow<TData, TWorkResults & { [K in TName]: TResult }> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'serial',\n works: [getWorkDefinition(work)],\n });\n return this as Workflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Add parallel works to the workflow.\n * Accepts an array of work definitions or Work instances.\n * All work names and result types are automatically inferred.\n * @throws Error if the workflow is sealed\n *\n * @example\n * ```typescript\n * workflow.parallel([\n * { name: 'work1', execute: async () => 'result1' },\n * { name: 'work2', execute: async () => 123 },\n * ]);\n * ```\n */\n parallel<const TParallelWorks extends readonly WorkInput<string, TData, unknown, TWorkResults>[]>(\n works: TParallelWorks\n ): Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>> {\n if (this._sealed) {\n throw new Error('Cannot add work to a sealed workflow');\n }\n this._works.push({\n type: 'parallel',\n works: works.map((w) => getWorkDefinition(w)),\n });\n return this as Workflow<TData, TWorkResults & ParallelWorksToRecord<TParallelWorks>>;\n }\n\n /**\n * Seal the workflow to prevent further modifications.\n * Returns a SealedWorkflow that can only be executed with run().\n *\n * @example\n * ```typescript\n * const sealed = new Workflow<{ userId: string }>()\n * .serial({ name: 'step1', execute: async () => 'result' })\n * .seal();\n *\n * sealed.name; // 'seal'\n * sealed.isSealed(); // true\n * await sealed.run({ userId: '123' }); // OK\n *\n * // TypeScript prevents modifications:\n * // sealed.serial(...) // Error: Property 'serial' does not exist\n * ```\n */\n seal(): ISealedWorkflow<TData, TWorkResults>;\n seal<TName extends string, TResult>(\n sealingWork: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n seal<TName extends string, TResult>(\n sealingWork?: ISealingWorkDefinition<TName, TData, TWorkResults, TResult>\n ): ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }> {\n // If sealingWork is provided, add it as a final serial work\n if (sealingWork) {\n this._works.push({\n type: 'serial',\n works: [sealingWork],\n });\n }\n this._sealed = true;\n return {\n name: 'seal',\n works: this._works,\n options: this._options,\n isSealed: () => this._sealed,\n run: this.run.bind(this),\n } as ISealedWorkflow<TData, TWorkResults & { [K in TName]: TResult }>;\n }\n\n /**\n * Execute the workflow with initial data\n */\n async run(initialData: TData): Promise<WorkflowResult<TData, TWorkResults>> {\n const startTime = Date.now();\n const context: IWorkflowContext<TData, TWorkResults> = {\n data: initialData,\n workResults: new WorkResultsMap<TWorkResults>(),\n };\n const workResults = new Map<keyof TWorkResults, WorkResult>();\n const collectedErrors: Error[] = [];\n\n try {\n for (const workGroup of this._works) {\n try {\n if (workGroup.type === 'serial') {\n await this.executeWork(workGroup.works[0], context, workResults);\n } else {\n await this.executeParallelWorks(workGroup.works, context, workResults);\n }\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n if (this._options.failFast) {\n throw err;\n }\n collectedErrors.push(err);\n }\n }\n\n // If failFast is false, check for collected errors\n if (collectedErrors.length > 0) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: collectedErrors[0],\n };\n }\n\n return {\n status: 'completed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n };\n } catch (error) {\n return {\n status: 'failed',\n context,\n workResults,\n totalDuration: Date.now() - startTime,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }\n\n /**\n * Execute a single work\n */\n private async executeWork(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n work: IWorkDefinition<string, TData, any, any>,\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return;\n }\n }\n\n try {\n const result = await work.execute(context);\n\n const workResult: WorkResult = {\n status: 'completed',\n result,\n duration: Date.now() - workStartTime,\n };\n\n // Store result in context for subsequent works\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, workResult as any);\n workResults.set(work.name as keyof TWorkResults, workResult);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n const failedResult: WorkResult = {\n status: 'failed',\n error: err,\n duration: Date.now() - workStartTime,\n };\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, failedResult as any);\n workResults.set(work.name as keyof TWorkResults, failedResult);\n\n // Call error handler if provided\n if (work.onError) {\n await work.onError(err, context);\n }\n\n // Re-throw to stop workflow execution (unless silenceError is true)\n if (!work.silenceError) {\n throw err;\n }\n }\n }\n\n /**\n * Execute multiple works in parallel\n */\n private async executeParallelWorks(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n works: IWorkDefinition<string, TData, any, any>[],\n context: IWorkflowContext<TData, TWorkResults>,\n workResults: Map<keyof TWorkResults, WorkResult>\n ): Promise<void> {\n const promises = works.map(async (work) => {\n const workStartTime = Date.now();\n\n // Check if work should run\n if (work.shouldRun) {\n const shouldRun = await work.shouldRun(context);\n if (!shouldRun) {\n const skippedResult: WorkResult = {\n status: 'skipped',\n duration: Date.now() - workStartTime,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(work.name as keyof TWorkResults, skippedResult as any);\n workResults.set(work.name as keyof TWorkResults, skippedResult);\n return { work, skipped: true };\n }\n }\n\n try {\n const result = await work.execute(context);\n return { work, result, startTime: workStartTime };\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n return { work, error: err, startTime: workStartTime };\n }\n });\n\n const results = await Promise.all(promises);\n\n // Process results and check for errors\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const errors: { work: IWorkDefinition<string, TData, any, any>; error: Error }[] = [];\n\n for (const result of results) {\n if ('skipped' in result && result.skipped) {\n continue;\n }\n\n const duration = Date.now() - result.startTime!;\n\n if ('error' in result && result.error) {\n const workResult: WorkResult = {\n status: 'failed',\n error: result.error,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n // Only track as error if silenceError is not set\n if (!result.work.silenceError) {\n errors.push({ work: result.work, error: result.error });\n }\n } else {\n const workResult: WorkResult = {\n status: 'completed',\n result: result.result,\n duration,\n };\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n context.workResults.set(result.work.name as keyof TWorkResults, workResult as any);\n workResults.set(result.work.name as keyof TWorkResults, workResult);\n }\n }\n\n // Call error handlers for all failed works (including silenced ones)\n for (const result of results) {\n if ('error' in result && result.error && result.work.onError) {\n await result.work.onError(result.error, context);\n }\n }\n\n // Throw the first non-silenced error to stop workflow\n if (errors.length > 0) {\n throw errors[0].error;\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yigitahmetsahin/workflow-ts",
3
- "version": "3.4.3",
3
+ "version": "3.4.5",
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",