UNPKG

@prostojs/wf

Version:

Generic workflow framework

191 lines (185 loc) 7.22 kB
import { FtringsPool } from '@prostojs/ftring'; type TStepOutput<IR> = void | { inputRequired: IR; expires?: number; errorList?: unknown; }; type TStepHandler<T, I, IR> = ((ctx: T, input: I) => TStepOutput<IR> | StepRetriableError<IR> | Promise<TStepOutput<IR> | StepRetriableError<IR>>); interface TFlowOutput<T, I, IR> { state: { schemaId: string; context: T; indexes: number[]; }; finished: boolean; inputRequired?: IR; interrupt?: boolean; break?: boolean; stepId: string; resume?: ((input: I) => Promise<TFlowOutput<T, unknown, IR>>); retry?: ((input?: I) => Promise<TFlowOutput<T, unknown, IR>>); error?: Error; expires?: number; errorList?: unknown; } type TWorkflowStepConditionFn<T> = ((ctx: T) => boolean | Promise<boolean>); interface TWorkflowStepSchemaObj<T, I> { condition?: string | TWorkflowStepConditionFn<T>; id: string; input?: I; steps?: never; } interface TSubWorkflowSchemaObj<T> { condition?: string | TWorkflowStepConditionFn<T>; while?: string | TWorkflowStepConditionFn<T>; steps: TWorkflowSchema<T>; id?: never; } type TWorkflowControl<T> = { continue: string | TWorkflowStepConditionFn<T>; break?: never; } | { break: string | TWorkflowStepConditionFn<T>; continue?: never; }; type TWorkflowItem<T> = TWorkflowStepSchemaObj<T, any> | TSubWorkflowSchemaObj<T> | TWorkflowControl<T> | string; type TWorkflowSchema<T> = TWorkflowItem<T>[]; declare class StepRetriableError<IR> extends Error { readonly originalError: Error; errorList?: unknown; readonly inputRequired?: IR | undefined; expires?: number | undefined; name: string; constructor(originalError: Error, errorList?: unknown, inputRequired?: IR | undefined, expires?: number | undefined); } /** * Workflow Step * * A minimum action withing workflow * * @example * new Step('step0', (ctx, input) => { * ctx.step0Data = 'completed' * console.log('step0 completed') * }) */ declare class Step<T, I, IR> { readonly id: string; protected handler: string | TStepHandler<T, I, IR>; protected globals: Record<string, unknown>; constructor(id: string, handler: string | TStepHandler<T, I, IR>, globals?: Record<string, unknown>); protected _handler?: TStepHandler<T, I, IR>; getGlobals(ctx: T, input: I): Record<string, unknown>; handle(ctx: T, input: I): TStepOutput<IR> | StepRetriableError<IR> | Promise<TStepOutput<IR> | StepRetriableError<IR>>; } /** * Shortcut for creating a workflow step * @param id step id * @param opts.input optional - instructions for step inputs * @param opts.handler step handler * @returns Step */ declare function createStep<T = any, I = any, IR = any>(id: string, opts: { input?: I; handler: string | TStepHandler<T, I, IR>; }): Step<T, I, IR>; type TWorkflowSpy<T, I, IR> = ((event: string, eventOutput: string | undefined | { fn: string | TWorkflowStepConditionFn<T>; result: boolean; }, flowOutput: TFlowOutput<T, I, IR>, ms?: number) => void); /** * Workflow container * * @example * const steps = [ * createStep('add', { * input: 'number', * handler: 'ctx.result += input', * }), * createStep('mul', { * input: 'number', * handler: 'ctx.result *= input', * }), * createStep('div', { * input: 'number', * handler: 'ctx.result = ctx.result / input', * }), * createStep('error', { * handler: 'ctx.result < 0 ? new StepRetriableError(new Error("test error")) : undefined', * }), * ] * const flow = new Workflow<{ result: number }>(steps) * flow.register('add-mul-div', [ * 'add', 'mul', 'div', * ]) * const result = await flow.start('add-mul-div', { result: 1 }) */ declare class Workflow<T, IR> { protected steps: Step<T, any, IR>[]; protected mappedSteps: Record<string, Step<T, any, IR>>; protected schemas: Record<string, TWorkflowSchema<T>>; protected schemaPrefix: Record<string, string>; protected spies: TWorkflowSpy<T, any, IR>[]; protected fnPool: FtringsPool<Promise<boolean>, unknown>; constructor(steps: Step<T, any, IR>[]); protected resolveStep(stepId: string): Step<T, any, IR>; attachSpy<I = any>(fn: TWorkflowSpy<T, I, IR>): () => void; detachSpy<I = any>(fn: TWorkflowSpy<T, I, IR>): void; addStep<I>(step: Step<T, I, IR>): void; protected emit(spy: TWorkflowSpy<T, any, IR> | undefined, event: string, eventOutput: string | undefined | { fn: string | TWorkflowStepConditionFn<T>; result: boolean; }, flowOutput: TFlowOutput<T, any, IR>, ms?: number): void; /** * Validate that schema refers only to existing step IDs * @param schemaId * @param item */ protected validateSchema(schemaId: string, item: TWorkflowItem<T>, prefix?: string): void; /** * Register flow (sequence of steps) under ID * @param id * @param schema * @param prefix adds to steps that not starting from '/' */ register(id: string, schema: TWorkflowSchema<T>, prefix?: string): void; /** * Start flow by ID * @param schemaId * @param initialContext initial context * @param input initial input (for the first step if required) * @returns */ start<I>(schemaId: string, initialContext: T, input?: I, spy?: TWorkflowSpy<T, I, IR>): Promise<TFlowOutput<T, I, IR>>; protected callConditionFn(spy: TWorkflowSpy<T, any, IR> | undefined, event: string, fn: string | TWorkflowStepConditionFn<T>, result: TFlowOutput<T, unknown, IR>): Promise<boolean>; protected loopInto<I>(event: string, opts: { schemaId: string; schema: TWorkflowSchema<T>; context: T; input?: I; indexes?: number[]; level?: number; spy?: TWorkflowSpy<T, I, IR>; }): Promise<TFlowOutput<T, unknown, IR>>; protected prefixStepId(id: string, prefix?: string): string; protected getItemStepId<T>(item: TWorkflowItem<T>, prefix?: string): string | undefined; protected normalizeWorkflowItem<T, I>(item: TWorkflowItem<T>, prefix?: string): { stepId?: string; input?: I; steps?: TWorkflowSchema<T>; conditionFn?: string | TWorkflowStepConditionFn<T>; continueFn?: string | TWorkflowStepConditionFn<T>; breakFn?: string | TWorkflowStepConditionFn<T>; whileFn?: string | TWorkflowStepConditionFn<T>; }; /** * Resume (re-try) interrupted flow * @param schemaId * @param state.indexes indexes from flowResult.state.indexes * @param state.context context from flowResult.state.context * @param input optional - input for interrupted step * @returns */ resume<I>(state: TFlowOutput<T, I, IR>['state'], input: I, spy?: TWorkflowSpy<T, I, IR>): Promise<TFlowOutput<T, unknown, IR>>; } export { Step, StepRetriableError, type TFlowOutput, type TStepHandler, type TStepOutput, type TSubWorkflowSchemaObj, type TWorkflowControl, type TWorkflowItem, type TWorkflowSchema, type TWorkflowSpy, type TWorkflowStepConditionFn, type TWorkflowStepSchemaObj, Workflow, createStep };