From a70992e005e77a36115c8b1727702975097404f9 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Tue, 1 Nov 2022 14:52:56 -0400 Subject: [PATCH] Move box into injectable within InitializableState to fix tests - The slight difference in the unit testing where we pretend that there are multiple environments but actually on one caused a problem that doesn't exist in the actual running program. Signed-off-by: Sebastian Malton --- .../directory-for-binaries.injectable.ts | 2 +- .../directory-for-downloads.injectable.ts | 2 +- .../directory-for-exes.injectable.ts | 2 +- .../directory-for-kube-configs.injectable.ts | 2 +- ...rectory-for-kubectl-binaries.injectable.ts | 2 +- .../directory-for-temp.injectable.ts | 2 +- .../directory-for-user-data.injectable.ts | 2 +- ...ctory-for-lens-local-storage.injectable.ts | 2 +- .../initializable-state/create-dependent.ts | 85 --------- src/common/initializable-state/create.ts | 177 ++++++++++++++---- .../semantic-version.injectable.ts | 2 +- src/common/vars/release-channel.injectable.ts | 2 +- ...nsion-package-root-directory.injectable.ts | 2 +- ...directory-for-extension-data.injectable.ts | 2 +- .../default-update-channel.injectable.ts | 2 +- 15 files changed, 155 insertions(+), 133 deletions(-) delete mode 100644 src/common/initializable-state/create-dependent.ts diff --git a/src/common/app-paths/directory-for-binaries.injectable.ts b/src/common/app-paths/directory-for-binaries.injectable.ts index ebba8a29e7..693bac2771 100644 --- a/src/common/app-paths/directory-for-binaries.injectable.ts +++ b/src/common/app-paths/directory-for-binaries.injectable.ts @@ -4,7 +4,7 @@ */ import directoryForUserDataInjectable, { initDirectoryForUserDataOnMainInjectable, initDirectoryForUserDataOnRendererInjectable } from "./directory-for-user-data.injectable"; import joinPathsInjectable from "../path/join-paths.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; const { value: directoryForBinariesInjectable, diff --git a/src/common/app-paths/directory-for-downloads.injectable.ts b/src/common/app-paths/directory-for-downloads.injectable.ts index 0d2b21dd52..75ebebfbfd 100644 --- a/src/common/app-paths/directory-for-downloads.injectable.ts +++ b/src/common/app-paths/directory-for-downloads.injectable.ts @@ -4,7 +4,7 @@ */ import { initAppPathsOnMainInjectable } from "../../main/app-paths/impl.injectable"; import { initAppPathsOnRendererInjectable } from "../../renderer/app-paths/impl.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; import { appPathsInjectionToken } from "./token"; const { diff --git a/src/common/app-paths/directory-for-exes.injectable.ts b/src/common/app-paths/directory-for-exes.injectable.ts index a52b95208c..4ca3d13f9e 100644 --- a/src/common/app-paths/directory-for-exes.injectable.ts +++ b/src/common/app-paths/directory-for-exes.injectable.ts @@ -4,7 +4,7 @@ */ import { initAppPathsOnMainInjectable } from "../../main/app-paths/impl.injectable"; import { initAppPathsOnRendererInjectable } from "../../renderer/app-paths/impl.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; import { appPathsInjectionToken } from "./token"; const { diff --git a/src/common/app-paths/directory-for-kube-configs.injectable.ts b/src/common/app-paths/directory-for-kube-configs.injectable.ts index f3d74c99d7..0e79c49260 100644 --- a/src/common/app-paths/directory-for-kube-configs.injectable.ts +++ b/src/common/app-paths/directory-for-kube-configs.injectable.ts @@ -4,7 +4,7 @@ */ import directoryForUserDataInjectable, { initDirectoryForUserDataOnMainInjectable, initDirectoryForUserDataOnRendererInjectable } from "./directory-for-user-data.injectable"; import joinPathsInjectable from "../path/join-paths.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; const { value: directoryForKubeConfigsInjectable, diff --git a/src/common/app-paths/directory-for-kubectl-binaries.injectable.ts b/src/common/app-paths/directory-for-kubectl-binaries.injectable.ts index 378689f18c..6a6687b62b 100644 --- a/src/common/app-paths/directory-for-kubectl-binaries.injectable.ts +++ b/src/common/app-paths/directory-for-kubectl-binaries.injectable.ts @@ -4,7 +4,7 @@ */ import directoryForBinariesInjectable, { initDirectoryForBinariesOnMainInjectable, initDirectoryForBinariesOnRendererInjectable } from "./directory-for-binaries.injectable"; import joinPathsInjectable from "../path/join-paths.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; const { value: directoryForKubectlBinariesInjectable, diff --git a/src/common/app-paths/directory-for-temp.injectable.ts b/src/common/app-paths/directory-for-temp.injectable.ts index d4bae4392d..a466dbdeb8 100644 --- a/src/common/app-paths/directory-for-temp.injectable.ts +++ b/src/common/app-paths/directory-for-temp.injectable.ts @@ -4,7 +4,7 @@ */ import { initAppPathsOnMainInjectable } from "../../main/app-paths/impl.injectable"; import { initAppPathsOnRendererInjectable } from "../../renderer/app-paths/impl.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; import { appPathsInjectionToken } from "./token"; const { diff --git a/src/common/app-paths/directory-for-user-data.injectable.ts b/src/common/app-paths/directory-for-user-data.injectable.ts index 349bb68fc4..f6c194a6db 100644 --- a/src/common/app-paths/directory-for-user-data.injectable.ts +++ b/src/common/app-paths/directory-for-user-data.injectable.ts @@ -4,7 +4,7 @@ */ import { initAppPathsOnMainInjectable } from "../../main/app-paths/impl.injectable"; import { initAppPathsOnRendererInjectable } from "../../renderer/app-paths/impl.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; import { appPathsInjectionToken } from "./token"; const { diff --git a/src/common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable.ts b/src/common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable.ts index 4661c5aa29..0828510ad1 100644 --- a/src/common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable.ts +++ b/src/common/directory-for-lens-local-storage/directory-for-lens-local-storage.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import directoryForUserDataInjectable, { initDirectoryForUserDataOnMainInjectable, initDirectoryForUserDataOnRendererInjectable } from "../app-paths/directory-for-user-data.injectable"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; import joinPathsInjectable from "../path/join-paths.injectable"; const { diff --git a/src/common/initializable-state/create-dependent.ts b/src/common/initializable-state/create-dependent.ts deleted file mode 100644 index 23ab184019..0000000000 --- a/src/common/initializable-state/create-dependent.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Copyright (c) OpenLens Authors. All rights reserved. - * Licensed under MIT License. See LICENSE in root directory for more information. - */ - -import type { DiContainerForInjection, Injectable, InjectionToken } from "@ogre-tools/injectable"; -import { getInjectable } from "@ogre-tools/injectable"; -import type { Runnable } from "../runnable/run-many-for"; -import type { InitializableState, InitializableStateValue } from "./create"; - -export interface CreateDependentInitializableStateArgs { - id: string; - init: (di: DiContainerForInjection) => Promise | T; - injectionToken?: InjectionToken, void>; - initAfter: Injectable, Runnable, void>[]; -} - -export interface CreateDependentInitializableStateResult { - value: Injectable, unknown, void>; - initializers: Injectable, Runnable, void>[]; -} - -export function createDependentInitializableState(args: CreateDependentInitializableStateArgs): CreateDependentInitializableStateResult { - const { id, init, injectionToken, initAfter } = args; - - let box: InitializableStateValue = { - set: false, - }; - let initCalled = false; - - const valueInjectable = getInjectable({ - id, - instantiate: (): InitializableState => ({ - get: () => { - if (!initCalled) { - throw new Error(`InitializableState(${id}) has not been initialized yet`); - } - - if (box.set === false) { - throw new Error(`InitializableState(${id}) has not finished initializing`); - } - - return box.value; - }, - }), - injectionToken, - }); - - const initializers = initAfter.map(runnableInjectable => getInjectable({ - id: `initialize-${id}-during-${runnableInjectable.injectionToken?.id}`, - instantiate: (di) => ({ - id: `initialize-${id}`, - run: (): void | Promise => { - if (initCalled) { - throw new Error(`Cannot initialize InitializableState(${id}) more than once`); - } - - initCalled = true; - const potentialValue = init(di); - - if (potentialValue instanceof Promise) { - // This is done because we have to run syncronously if `init` is syncronous to prevent ordering issues - return (async () => { - box = { - set: true, - value: await potentialValue, - }; - })(); - } else { - box = { - set: true, - value: potentialValue, - }; - } - }, - runAfter: di.inject(runnableInjectable), - }), - injectionToken: runnableInjectable.injectionToken, - })); - - return { - value: valueInjectable, - initializers, - }; -} diff --git a/src/common/initializable-state/create.ts b/src/common/initializable-state/create.ts index 5ef928c791..66fe43da1a 100644 --- a/src/common/initializable-state/create.ts +++ b/src/common/initializable-state/create.ts @@ -6,6 +6,7 @@ import type { DiContainerForInjection, Injectable, InjectionToken } from "@ogre-tools/injectable"; import { getInjectable } from "@ogre-tools/injectable"; import type { Runnable } from "../runnable/run-many-for"; +import type { Discriminable } from "../utils/composable-responsibilities/discriminable/discriminable"; export interface CreateInitializableStateArgs { id: string; @@ -14,13 +15,23 @@ export interface CreateInitializableStateArgs { when: InjectionToken, void>; } +const setInitializing = Symbol("set-initializing"); +const initialize = Symbol("initialize"); + export interface InitializableState { get: () => T; + [setInitializing]: () => void; + [initialize]: (value: T) => void; } +export type UnsetValue = Discriminable<"uninitialized">; +export type InitializingValue = Discriminable<"initializing">; +export type InitializedValue = Discriminable<"initialized"> & { value: T }; + export type InitializableStateValue = - | { set: false } - | { set: true; value: T }; + | UnsetValue + | InitializingValue + | InitializedValue; export interface CreateInitializableStateResult { value: Injectable, unknown, void>; @@ -30,54 +41,62 @@ export interface CreateInitializableStateResult { export function createInitializableState(args: CreateInitializableStateArgs): CreateInitializableStateResult { const { id, init, injectionToken, when } = args; - let box: InitializableStateValue = { - set: false, - }; - let initCalled = false; - const valueInjectable = getInjectable({ id, - instantiate: (): InitializableState => ({ - get: () => { - if (!initCalled) { - throw new Error(`InitializableState(${id}) has not been initialized yet`); - } + instantiate: (): InitializableState => { + let box: InitializableStateValue = { + kind: "uninitialized", + }; - if (box.set === false) { - throw new Error(`InitializableState(${id}) has not finished initializing`); - } + return { + get: () => { + if (box.kind !== "initialized") { + throw new Error(`Cannot get value from "${id}"; it is currently in state=${box.kind}`); + } - return box.value; - }, - }), + return box.value; + }, + [setInitializing]: () => { + if (box.kind !== "uninitialized") { + throw new Error(`Cannot start initializing value for "${id}"; it is currently in state=${box.kind}`); + } + + box = { + kind: "initializing", + }; + }, + [initialize]: (value) => { + if (box.kind !== "initializing") { + throw new Error(`Cannot initialize value for "${id}"; it is currently in state=${box.kind}`); + } + + box = { + kind: "initialized", + value, + }; + }, + }; + }, injectionToken, }); + const subId = `initialize id="${id}" during id="${when.id}"`; const initializer = getInjectable({ - id: `initialize-${id}`, + id: subId, instantiate: (di) => ({ - id: `initialize-${id}`, - run: async () => { - if (initCalled) { - throw new Error(`Cannot initialize InitializableState(${id}) more than once`); - } + id: subId, + run: (): void | Promise => { + const value = di.inject(valueInjectable); + + value[setInitializing](); - initCalled = true; const potentialValue = init(di); if (potentialValue instanceof Promise) { // This is done because we have to run syncronously if `init` is syncronous to prevent ordering issues - return (async () => { - box = { - set: true, - value: await potentialValue, - }; - })(); + return potentialValue.then(value[initialize]); } else { - box = { - set: true, - value: potentialValue, - }; + value[initialize](potentialValue); } }, }), @@ -89,3 +108,91 @@ export function createInitializableState(args: CreateInitializableStateArgs { + id: string; + init: (di: DiContainerForInjection) => Promise | T; + injectionToken?: InjectionToken, void>; + initAfter: Injectable, Runnable, void>[]; +} + +export interface CreateDependentInitializableStateResult { + value: Injectable, unknown, void>; + initializers: Injectable, Runnable, void>[]; +} + +export function createDependentInitializableState(args: CreateDependentInitializableStateArgs): CreateDependentInitializableStateResult { + const { id, init, injectionToken, initAfter } = args; + + const valueInjectable = getInjectable({ + id, + instantiate: (): InitializableState => { + let box: InitializableStateValue = { + kind: "uninitialized", + }; + + return { + get: () => { + if (box.kind !== "initialized") { + throw new Error(`Cannot get value from "${id}"; it is currently in state=${box.kind}`); + } + + return box.value; + }, + [setInitializing]: () => { + if (box.kind !== "uninitialized") { + throw new Error(`Cannot start initializing value for "${id}"; it is currently in state=${box.kind}`); + } + + box = { + kind: "initializing", + }; + }, + [initialize]: (value) => { + if (box.kind !== "initializing") { + throw new Error(`Cannot initialize value for "${id}"; it is currently in state=${box.kind}`); + } + + box = { + kind: "initialized", + value, + }; + }, + }; + }, + injectionToken, + }); + + const initializers = initAfter.map(runnableInjectable => { + const subId = `initialize "${id}" after "${runnableInjectable.id}"`; + + return getInjectable({ + id: subId, + instantiate: (di) => ({ + id: subId, + run: (): void | Promise => { + const value = di.inject(valueInjectable); + + value[setInitializing](); + + const potentialValue = init(di); + + if (potentialValue instanceof Promise) { + // This is done because we have to run syncronously if `init` is syncronous to prevent ordering issues + return potentialValue.then(value[initialize]); + } else { + value[initialize](potentialValue); + } + }, + runAfter: di.inject(runnableInjectable), + }), + injectionToken: runnableInjectable.injectionToken, + }); + }); + + return { + value: valueInjectable, + initializers, + }; +} diff --git a/src/common/vars/build-version/semantic-version.injectable.ts b/src/common/vars/build-version/semantic-version.injectable.ts index 277d6b6538..d559a99842 100644 --- a/src/common/vars/build-version/semantic-version.injectable.ts +++ b/src/common/vars/build-version/semantic-version.injectable.ts @@ -6,7 +6,7 @@ import { SemVer } from "semver"; import { initializeBuildVersionOnMainInjectable } from "../../../main/vars/build-version/build-version.injectable"; import { initializeBuildVersionOnRendererInjectable } from "../../../renderer/vars/build-version.injectable"; -import { createDependentInitializableState } from "../../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../../initializable-state/create"; import { buildVersionInjectionToken } from "./token"; const { diff --git a/src/common/vars/release-channel.injectable.ts b/src/common/vars/release-channel.injectable.ts index 6d9f2fc2cf..5f2021922a 100644 --- a/src/common/vars/release-channel.injectable.ts +++ b/src/common/vars/release-channel.injectable.ts @@ -4,7 +4,7 @@ */ import buildSemanticVersionInjectable, { initBuildSemanticVersionOnMainInjectable, initBuildSemanticVersionOnRendererInjectable } from "./build-version/semantic-version.injectable"; import type { ReleaseChannel } from "../../features/application-update/common/update-channels"; -import { createDependentInitializableState } from "../initializable-state/create-dependent"; +import { createDependentInitializableState } from "../initializable-state/create"; const { value: releaseChannelInjectable, diff --git a/src/extensions/extension-installer/extension-package-root-directory.injectable.ts b/src/extensions/extension-installer/extension-package-root-directory.injectable.ts index 2a7177cf0d..40f30da8c8 100644 --- a/src/extensions/extension-installer/extension-package-root-directory.injectable.ts +++ b/src/extensions/extension-installer/extension-package-root-directory.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import directoryForUserDataInjectable, { initDirectoryForUserDataOnMainInjectable, initDirectoryForUserDataOnRendererInjectable } from "../../common/app-paths/directory-for-user-data.injectable"; -import { createDependentInitializableState } from "../../common/initializable-state/create-dependent"; +import { createDependentInitializableState } from "../../common/initializable-state/create"; const { value: extensionPackageRootDirectoryInjectable, diff --git a/src/extensions/extension-loader/file-system-provisioner-store/directory-for-extension-data.injectable.ts b/src/extensions/extension-loader/file-system-provisioner-store/directory-for-extension-data.injectable.ts index 3c55c0b28e..fbe8621a5f 100644 --- a/src/extensions/extension-loader/file-system-provisioner-store/directory-for-extension-data.injectable.ts +++ b/src/extensions/extension-loader/file-system-provisioner-store/directory-for-extension-data.injectable.ts @@ -3,7 +3,7 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ import directoryForUserDataInjectable, { initDirectoryForUserDataOnMainInjectable, initDirectoryForUserDataOnRendererInjectable } from "../../../common/app-paths/directory-for-user-data.injectable"; -import { createDependentInitializableState } from "../../../common/initializable-state/create-dependent"; +import { createDependentInitializableState } from "../../../common/initializable-state/create"; import joinPathsInjectable from "../../../common/path/join-paths.injectable"; const { diff --git a/src/features/application-update/common/selected-update-channel/default-update-channel.injectable.ts b/src/features/application-update/common/selected-update-channel/default-update-channel.injectable.ts index 1027bbdd13..a58cf265e0 100644 --- a/src/features/application-update/common/selected-update-channel/default-update-channel.injectable.ts +++ b/src/features/application-update/common/selected-update-channel/default-update-channel.injectable.ts @@ -2,7 +2,7 @@ * Copyright (c) OpenLens Authors. All rights reserved. * Licensed under MIT License. See LICENSE in root directory for more information. */ -import { createDependentInitializableState } from "../../../../common/initializable-state/create-dependent"; +import { createDependentInitializableState } from "../../../../common/initializable-state/create"; import releaseChannelInjectable, { initReleaseChannelOnMainInjectable, initReleaseChannelOnRendererInjectable } from "../../../../common/vars/release-channel.injectable"; import { updateChannels } from "../update-channels";