1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

Add support for multiple "runAfter" runnables

- Needed so that several dependencies can be declared

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2022-12-01 09:17:18 -05:00
parent f9084bc2b7
commit 5b80dfc70a

View File

@ -3,46 +3,81 @@
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable";
import type { Composite } from "../utils/composite/get-composite/get-composite";
import { getCompositeFor } from "../utils/composite/get-composite/get-composite";
import type { SingleOrMany } from "../utils";
import { getOrInsertSet, isDefined } from "../utils";
import { observable, when } from "mobx";
import * as uuid from "uuid";
import assert from "assert";
export interface Runnable<TParameter = void> {
id: string;
run: Run<TParameter>;
runAfter?: Runnable<TParameter>;
runAfter?: SingleOrMany<Runnable<TParameter>>;
}
type Run<Param> = (parameter: Param) => Promise<void> | void;
export type RunMany = <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => Run<Param>;
async function runCompositeRunnables<Param>(param: Param, composite: Composite<Runnable<Param>>) {
await composite.value.run(param);
await Promise.all(composite.children.map(composite => runCompositeRunnables(param, composite)));
}
const computedNextEdge = (traversed: string[], graph: Map<string, Set<string>>, currentId: string) => {
const currentNode = graph.get(currentId);
assert(currentNode, `Runnable graph does not contain node with id="${currentId}"`);
for (const nextId of currentNode.values()) {
if (traversed.includes(nextId)) {
throw new Error(`Cycle in runnable graph: "${traversed.join(`" -> "`)}" -> "${nextId}"`);
}
computedNextEdge([...traversed, nextId], graph, nextId);
}
};
const verifyRunnablesAreDAG = <Param>(runnables: Runnable<Param>[]) => {
const rootId = uuid.v4();
const runnableGraph = new Map<string, Set<string>>();
// Build the Directed graph
for (const runnable of runnables) {
getOrInsertSet(runnableGraph, runnable.id);
if (!runnable.runAfter) {
getOrInsertSet(runnableGraph, rootId).add(runnable.id);
} else if (Array.isArray(runnable.runAfter)) {
for (const parentRunnable of runnable.runAfter) {
getOrInsertSet(runnableGraph, parentRunnable.id).add(runnable.id);
}
} else {
getOrInsertSet(runnableGraph, runnable.runAfter.id).add(runnable.id);
}
}
// Do a DFS to find any cycles
computedNextEdge([], runnableGraph, rootId);
};
const executeRunnableWith = <Param>(param: Param) => {
const finishedIds = observable.set<string>();
return async (runnable: Runnable<Param>): Promise<void> => {
const parentRunnables = [runnable.runAfter].flat().filter(isDefined);
for (const parentRunnable of parentRunnables) {
await when(() => finishedIds.has(parentRunnable.id));
}
await runnable.run(param);
finishedIds.add(runnable.id);
};
};
export function runManyFor(di: DiContainerForInjection): RunMany {
return <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => async (param: Param) => {
const executeRunnable = executeRunnableWith(param);
const allRunnables = di.injectMany(injectionToken);
const rootId = uuid.v4();
const getCompositeRunnables = getCompositeFor<Runnable<Param>>({
getId: (runnable) => runnable.id,
getParentId: (runnable) => (
runnable.id === rootId
? undefined
: runnable.runAfter?.id ?? rootId
),
});
const composite = getCompositeRunnables([
// This is a dummy runnable to conform to the requirements of `getCompositeFor` to only have one root
{
id: rootId,
run: () => {},
},
...allRunnables,
]);
await runCompositeRunnables(param, composite);
verifyRunnablesAreDAG(allRunnables);
await Promise.all(allRunnables.map(executeRunnable));
};
}