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:
parent
ab11b20bd9
commit
ecf3eff8d4
101
packages/core/src/common/runnable/helpers.ts
Normal file
101
packages/core/src/common/runnable/helpers.ts
Normal 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;
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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);
|
||||||
|
|
||||||
|
|||||||
@ -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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
40
packages/core/src/common/runnable/types.ts
Normal file
40
packages/core/src/common/runnable/types.ts
Normal 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;
|
||||||
Loading…
Reference in New Issue
Block a user