From 72a01cdc176191d2af165baae126b2a4bbb7a7c8 Mon Sep 17 00:00:00 2001 From: Sebastian Malton Date: Mon, 17 Apr 2023 15:47:57 -0400 Subject: [PATCH] chore: Introduce competition for InitializableState Signed-off-by: Sebastian Malton --- .../common/initializable-state/create.test.ts | 78 ++++++++++++++++++- .../src/common/initializable-state/create.ts | 65 +++++++++++++++- 2 files changed, 139 insertions(+), 4 deletions(-) diff --git a/packages/core/src/common/initializable-state/create.test.ts b/packages/core/src/common/initializable-state/create.test.ts index 8241aa1612..5bd1d6d461 100644 --- a/packages/core/src/common/initializable-state/create.test.ts +++ b/packages/core/src/common/initializable-state/create.test.ts @@ -5,11 +5,14 @@ import type { AsyncFnMock } from "@async-fn/jest"; import asyncFn from "@async-fn/jest"; -import type { DiContainer, Injectable } from "@ogre-tools/injectable"; +import type { Runnable } from "@k8slens/run-many"; +import { runManyFor } from "@k8slens/run-many"; +import type { DiContainer, Injectable, InjectionToken } from "@ogre-tools/injectable"; +import { createContainer, getInjectionToken } from "@ogre-tools/injectable"; import { runInAction } from "mobx"; import { getDiForUnitTesting } from "../../main/getDiForUnitTesting"; -import type { InitializableState } from "./create"; -import { createInitializableState } from "./create"; +import type { ImplInitializableInjectionTokensArgs, Initializable, InitializableState } from "./create"; +import { getInjectablesForInitializable, getInitializable, createInitializableState } from "./create"; describe("InitializableState tests", () => { let di: DiContainer; @@ -75,3 +78,72 @@ describe("InitializableState tests", () => { }); }); }); + +describe("InitializableTokens technical tests", () => { + let di: DiContainer; + let initializableToken: Initializable; + let phase: InjectionToken, void>; + + beforeEach(() => { + di = createContainer("irrelevant"); + + initializableToken = getInitializable("some-root-id"); + phase = getInjectionToken({ id: "some-runnable-phase" }); + }); + + it("throws given attempting to inject the state token", () => { + expect(() => di.inject(initializableToken.stateToken)).toThrowErrorMatchingInlineSnapshot( + `"Tried to inject non-registered injectable "irrelevant" -> "some-root-id-state-token"."`, + ); + }); + + describe("given some implementation for initializableToken is registered", () => { + let mockInit: AsyncFnMock["init"]>; + + beforeEach(() => { + mockInit = asyncFn(); + + const { initializationInjectable, stateInjectable } = getInjectablesForInitializable({ + init: mockInit, + phase, + token: initializableToken, + }); + + di.register(initializationInjectable, stateInjectable); + }); + + it("throws given attempting to inject the state token", () => { + expect(() => di.inject(initializableToken.stateToken)).toThrowErrorMatchingInlineSnapshot( + `"Tried to inject "some-root-id" before initialization was complete"`, + ); + }); + + describe("given the phase is started to be run", () => { + let runManyPromise: Promise; + + beforeEach(() => { + runManyPromise = runManyFor(di)(phase)(); + }); + + it("throws given attempting to inject the state token", () => { + expect(() => di.inject(initializableToken.stateToken)).toThrowErrorMatchingInlineSnapshot( + `"Tried to inject "some-root-id" before initialization was complete"`, + ); + }); + + describe("when initialization is complete", () => { + beforeEach(async () => { + await mockInit.resolve(10); + }); + + it("initializes the state", () => { + expect(di.inject(initializableToken.stateToken)).toBe(10); + }); + + it("allows the runMany to complete", async () => { + await runManyPromise; + }); + }); + }); + }); +}); diff --git a/packages/core/src/common/initializable-state/create.ts b/packages/core/src/common/initializable-state/create.ts index 829de57d94..bb45d878b5 100644 --- a/packages/core/src/common/initializable-state/create.ts +++ b/packages/core/src/common/initializable-state/create.ts @@ -3,8 +3,10 @@ * Licensed under MIT License. See LICENSE in root directory for more information. */ +import type { Runnable } from "@k8slens/run-many"; import type { DiContainerForInjection, Injectable, InjectionToken } from "@ogre-tools/injectable"; -import { getInjectable } from "@ogre-tools/injectable"; +import { getInjectionToken, getInjectable } from "@ogre-tools/injectable"; +import assert from "assert"; export interface CreateInitializableStateArgs { id: string; @@ -21,6 +23,67 @@ type InitializableStateValue = | { set: false } | { set: true; value: T } ; +export interface Initializable { + readonly rootId: string; + readonly stateToken: InjectionToken; +} + +export const getInitializable = (rootId: string): Initializable => ({ + rootId, + stateToken: getInjectionToken({ + id: `${rootId}-state-token`, + }), +}); + +type InitState = { set: true; value: T } | { set: false }; + +export interface ImplInitializableInjectionTokensArgs { + token: Initializable; + init: (di: DiContainerForInjection) => T | Promise; + phase: InjectionToken, void>; + runAfter?: Runnable["runAfter"]; +} + +export const getInjectablesForInitializable = ({ + init, + phase, + token: { + rootId, + stateToken, + }, + runAfter, +}: ImplInitializableInjectionTokensArgs) => { + let state: InitState = { set: false }; + + const stateInjectable = getInjectable({ + id: `${rootId}-state`, + instantiate: () => { + assert(state.set, `Tried to inject "${rootId}" before initialization was complete`); + + return state.value; + }, + injectionToken: stateToken, + }); + const initializationInjectable = getInjectable({ + id: `${rootId}-initialization`, + instantiate: (di) => ({ + run: async () => { + state = { + set: true, + value: await init(di), + }; + }, + runAfter, + }), + injectionToken: phase, + }); + + return { + stateInjectable, + initializationInjectable, + }; +}; + export function createInitializableState(args: CreateInitializableStateArgs): Injectable, unknown, void> { const { id, init, injectionToken } = args;