@prostojs/wf
Version:
Generic workflow framework
191 lines (185 loc) • 7.22 kB
TypeScript
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 };