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

Convert runMany and runManySync to use injectManyWithMeta

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton 2023-02-27 15:17:32 -05:00
parent ab11b20bd9
commit ecf3eff8d4
6 changed files with 242 additions and 206 deletions

View File

@ -0,0 +1,101 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { DiContainerForInjection, Injectable, InjectionInstanceWithMeta, InjectionToken } from "@ogre-tools/injectable";
import { getOrInsertSetFor, isDefined } from "../utils";
import * as uuid from "uuid";
import assert from "assert";
import type { Runnable, RunnableSync, RunnableSyncWithId, RunnableWithId } from "./types";
const computedNextEdge = (traversed: string[], graph: Map<string, Set<string>>, currentId: string, seenIds: Set<string>) => {
seenIds.add(currentId);
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, seenIds);
}
};
export function verifyRunnablesAreDAG<Param>(injectionToken: InjectionToken<Runnable<Param>, void>, runnables: RunnableWithId<Param>[]): void;
export function verifyRunnablesAreDAG<Param>(injectionToken: InjectionToken<RunnableSync<Param>, void>, runnables: RunnableSyncWithId<Param>[]): void;
export function verifyRunnablesAreDAG<Param>(injectionToken: InjectionToken<Runnable<Param>, void> | InjectionToken<RunnableSync<Param>, void>, runnables: (RunnableWithId<Param>[]) | (RunnableSyncWithId<Param>[])): void {
const rootId = uuid.v4();
const runnableGraph = new Map<string, Set<string>>();
const seenIds = new Set<string>();
const addRunnableId = getOrInsertSetFor(runnableGraph);
// Build the Directed graph
for (const runnable of runnables) {
addRunnableId(runnable.id);
if (runnable.runAfter.length === 0) {
addRunnableId(rootId).add(runnable.id);
} else {
for (const parentRunnable of runnable.runAfter) {
addRunnableId(parentRunnable.id).add(runnable.id);
}
}
}
addRunnableId(rootId);
// Do a DFS to find any cycles
computedNextEdge([], runnableGraph, rootId, seenIds);
for (const id of runnableGraph.keys()) {
if (!seenIds.has(id)) {
const runnable = runnables.find(runnable => runnable.id === id);
if (!runnable) {
throw new Error(`Runnable "${id}" is not part of the injection token "${injectionToken.id}"`);
}
const runAfters = [runnable.runAfter]
.flat()
.filter(isDefined)
.map(runnable => runnable.id)
.join('", "');
throw new Error(`Runnable "${id}" is unreachable for injection token "${injectionToken.id}": run afters "${runAfters}" are a part of different injection tokens.`);
}
}
}
export const convertToWithIdWith = (di: DiContainerForInjection) => {
const convertToWithIdPlain = <Param>(injectable: Injectable<Runnable<Param>, Runnable<Param>, void>): RunnableWithId<Param> => {
const instance = di.inject(injectable);
return ({
id: injectable.id,
run: instance.run,
runAfter: [instance.runAfter]
.flat()
.filter(isDefined)
.map(convertToWithIdPlain),
});
};
function convertToWithId<Param>(src: InjectionInstanceWithMeta<Runnable<Param>>): RunnableWithId<Param>;
function convertToWithId<Param>(src: InjectionInstanceWithMeta<RunnableSync<Param>>): RunnableSyncWithId<Param>;
function convertToWithId<Param>(src: InjectionInstanceWithMeta<Runnable<Param>> | InjectionInstanceWithMeta<RunnableSync<Param>>): RunnableWithId<Param> | RunnableSyncWithId<Param> {
return ({
id: src.meta.id,
run: src.instance.run,
runAfter: [src.instance.runAfter]
.flat()
.filter(isDefined)
.map(convertToWithIdPlain),
});
}
return convertToWithId;
};

View File

@ -5,7 +5,7 @@
import type { AsyncFnMock } from "@async-fn/jest"; import type { AsyncFnMock } from "@async-fn/jest";
import asyncFn from "@async-fn/jest"; import asyncFn from "@async-fn/jest";
import { createContainer, getInjectable, getInjectionToken } from "@ogre-tools/injectable"; import { createContainer, getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { Runnable } from "./run-many-for"; import type { Runnable } from "./types";
import { runManyFor } from "./run-many-for"; import { runManyFor } from "./run-many-for";
import { getPromiseStatus } from "../test-utils/get-promise-status"; import { getPromiseStatus } from "../test-utils/get-promise-status";
import { runInAction } from "mobx"; import { runInAction } from "mobx";
@ -28,7 +28,6 @@ describe("runManyFor", () => {
const someInjectable = getInjectable({ const someInjectable = getInjectable({
id: "some-injectable", id: "some-injectable",
instantiate: () => ({ instantiate: () => ({
id: "some-injectable",
run: () => runMock("some-call"), run: () => runMock("some-call"),
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
@ -37,7 +36,6 @@ describe("runManyFor", () => {
const someOtherInjectable = getInjectable({ const someOtherInjectable = getInjectable({
id: "some-other-injectable", id: "some-other-injectable",
instantiate: () => ({ instantiate: () => ({
id: "some-other-injectable",
run: () => runMock("some-other-call"), run: () => runMock("some-other-call"),
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
@ -85,32 +83,25 @@ describe("runManyFor", () => {
const someInjectable1 = getInjectable({ const someInjectable1 = getInjectable({
id: "some-injectable-1", id: "some-injectable-1",
instantiate: () => ({
instantiate: (di) => ({
id: "some-injectable-1",
run: () => runMock("third-level-run"), run: () => runMock("third-level-run"),
runAfter: di.inject(someInjectable2), runAfter: someInjectable2,
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
}); });
const someInjectable2 = getInjectable({ const someInjectable2 = getInjectable({
id: "some-injectable-2", id: "some-injectable-2",
instantiate: () => ({
instantiate: (di) => ({
id: "some-injectable-2",
run: () => runMock("second-level-run"), run: () => runMock("second-level-run"),
runAfter: di.inject(someInjectable3), runAfter: someInjectable3,
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
}); });
const someInjectable3 = getInjectable({ const someInjectable3 = getInjectable({
id: "some-injectable-3", id: "some-injectable-3",
instantiate: () => ({ instantiate: () => ({
id: "some-injectable-3",
run: () => runMock("first-level-run"), run: () => runMock("first-level-run"),
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
@ -197,24 +188,18 @@ describe("runManyFor", () => {
const someInjectable = getInjectable({ const someInjectable = getInjectable({
id: "some-runnable-1", id: "some-runnable-1",
instantiate: () => ({
instantiate: (di) => ({
id: "some-runnable-1",
run: () => runMock("some-runnable-1"), run: () => runMock("some-runnable-1"),
runAfter: di.inject(someOtherInjectable), runAfter: someOtherInjectable,
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
}); });
const someOtherInjectable = getInjectable({ const someOtherInjectable = getInjectable({
id: "some-runnable-2", id: "some-runnable-2",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-2",
run: () => runMock("some-runnable-2"), run: () => runMock("some-runnable-2"),
}), }),
injectionToken: someOtherInjectionToken, injectionToken: someOtherInjectionToken,
}); });
@ -244,38 +229,29 @@ describe("runManyFor", () => {
const someInjectable = getInjectable({ const someInjectable = getInjectable({
id: "some-runnable-1", id: "some-runnable-1",
instantiate: () => ({
instantiate: (di) => ({
id: "some-runnable-1",
run: () => runMock("some-runnable-1"), run: () => runMock("some-runnable-1"),
runAfter: [ runAfter: [
di.inject(someOtherInjectable), someOtherInjectable,
di.inject(someSecondInjectable), someSecondInjectable,
], ],
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
}); });
const someSecondInjectable = getInjectable({ const someSecondInjectable = getInjectable({
id: "some-runnable-2", id: "some-runnable-2",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-2",
run: () => runMock("some-runnable-2"), run: () => runMock("some-runnable-2"),
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
}); });
const someOtherInjectable = getInjectable({ const someOtherInjectable = getInjectable({
id: "some-runnable-3", id: "some-runnable-3",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-3",
run: () => runMock("some-runnable-3"), run: () => runMock("some-runnable-3"),
}), }),
injectionToken: someOtherInjectionToken, injectionToken: someOtherInjectionToken,
}); });
@ -306,23 +282,17 @@ describe("runManyFor", () => {
const someInjectable = getInjectable({ const someInjectable = getInjectable({
id: "some-runnable-1", id: "some-runnable-1",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-1",
run: (parameter) => runMock("run-of-some-runnable-1", parameter), run: (parameter) => runMock("run-of-some-runnable-1", parameter),
}), }),
injectionToken: someInjectionTokenForRunnablesWithParameter, injectionToken: someInjectionTokenForRunnablesWithParameter,
}); });
const someOtherInjectable = getInjectable({ const someOtherInjectable = getInjectable({
id: "some-runnable-2", id: "some-runnable-2",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-2",
run: (parameter) => runMock("run-of-some-runnable-2", parameter), run: (parameter) => runMock("run-of-some-runnable-2", parameter),
}), }),
injectionToken: someInjectionTokenForRunnablesWithParameter, injectionToken: someInjectionTokenForRunnablesWithParameter,
}); });
@ -359,7 +329,6 @@ describe("runManyFor", () => {
const runnableOneInjectable = getInjectable({ const runnableOneInjectable = getInjectable({
id: "runnable-1", id: "runnable-1",
instantiate: () => ({ instantiate: () => ({
id: "runnable-1",
run: () => runMock("runnable-1"), run: () => runMock("runnable-1"),
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
@ -368,7 +337,6 @@ describe("runManyFor", () => {
const runnableTwoInjectable = getInjectable({ const runnableTwoInjectable = getInjectable({
id: "runnable-2", id: "runnable-2",
instantiate: () => ({ instantiate: () => ({
id: "runnable-2",
run: () => runMock("runnable-2"), run: () => runMock("runnable-2"),
runAfter: [], // shouldn't block being called runAfter: [], // shouldn't block being called
}), }),
@ -377,42 +345,38 @@ describe("runManyFor", () => {
const runnableThreeInjectable = getInjectable({ const runnableThreeInjectable = getInjectable({
id: "runnable-3", id: "runnable-3",
instantiate: (di) => ({ instantiate: () => ({
id: "runnable-3",
run: () => runMock("runnable-3"), run: () => runMock("runnable-3"),
runAfter: di.inject(runnableOneInjectable), runAfter: runnableOneInjectable,
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
}); });
const runnableFourInjectable = getInjectable({ const runnableFourInjectable = getInjectable({
id: "runnable-4", id: "runnable-4",
instantiate: (di) => ({ instantiate: () => ({
id: "runnable-4",
run: () => runMock("runnable-4"), run: () => runMock("runnable-4"),
runAfter: [di.inject(runnableThreeInjectable)], // should be the same as an single item runAfter: [runnableThreeInjectable], // should be the same as an single item
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
}); });
const runnableFiveInjectable = getInjectable({ const runnableFiveInjectable = getInjectable({
id: "runnable-5", id: "runnable-5",
instantiate: (di) => ({ instantiate: () => ({
id: "runnable-5",
run: () => runMock("runnable-5"), run: () => runMock("runnable-5"),
runAfter: di.inject(runnableThreeInjectable), runAfter: runnableThreeInjectable,
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
}); });
const runnableSixInjectable = getInjectable({ const runnableSixInjectable = getInjectable({
id: "runnable-6", id: "runnable-6",
instantiate: (di) => ({ instantiate: () => ({
id: "runnable-6",
run: () => runMock("runnable-6"), run: () => runMock("runnable-6"),
runAfter: [ runAfter: [
di.inject(runnableFourInjectable), runnableFourInjectable,
di.inject(runnableFiveInjectable), runnableFiveInjectable,
], ],
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
@ -420,12 +384,11 @@ describe("runManyFor", () => {
const runnableSevenInjectable = getInjectable({ const runnableSevenInjectable = getInjectable({
id: "runnable-7", id: "runnable-7",
instantiate: (di) => ({ instantiate: () => ({
id: "runnable-7",
run: () => runMock("runnable-7"), run: () => runMock("runnable-7"),
runAfter: [ runAfter: [
di.inject(runnableFiveInjectable), runnableFiveInjectable,
di.inject(runnableSixInjectable), runnableSixInjectable,
], ],
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,

View File

@ -3,84 +3,15 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable"; import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable";
import type { SingleOrMany } from "../utils"; import { getOrInsert } from "../utils";
import { getOrInsert, getOrInsertSetFor, isDefined } from "../utils";
import * as uuid from "uuid";
import assert from "assert";
import type { Asyncify } from "type-fest";
import type TypedEventEmitter from "typed-emitter"; import type TypedEventEmitter from "typed-emitter";
import EventEmitter from "events"; import EventEmitter from "events";
import { convertToWithIdWith, verifyRunnablesAreDAG } from "./helpers";
export interface Runnable<TParameter = void> { import type { RunnableWithId, Runnable, Run } from "./types";
id: string; import type { Asyncify } from "type-fest";
run: Run<TParameter>;
runAfter?: SingleOrMany<Runnable<TParameter>>;
}
type Run<Param> = (parameter: Param) => Promise<void> | void;
export type RunMany = <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => Asyncify<Run<Param>>; export type RunMany = <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => Asyncify<Run<Param>>;
const computedNextEdge = (traversed: string[], graph: Map<string, Set<string>>, currentId: string, seenIds: Set<string>) => {
seenIds.add(currentId);
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, seenIds);
}
};
const verifyRunnablesAreDAG = <Param>(injectionToken: InjectionToken<Runnable<Param>, void>, runnables: Runnable<Param>[]) => {
const rootId = uuid.v4();
const runnableGraph = new Map<string, Set<string>>();
const seenIds = new Set<string>();
const addRunnableId = getOrInsertSetFor(runnableGraph);
// Build the Directed graph
for (const runnable of runnables) {
addRunnableId(runnable.id);
if (!runnable.runAfter || (Array.isArray(runnable.runAfter) && runnable.runAfter.length === 0)) {
addRunnableId(rootId).add(runnable.id);
} else if (Array.isArray(runnable.runAfter)) {
for (const parentRunnable of runnable.runAfter) {
addRunnableId(parentRunnable.id).add(runnable.id);
}
} else {
addRunnableId(runnable.runAfter.id).add(runnable.id);
}
}
addRunnableId(rootId);
// Do a DFS to find any cycles
computedNextEdge([], runnableGraph, rootId, seenIds);
for (const id of runnableGraph.keys()) {
if (!seenIds.has(id)) {
const runnable = runnables.find(runnable => runnable.id === id);
if (!runnable) {
throw new Error(`Runnable "${id}" is not part of the injection token "${injectionToken.id}"`);
}
const runAfters = [runnable.runAfter]
.flat()
.filter(isDefined)
.map(runnable => runnable.id)
.join('", "');
throw new Error(`Runnable "${id}" is unreachable for injection token "${injectionToken.id}": run afters "${runAfters}" are a part of different injection tokens.`);
}
}
};
interface BarrierEvent { interface BarrierEvent {
finish: (id: string) => void; finish: (id: string) => void;
} }
@ -116,10 +47,8 @@ class DynamicBarrier {
const executeRunnableWith = <Param>(param: Param) => { const executeRunnableWith = <Param>(param: Param) => {
const barrier = new DynamicBarrier(); const barrier = new DynamicBarrier();
return async (runnable: Runnable<Param>): Promise<void> => { return async (runnable: RunnableWithId<Param>): Promise<void> => {
const parentRunnables = [runnable.runAfter].flat().filter(isDefined); for (const parentRunnable of runnable.runAfter) {
for (const parentRunnable of parentRunnables) {
await barrier.blockOn(parentRunnable.id); await barrier.blockOn(parentRunnable.id);
} }
@ -129,9 +58,11 @@ const executeRunnableWith = <Param>(param: Param) => {
}; };
export function runManyFor(di: DiContainerForInjection): RunMany { export function runManyFor(di: DiContainerForInjection): RunMany {
const convertToWithId = convertToWithIdWith(di);
return <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => async (param: Param) => { return <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => async (param: Param) => {
const executeRunnable = executeRunnableWith(param); const executeRunnable = executeRunnableWith(param);
const allRunnables = di.injectMany(injectionToken); const allRunnables = di.injectManyWithMeta(injectionToken).map(x => convertToWithId(x));
verifyRunnablesAreDAG(injectionToken, allRunnables); verifyRunnablesAreDAG(injectionToken, allRunnables);

View File

@ -3,8 +3,8 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import { createContainer, getInjectable, getInjectionToken } from "@ogre-tools/injectable"; import { createContainer, getInjectable, getInjectionToken } from "@ogre-tools/injectable";
import type { RunnableSync } from "./run-many-sync-for";
import { runManySyncFor } from "./run-many-sync-for"; import { runManySyncFor } from "./run-many-sync-for";
import type { RunnableSync } from "./types";
describe("runManySyncFor", () => { describe("runManySyncFor", () => {
describe("given hierarchy, when running many", () => { describe("given hierarchy, when running many", () => {
@ -22,7 +22,6 @@ describe("runManySyncFor", () => {
const someInjectable = getInjectable({ const someInjectable = getInjectable({
id: "some-injectable", id: "some-injectable",
instantiate: () => ({ instantiate: () => ({
id: "some-injectable",
run: () => runMock("some-call"), run: () => runMock("some-call"),
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
@ -31,7 +30,6 @@ describe("runManySyncFor", () => {
const someOtherInjectable = getInjectable({ const someOtherInjectable = getInjectable({
id: "some-other-injectable", id: "some-other-injectable",
instantiate: () => ({ instantiate: () => ({
id: "some-other-injectable",
run: () => runMock("some-other-call"), run: () => runMock("some-other-call"),
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
@ -66,32 +64,25 @@ describe("runManySyncFor", () => {
const someInjectable1 = getInjectable({ const someInjectable1 = getInjectable({
id: "some-injectable-1", id: "some-injectable-1",
instantiate: () => ({
instantiate: (di) => ({
id: "some-injectable-1",
run: () => void runMock("third-level-run"), run: () => void runMock("third-level-run"),
runAfter: di.inject(someInjectable2), runAfter: someInjectable2,
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
}); });
const someInjectable2 = getInjectable({ const someInjectable2 = getInjectable({
id: "some-injectable-2", id: "some-injectable-2",
instantiate: () => ({
instantiate: (di) => ({
id: "some-injectable-2",
run: () => void runMock("second-level-run"), run: () => void runMock("second-level-run"),
runAfter: di.inject(someInjectable3), runAfter: someInjectable3,
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
}); });
const someInjectable3 = getInjectable({ const someInjectable3 = getInjectable({
id: "some-injectable-3", id: "some-injectable-3",
instantiate: () => ({ instantiate: () => ({
id: "some-injectable-3",
run: () => void runMock("first-level-run"), run: () => void runMock("first-level-run"),
}), }),
injectionToken: someInjectionTokenForRunnables, injectionToken: someInjectionTokenForRunnables,
@ -124,24 +115,18 @@ describe("runManySyncFor", () => {
const someInjectable = getInjectable({ const someInjectable = getInjectable({
id: "some-runnable-1", id: "some-runnable-1",
instantiate: () => ({
instantiate: (di) => ({
id: "some-runnable-1",
run: () => runMock("some-runnable-1"), run: () => runMock("some-runnable-1"),
runAfter: di.inject(someOtherInjectable), runAfter: someOtherInjectable,
}), }),
injectionToken: someInjectionToken, injectionToken: someInjectionToken,
}); });
const someOtherInjectable = getInjectable({ const someOtherInjectable = getInjectable({
id: "some-runnable-2", id: "some-runnable-2",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-2",
run: () => runMock("some-runnable-2"), run: () => runMock("some-runnable-2"),
}), }),
injectionToken: someOtherInjectionToken, injectionToken: someOtherInjectionToken,
}); });
@ -152,7 +137,7 @@ describe("runManySyncFor", () => {
); );
return expect(() => runMany()).toThrow( return expect(() => runMany()).toThrow(
/Tried to get a composite but encountered missing parent ids: "some-runnable-2".\n\nAvailable parent ids are:\n"[0-9a-z-]+",\n"some-runnable-1"/, /Runnable "some-runnable-1" is unreachable for injection token "some-injection-token": run afters "some-runnable-2" are a part of different injection tokens./,
); );
}); });
@ -172,23 +157,17 @@ describe("runManySyncFor", () => {
const someInjectable = getInjectable({ const someInjectable = getInjectable({
id: "some-runnable-1", id: "some-runnable-1",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-1",
run: (parameter) => void runMock("run-of-some-runnable-1", parameter), run: (parameter) => void runMock("run-of-some-runnable-1", parameter),
}), }),
injectionToken: someInjectionTokenForRunnablesWithParameter, injectionToken: someInjectionTokenForRunnablesWithParameter,
}); });
const someOtherInjectable = getInjectable({ const someOtherInjectable = getInjectable({
id: "some-runnable-2", id: "some-runnable-2",
instantiate: () => ({ instantiate: () => ({
id: "some-runnable-2",
run: (parameter) => void runMock("run-of-some-runnable-2", parameter), run: (parameter) => void runMock("run-of-some-runnable-2", parameter),
}), }),
injectionToken: someInjectionTokenForRunnablesWithParameter, injectionToken: someInjectionTokenForRunnablesWithParameter,
}); });

View File

@ -3,53 +3,75 @@
* Licensed under MIT License. See LICENSE in root directory for more information. * Licensed under MIT License. See LICENSE in root directory for more information.
*/ */
import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable"; import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable";
import type { Composite } from "../utils/composite/get-composite/get-composite"; import type { Disposer } from "../utils";
import { getCompositeFor } from "../utils/composite/get-composite/get-composite"; import type { RunnableSync, RunSync, RunnableSyncWithId } from "./types";
import * as uuid from "uuid"; import { convertToWithIdWith, verifyRunnablesAreDAG } from "./helpers";
import type TypedEventEmitter from "typed-emitter";
export interface RunnableSync<TParameter = void> { import EventEmitter from "events";
id: string;
run: RunSync<TParameter>;
runAfter?: RunnableSync<TParameter>;
}
/**
* NOTE: this is the worse of two evils. This makes sure that `RunnableSync` always is sync.
* If the return type is `void` instead then async functions (those return `Promise<T>`) can
* coerce to it.
*/
type RunSync<Param> = (parameter: Param) => undefined;
export type RunManySync = <Param>(injectionToken: InjectionToken<RunnableSync<Param>, void>) => RunSync<Param>; export type RunManySync = <Param>(injectionToken: InjectionToken<RunnableSync<Param>, void>) => RunSync<Param>;
function runCompositeRunnableSyncs<Param>(param: Param, composite: Composite<RunnableSync<Param>>): undefined { class SyncBarrier {
composite.value.run(param); private readonly finishedIds = new Set<string>();
composite.children.map(composite => runCompositeRunnableSyncs(param, composite)); private readonly events: TypedEventEmitter<Record<string, () => void>> = new EventEmitter();
return undefined; setFinished(id: string): void {
this.finishedIds.add(id);
this.events.emit(id);
}
onceParentsAreFinished(id: string, parentIds: string[], action: () => void) {
const finishers = new Map<string, Disposer>();
const checkAndRun = () => {
if (finishers.size === 0) {
action();
this.setFinished(id);
}
};
for (const parentId of parentIds) {
if (this.finishedIds.has(parentId)) {
continue;
}
const onParentFinished = () => {
this.events.removeListener(parentId, onParentFinished);
finishers.delete(parentId);
checkAndRun();
};
finishers.set(parentId, onParentFinished);
this.events.once(parentId, onParentFinished);
}
checkAndRun();
}
} }
const executeRunnableWith = <Param>(param: Param) => {
const barrier = new SyncBarrier();
return (runnable: RunnableSyncWithId<Param>) => {
barrier.onceParentsAreFinished(
runnable.id,
runnable.runAfter.map(r => r.id),
() => runnable.run(param),
);
};
};
export function runManySyncFor(di: DiContainerForInjection): RunManySync { export function runManySyncFor(di: DiContainerForInjection): RunManySync {
return <Param>(injectionToken: InjectionToken<RunnableSync<Param>, void>) => (param: Param): undefined => { const convertToWithId = convertToWithIdWith(di);
const allRunnables = di.injectMany(injectionToken);
const rootId = uuid.v4();
const getCompositeRunnables = getCompositeFor<RunnableSync<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: () => undefined,
},
...allRunnables,
]);
return runCompositeRunnableSyncs(param, composite); return <Param>(injectionToken: InjectionToken<RunnableSync<Param>, void>) => (param: Param): undefined => {
const executeRunnable = executeRunnableWith(param);
const allRunnables = di.injectManyWithMeta(injectionToken).map(convertToWithId);
verifyRunnablesAreDAG(injectionToken, allRunnables);
allRunnables.forEach(executeRunnable);
return undefined;
}; };
} }

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Injectable } from "@ogre-tools/injectable";
import type { SingleOrMany } from "../utils";
export type Run<Param> = (parameter: Param) => Promise<void> | void;
export interface Runnable<T = void> {
id?: never;
run: Run<T>;
readonly runAfter?: SingleOrMany<Injectable<Runnable<T>, Runnable<T>, void>>;
}
export interface RunnableWithId<T> {
run: Run<T>;
readonly id: string;
readonly runAfter: RunnableWithId<T>[];
}
export interface RunnableSync<T = void> {
id?: never;
run: RunSync<T>;
runAfter?: SingleOrMany<Injectable<RunnableSync<T>, RunnableSync<T>, void>>;
}
export interface RunnableSyncWithId<T> {
run: RunSync<T>;
readonly id: string;
readonly runAfter: RunnableSyncWithId<T>[];
}
/**
* NOTE: this is the worse of two evils. This makes sure that `RunnableSync` always is sync.
* If the return type is `void` instead then async functions (those return `Promise<T>`) can
* coerce to it.
*/
export type RunSync<Param> = (parameter: Param) => undefined;