Refactor runnables (#6528)

* Convert runManyFor to use composite

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert runManySyncFor to use composite and to enfore sync-ness

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Remove dead code

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert getStartableStoppable to be always sync as it is never used in an async fashion

Signed-off-by: Sebastian Malton <sebastian@malton.name>

* Convert all sync runnables to comply with new checks for sync-ness

Signed-off-by: Sebastian Malton <sebastian@malton.name>

Signed-off-by: Sebastian Malton <sebastian@malton.name>
This commit is contained in:
Sebastian Malton authored and GitHub committed 2022-11-07 19:04:56 +02:00
1 parent 82ab60d544
commit b498f12186
42 files changed
+191 -401

No files matched your search

+1 -1
View File
@@ -223,7 +223,7 @@ describe("runManyFor", () => {
);
return expect(() => runMany()).rejects.toThrow(
'Tried to run runnable "some-runnable-1" after the runnable "some-runnable-2" which does not share the "some-injection-token" injection token.',
/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"/,
);
});
+29 -33
View File
@@ -2,13 +2,10 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { pipeline } from "@ogre-tools/fp";
import type {
DiContainerForInjection,
InjectionToken,
} from "@ogre-tools/injectable";
import { filter, forEach, map, tap } from "lodash/fp";
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
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 * as uuid from "uuid";
export interface Runnable<TParameter = void> {
id: string;
@@ -18,35 +15,34 @@ export interface Runnable<TParameter = void> {
type Run<Param> = (parameter: Param) => Promise<void> | void;
export type RunMany = <Param>(
injectionToken: InjectionToken<Runnable<Param>, void>
) => Run<Param>;
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)));
}
export function runManyFor(di: DiContainerForInjection): RunMany {
return (injectionToken) => async (parameter) => {
return <Param>(injectionToken: InjectionToken<Runnable<Param>, void>) => async (param: 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,
]);
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor((injectionToken as any).id, allRunnables);
const recursedRun = async (
runAfterRunnable: Runnable<any> | undefined = undefined,
) =>
await pipeline(
allRunnables,
tap(runnables => forEach(throwWithIncorrectHierarchy, runnables)),
filter((runnable) => runnable.runAfter === runAfterRunnable),
map(async (runnable) => {
await runnable.run(parameter);
await recursedRun(runnable);
}),
(promises) => Promise.all(promises),
);
await recursedRun();
await runCompositeRunnables(param, composite);
};
}
@@ -69,7 +69,7 @@ describe("runManySyncFor", () => {
instantiate: (di) => ({
id: "some-injectable-1",
run: () => runMock("third-level-run"),
run: () => void runMock("third-level-run"),
runAfter: di.inject(someInjectable2),
}),
@@ -81,7 +81,7 @@ describe("runManySyncFor", () => {
instantiate: (di) => ({
id: "some-injectable-2",
run: () => runMock("second-level-run"),
run: () => void runMock("second-level-run"),
runAfter: di.inject(someInjectable3),
}),
@@ -92,7 +92,7 @@ describe("runManySyncFor", () => {
id: "some-injectable-3",
instantiate: () => ({
id: "some-injectable-3",
run: () => runMock("first-level-run"),
run: () => void runMock("first-level-run"),
}),
injectionToken: someInjectionTokenForRunnables,
});
@@ -151,13 +151,13 @@ describe("runManySyncFor", () => {
someInjectionToken,
);
return expect(() => runMany()).rejects.toThrow(
'Tried to run runnable "some-runnable-1" after the runnable "some-runnable-2" which does not share the "some-injection-token" injection token.',
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"/,
);
});
describe("when running many with parameter", () => {
let runMock: jest.Mock<(arg: string, arg2: string) => void>;
let runMock: jest.Mock<(arg: string, arg2: string) => undefined>;
beforeEach(() => {
const rootDi = createContainer("irrelevant");
@@ -175,7 +175,7 @@ describe("runManySyncFor", () => {
instantiate: () => ({
id: "some-runnable-1",
run: (parameter) => runMock("run-of-some-runnable-1", parameter),
run: (parameter) => void runMock("run-of-some-runnable-1", parameter),
}),
injectionToken: someInjectionTokenForRunnablesWithParameter,
@@ -186,7 +186,7 @@ describe("runManySyncFor", () => {
instantiate: () => ({
id: "some-runnable-2",
run: (parameter) => runMock("run-of-some-runnable-2", parameter),
run: (parameter) => void runMock("run-of-some-runnable-2", parameter),
}),
injectionToken: someInjectionTokenForRunnablesWithParameter,
+36 -29
View File
@@ -2,11 +2,10 @@
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import { pipeline } from "@ogre-tools/fp";
import type { DiContainerForInjection, InjectionToken } from "@ogre-tools/injectable";
import { filter, forEach, map, tap } from "lodash/fp";
import type { Runnable } from "./run-many-for";
import { throwWithIncorrectHierarchyFor } from "./throw-with-incorrect-hierarchy-for";
import type { Composite } from "../utils/composite/get-composite/get-composite";
import { getCompositeFor } from "../utils/composite/get-composite/get-composite";
import * as uuid from "uuid";
export interface RunnableSync<TParameter = void> {
id: string;
@@ -14,35 +13,43 @@ export interface RunnableSync<TParameter = void> {
runAfter?: RunnableSync<TParameter>;
}
type RunSync<Param> = (parameter: Param) => void;
/**
* 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<Runnable<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 {
composite.value.run(param);
composite.children.map(composite => runCompositeRunnableSyncs(param, composite));
return undefined;
}
export function runManySyncFor(di: DiContainerForInjection): RunManySync {
return (injectionToken) => async (parameter) => {
return <Param>(injectionToken: InjectionToken<RunnableSync<Param>, void>) => (param: Param): undefined => {
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,
]);
const throwWithIncorrectHierarchy = throwWithIncorrectHierarchyFor((injectionToken as any).id, allRunnables);
const recursedRun = (
runAfterRunnable: RunnableSync<any> | undefined = undefined,
) =>
pipeline(
allRunnables,
tap(runnables => forEach(throwWithIncorrectHierarchy, runnables)),
filter((runnable) => runnable.runAfter === runAfterRunnable),
map((runnable) => {
runnable.run(parameter);
recursedRun(runnable);
}),
);
recursedRun();
return runCompositeRunnableSyncs(param, composite);
};
}
@@ -1,14 +0,0 @@
/**
* Copyright (c) OpenLens Authors. All rights reserved.
* Licensed under MIT License. See LICENSE in root directory for more information.
*/
import type { Runnable } from "./run-many-for";
import type { RunnableSync } from "./run-many-sync-for";
export const throwWithIncorrectHierarchyFor = (injectionTokenId: string, allRunnables: Runnable<any>[] | RunnableSync<any>[]) => (
(runnable: Runnable<any> | RunnableSync<any>) => {
if (runnable.runAfter && !allRunnables.includes(runnable.runAfter)) {
throw new Error(`Tried to run runnable "${runnable.id}" after the runnable "${runnable.runAfter.id}" which does not share the "${injectionTokenId}" injection token.`);
}
}
);