import { runWithContainer, createContainer } from 'farrow-pipeline'; import { ProgressRecord, Progresses2Threads, Progresses2Runners, PluginOptions, ClearDraftProgress, InitOptions, generateRunner, hasOwnProperty, DEFAULT_OPTIONS, } from './sync'; import { useRunner } from './runner'; // eslint-disable-next-line @typescript-eslint/no-invalid-void-type export type AsyncInitializer = () => void | O | Promise; const ASYNC_PLUGIN_SYMBOL = 'ASYNC_PLUGIN_SYMBOL'; export type AsyncPlugin = { initializer: AsyncInitializer; ASYNC_PLUGIN_SYMBOL: typeof ASYNC_PLUGIN_SYMBOL; } & Required; export type IndexAsyncPlugin = AsyncPlugin & { index: number; }; export type AsyncPlugins = AsyncPlugin[]; export type AsyncIndexPlugins = IndexAsyncPlugin[]; export type AsyncPluginFromAsyncManager> = M extends AsyncManager ? AsyncPlugin>>> : never; export type PluginFromAsyncManager> = M extends AsyncManager ? AsyncPlugin>>> : never; export type AsyncManager< EP extends Record, // eslint-disable-next-line @typescript-eslint/no-invalid-void-type PR extends ProgressRecord | void = void, > = { createPlugin: ( initializer: AsyncInitializer< Partial>> >, options?: PluginOptions, ) => AsyncPlugin>>>; isPlugin: ( input: Record, ) => input is AsyncPlugin< Partial>> >; usePlugin: ( ...input: AsyncPlugins< Partial>> > ) => AsyncManager; init: ( options?: InitOptions, ) => Promise>>; run: (cb: () => O, options?: InitOptions) => O; registe: (newShape: Partial) => void; clone: () => AsyncManager; clear: () => void; useRunner: () => Progresses2Runners>; }; export const createAsyncManager = < // 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, ): AsyncManager => { let index = 0; const createPlugin: AsyncManager['createPlugin'] = ( initializer, options = {}, ) => ({ ...DEFAULT_OPTIONS, name: `No.${index++} plugin`, ...options, ASYNC_PLUGIN_SYMBOL, initializer, }); const isPlugin: AsyncManager['isPlugin'] = ( input, ): input is AsyncPlugin< Partial>> > => hasOwnProperty(input, ASYNC_PLUGIN_SYMBOL) && input[ASYNC_PLUGIN_SYMBOL] === ASYNC_PLUGIN_SYMBOL; const registe: AsyncManager['registe'] = extraProcesses => { // eslint-disable-next-line no-param-reassign processes = { ...extraProcesses, ...processes, } as any; }; const clone = () => { let plugins: AsyncIndexPlugins< Partial>> > = []; const usePlugin: AsyncManager['usePlugin'] = (...input) => { for (const plugin of input) { if (isPlugin(plugin)) { if (!includeAsyncPlugin(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 manager; }; const clear = () => { plugins = []; }; const currentContainer = createContainer(); const init: AsyncManager['init'] = async options => { const container = options?.container || currentContainer; const sortedPlugins = sortAsyncPlugins(plugins); checkAsyncPlugins(sortedPlugins); const hooksList = await Promise.all( sortedPlugins.map(plugin => runWithContainer(() => plugin.initializer(), container), ), ); return generateRunner(hooksList, container, processes); }; const run: AsyncManager['run'] = (cb, options) => { const container = options?.container || currentContainer; return runWithContainer(cb, container); }; const manager = { createPlugin, isPlugin, usePlugin, init, run, clear, clone, registe, useRunner, }; return manager; }; return clone(); }; const includeAsyncPlugin = ( plugins: AsyncPlugins, input: AsyncPlugin, ): boolean => { for (const plugin of plugins) { if (plugin.name === input.name) { return true; } } return false; }; const sortAsyncPlugins = ( input: AsyncIndexPlugins, ): AsyncIndexPlugins => { 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 checkAsyncPlugins = (plugins: AsyncIndexPlugins) => { 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.`, ); } } } };