// eslint-disable-next-line eslint-comments/disable-enable-pair /* eslint-disable max-lines */ import { Middleware, Pipeline, isPipeline, createPipeline, AsyncPipeline, MaybeAsync, runWithContainer, createContainer, Container, } from 'farrow-pipeline'; import { Waterfall, Brook, isWaterfall, createWaterfall, AsyncWaterfall, AsyncBrook, isAsyncWaterfall, createAsyncWaterfall, } from '../waterfall'; import { Worker, Workflow, isWorkflow, createWorkflow, AsyncWorker, AsyncWorkflow, isAsyncWorkflow, createAsyncWorkflow, ParallelWorkflow, isParallelWorkflow, createParallelWorkflow, } from '../workflow'; import { RunnerContext, useRunner } from './runner'; // eslint-disable-next-line @typescript-eslint/no-invalid-void-type export type Initializer = () => O | void; const SYNC_PLUGIN_SYMBOL = 'SYNC_PLUGIN_SYMBOL'; export type Plugin = { initializer: Initializer; SYNC_PLUGIN_SYMBOL: typeof SYNC_PLUGIN_SYMBOL; } & Required; export type IndexPlugin = Plugin & { index: number; }; export type Plugins = Plugin[]; export type IndexPlugins = IndexPlugin[]; export type PluginOptions = { name?: string; pre?: string[]; post?: string[]; rivals?: string[]; required?: string[]; }; export type Progress = | Waterfall | AsyncWaterfall | Workflow | AsyncWorkflow | ParallelWorkflow | Pipeline | AsyncPipeline; export type Progress2Thread

= P extends Workflow< infer I, infer O > ? Worker : P extends AsyncWorkflow ? AsyncWorker : P extends ParallelWorkflow ? AsyncWorker : P extends Waterfall ? Brook : P extends AsyncWaterfall ? AsyncBrook : P extends Pipeline ? Middleware : P extends AsyncPipeline ? Middleware> : never; export type ProgressRecord = Record; // eslint-disable-next-line @typescript-eslint/no-invalid-void-type export type Progresses2Threads = { [K in keyof PS]: PS[K] extends Progress ? Progress2Thread : // eslint-disable-next-line @typescript-eslint/no-invalid-void-type PS[K] extends void ? // eslint-disable-next-line @typescript-eslint/no-invalid-void-type void : never; }; export type RunnerFromProgress

= P extends Waterfall< infer I > ? Waterfall['run'] : P extends AsyncWaterfall ? AsyncWaterfall['run'] : P extends Workflow ? Workflow['run'] : P extends AsyncWorkflow ? AsyncWorkflow['run'] : P extends ParallelWorkflow ? ParallelWorkflow['run'] : P extends Pipeline ? Pipeline['run'] : P extends AsyncPipeline ? AsyncPipeline['run'] : never; // eslint-disable-next-line @typescript-eslint/no-invalid-void-type export type Progresses2Runners = { [K in keyof PS]: PS[K] extends Progress ? RunnerFromProgress : // eslint-disable-next-line @typescript-eslint/no-invalid-void-type PS[K] extends void ? // eslint-disable-next-line @typescript-eslint/no-invalid-void-type void : never; }; export type ClearDraftProgress> = { [K in keyof I]: I[K] extends Progress ? I[K] : never; }; export type PluginFromManager> = M extends Manager< infer EP, infer PR > ? Plugin>>> : never; export type InitOptions = { container?: Container; }; export type Manager< EP extends Record, // eslint-disable-next-line @typescript-eslint/no-invalid-void-type PR extends ProgressRecord | void = void, > = { createPlugin: ( initializer: Initializer< Partial>> >, options?: PluginOptions, ) => Plugin>>>; isPlugin: ( input: Record, ) => input is Plugin< Partial>> >; usePlugin: ( ...input: Plugins>>> ) => Manager; init: ( options?: InitOptions, ) => Progresses2Runners>; run: (cb: () => O, options?: InitOptions) => O; registe: (newShape: Partial) => void; clear: () => void; clone: () => Manager; useRunner: () => Progresses2Runners>; }; export const DEFAULT_OPTIONS: Required = { name: 'untitled', pre: [], post: [], rivals: [], required: [], }; export const createManager = < // eslint-disable-next-line @typescript-eslint/ban-types EP extends Record = {}, // eslint-disable-next-line @typescript-eslint/no-invalid-void-type PR extends ProgressRecord | void = void, >( processes?: PR, ): Manager => { let index = 0; const createPlugin: Manager['createPlugin'] = ( initializer, options = {}, ) => ({ ...DEFAULT_OPTIONS, name: `No.${index++} plugin`, ...options, SYNC_PLUGIN_SYMBOL, initializer, }); const isPlugin: Manager['isPlugin'] = ( input, ): input is Plugin< Partial>> > => hasOwnProperty(input, SYNC_PLUGIN_SYMBOL) && input[SYNC_PLUGIN_SYMBOL] === SYNC_PLUGIN_SYMBOL; const registe: Manager['registe'] = extraProcesses => { // eslint-disable-next-line no-param-reassign processes = { ...extraProcesses, ...processes, } as any; }; const clone = () => { let plugins: IndexPlugins< Partial>> > = []; const usePlugin: Manager['usePlugin'] = (...input) => { for (const plugin of input) { if (isPlugin(plugin)) { if (!includePlugin(plugins, plugin)) { plugins.push({ ...plugin, index: plugins.length, }); } } else { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error console.warn(`Unknown plugin: ${plugin.name}`); } } return { createPlugin, isPlugin, usePlugin, init, run, clear, registe, useRunner, clone, }; }; const clear = () => { plugins = []; }; const currentContainer = createContainer(); const init: Manager['init'] = options => { const container = options?.container || currentContainer; const sortedPlugins = sortPlugins(plugins); checkPlugins(sortedPlugins); const hooksList = sortedPlugins.map(plugin => runWithContainer(() => plugin.initializer(), container), ); return generateRunner(hooksList, container, processes); }; const run: Manager['run'] = (cb, options) => { const container = options?.container || currentContainer; return runWithContainer(cb, container); }; return { createPlugin, isPlugin, usePlugin, init, clear, run, registe, useRunner, clone, }; }; return clone(); }; export const generateRunner = < // eslint-disable-next-line @typescript-eslint/ban-types EP extends Record = {}, // eslint-disable-next-line @typescript-eslint/no-invalid-void-type PR extends ProgressRecord | void = void, >( // eslint-disable-next-line @typescript-eslint/no-invalid-void-type hooksList: (void | Partial< Progresses2Threads> >)[], container: Container, processes?: PR, ): Progresses2Runners> => { const runner = {}; const cloneShape = cloneProgressRecord(processes); if (processes) { for (const key in cloneShape) { for (const hooks of hooksList) { if (!hooks) { continue; } if (hooks[key]) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error cloneShape[key].use(hooks[key]); } } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error runner[key] = (input: any, options: any) => (cloneShape[key] as any).run(input, { container, ...options, }); } } container.write(RunnerContext, runner); return runner as any; }; export const cloneProgress = (progress: Progress): Progress => { if (isWaterfall(progress)) { return createWaterfall(); } if (isAsyncWaterfall(progress)) { return createAsyncWaterfall(); } if (isWorkflow(progress)) { return createWorkflow(); } if (isAsyncWorkflow(progress)) { return createAsyncWorkflow(); } if (isParallelWorkflow(progress)) { return createParallelWorkflow(); } if (isPipeline(progress)) { return createPipeline(); } // eslint-disable-next-line @typescript-eslint/restrict-template-expressions throw new Error(`Unknown progress: ${progress}`); }; // eslint-disable-next-line @typescript-eslint/no-invalid-void-type export const cloneProgressRecord = ( record: PR, ): PR => { if (!record) { return record; } const result: PR = {} as any; for (const key in record) { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-expect-error result[key] = cloneProgress(record[key]); } return result; }; const includePlugin = (plugins: Plugins, input: Plugin): boolean => { for (const plugin of plugins) { if (plugin.name === input.name) { return true; } } return false; }; const sortPlugins = (input: IndexPlugins): IndexPlugins => { let plugins = input.slice(); for (let i = 0; i < plugins.length; i++) { const plugin = plugins[i]; for (const pre of plugin.pre) { for (let j = i + 1; j < plugins.length; j++) { if (plugins[j].name === pre) { plugins = [ ...plugins.slice(0, i), plugins[j], ...plugins.slice(i, j), ...plugins.slice(j + 1, plugins.length), ]; } } } for (const post of plugin.post) { for (let j = 0; j < i; j++) { if (plugins[j].name === post) { plugins = [ ...plugins.slice(0, j), ...plugins.slice(j + 1, i + 1), plugins[j], ...plugins.slice(i + 1, plugins.length), ]; } } } } return plugins; }; const checkPlugins = (plugins: Plugins) => { for (const origin of plugins) { for (const rival of origin.rivals) { for (const plugin of plugins) { if (rival === plugin.name) { throw new Error(`${origin.name} has rival ${plugin.name}`); } } } for (const required of origin.required) { if (!plugins.some(plugin => plugin.name === required)) { throw new Error( `The plugin: ${required} is required when plugin: ${origin.name} is exist.`, ); } } } }; export const hasOwnProperty = < X extends Record, Y extends PropertyKey, >( obj: X, prop: Y, ): obj is X & Record => obj.hasOwnProperty(prop);