Skip to content

Commit

Permalink
Di rework (#123)
Browse files Browse the repository at this point in the history
* Make inversify optional

* Remove reflect-metadata imports
  • Loading branch information
Animagne authored Jan 8, 2025
1 parent 752de06 commit 35c5556
Show file tree
Hide file tree
Showing 104 changed files with 725 additions and 596 deletions.
2 changes: 2 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
"**/node_modules": true,
"**/out": true,
"**/lib": true,
"**/cypress": true,
"**/dist": true,
"common/temp": true
},
"eslint.workingDirectories": [
Expand Down
12 changes: 7 additions & 5 deletions cloud-agnostic/core/src/Bindable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Container } from "inversify";

import { Dependency } from "./Dependency";
import { DependenciesConfig, DependencyConfig } from "./DependencyConfig";
import { DependencyFactory } from "./DependencyFactory";
import { DIContainer } from "./DIContainer";
import { DependencyError } from "./internal";
import { Types } from "./Types";

Expand Down Expand Up @@ -34,7 +34,7 @@ export abstract class Bindable {
}

private bindNamedDependencies(
container: Container,
container: DIContainer,
factory: DependencyFactory,
configs: DependencyConfig[]
): void {
Expand All @@ -46,15 +46,17 @@ export abstract class Bindable {
}

private bindDependency(
container: Container,
container: DIContainer,
factory: DependencyFactory,
config: DependencyConfig
) {
factory.getDependency(config.dependencyName).register(container, config);
}

protected bindDependencies(container: Container): void {
const config = container.get<DependenciesConfig>(Types.dependenciesConfig);
protected bindDependencies(container: DIContainer): void {
const config = container.resolve<DependenciesConfig>(
Types.dependenciesConfig
);

this._dependencyFactories.forEach((factory) => {
const dependencyConfig = config[factory.dependencyType];
Expand Down
31 changes: 31 additions & 0 deletions cloud-agnostic/core/src/DIContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

import { DIIdentifier } from "./internal";

export abstract class DIContainer {
abstract registerFactory<T>(
key: DIIdentifier<T>,
factory: (container: DIContainer) => T
): void;

abstract registerNamedFactory<T>(
key: DIIdentifier<T>,
factory: (container: DIContainer) => T,
name: string
): void;

abstract registerInstance<T>(key: DIIdentifier<T>, instance: T): void;

abstract unregister<T>(key: DIIdentifier<T>): void;

abstract resolve<T>(key: DIIdentifier<T>): T;

abstract resolveNamed<T>(key: DIIdentifier<T>, name: string): T;

abstract resolveAll<T>(key: DIIdentifier<T>): T[];

abstract createChild(): DIContainer;
}
4 changes: 2 additions & 2 deletions cloud-agnostic/core/src/Dependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Container } from "inversify";

import { DependencyConfig } from "./DependencyConfig";
import { DIContainer } from "./DIContainer";

export abstract class Dependency {
public abstract dependencyName: string;
public abstract dependencyType: string;
public abstract register(
container: Container,
container: DIContainer,
config?: DependencyConfig
): void;
}
47 changes: 24 additions & 23 deletions cloud-agnostic/core/src/NamedDependency.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { Container, interfaces } from "inversify";

import { Dependency } from "./Dependency";
import { DependencyConfig } from "./DependencyConfig";
import { DIContainer } from "./DIContainer";
import { DIIdentifier } from "./internal";

export class NamedInstance<T> {
constructor(
Expand All @@ -16,13 +17,13 @@ export class NamedInstance<T> {

export abstract class NamedDependency extends Dependency {
protected abstract _registerInstance(
container: Container,
childContainer: Container,
container: DIContainer,
childContainer: DIContainer,
config: DependencyConfig
): void;

public registerInstance(
container: Container,
container: DIContainer,
config: DependencyConfig
): void {
const childContainer = container.createChild();
Expand All @@ -32,26 +33,26 @@ export abstract class NamedDependency extends Dependency {
}

protected bindNamed<T>(
container: Container,
childContainer: Container,
serviceIdentifier: interfaces.ServiceIdentifier<T>,
container: DIContainer,
childContainer: DIContainer,
serviceIdentifier: DIIdentifier<T>,
instanceName: string
): void {
container
.bind(NamedInstance<T>)
.toDynamicValue(() => {
return new NamedInstance<T>(
childContainer.get(serviceIdentifier),
instanceName
);
})
.inSingletonScope();
container
.bind(serviceIdentifier)
.toDynamicValue(() => {
return childContainer.get(serviceIdentifier);
})
.inSingletonScope()
.whenTargetNamed(instanceName);
container.registerFactory<NamedInstance<T>>(NamedInstance<T>, () => {
return new NamedInstance<T>(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
childContainer.resolve(serviceIdentifier),
instanceName
);
});
container.registerNamedFactory<T>(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
serviceIdentifier,
() => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return childContainer.resolve(serviceIdentifier);
},
instanceName
);
}
}
2 changes: 2 additions & 0 deletions cloud-agnostic/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

export * from "./DIContainer";
export * from "./Bindable";
export * from "./Dependency";
export * from "./NamedDependency";
Expand Down
12 changes: 12 additions & 0 deletions cloud-agnostic/core/src/internal/Types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Constructor<T> = new (...args: any[]) => T;
export interface Abstract<T> {
prototype: T;
}

export type DIIdentifier<T> = symbol | Constructor<T> | Abstract<T>;
1 change: 1 addition & 0 deletions cloud-agnostic/core/src/internal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*--------------------------------------------------------------------------------------------*/
export * from "./Errors";
export * from "./Helpers";
export * from "./Types";
80 changes: 80 additions & 0 deletions cloud-agnostic/core/src/inversify/InversifyWrapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import "reflect-metadata";

import { Container, decorate, injectable, METADATA_KEY } from "inversify";

import { DIContainer } from "../DIContainer";
import { Abstract, Constructor, DIIdentifier } from "../internal";

export class InversifyWrapper extends DIContainer {
constructor(private _container: Container) {
super();
}

public static create(): DIContainer {
return new InversifyWrapper(new Container());
}

private needsDecoration<T>(
key: DIIdentifier<T>
): key is Constructor<T> | Abstract<T> {
if (typeof key === "symbol") return false;
return Reflect.hasOwnMetadata(METADATA_KEY.PARAM_TYPES, key);
}

public override registerFactory<T>(
key: DIIdentifier<T>,
factory: (container: DIContainer) => T
): void {
if (this.needsDecoration(key)) {
decorate(injectable(), key);
}

this._container
.bind<T>(key)
.toDynamicValue(() => factory(this))
.inSingletonScope();
}

public override registerNamedFactory<T>(
key: DIIdentifier<T>,
factory: (container: DIContainer) => T,
name: string
): void {
if (this.needsDecoration(key)) {
decorate(injectable(), key);
}

this._container
.bind<T>(key)
.toDynamicValue(() => factory(this))
.whenTargetNamed(name);
}

public override registerInstance<T>(key: DIIdentifier<T>, instance: T): void {
this._container.bind<T>(key).toConstantValue(instance);
}

public override unregister<T>(key: DIIdentifier<T>): void {
this._container.unbind(key);
}

public override resolve<T>(key: DIIdentifier<T>): T {
return this._container.get<T>(key);
}

public override resolveNamed<T>(key: DIIdentifier<T>, name: string): T {
return this._container.getNamed<T>(key, name);
}

public override resolveAll<T>(key: DIIdentifier<T>): T[] {
return this._container.getAll<T>(key);
}

public override createChild(): DIContainer {
return new InversifyWrapper(this._container.createChild());
}
}
5 changes: 5 additions & 0 deletions cloud-agnostic/core/src/inversify/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
export * from "./InversifyWrapper";
29 changes: 16 additions & 13 deletions cloud-agnostic/core/src/test/Bindable.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
import { expect } from "chai";
import { Container } from "inversify";

import { Bindable, DependenciesConfig, NamedInstance } from "..";
import { DependencyError, DependencyTypeError } from "../internal";
import { InversifyWrapper } from "../inversify";

import { ConcreteTest, Test, TestConfig } from "./Test";
import { ConcreteTestDependencyBindings } from "./TestDependency";
Expand Down Expand Up @@ -63,17 +63,20 @@ function validateTestObject(test: Test, config: TestConfig) {

describe(`${Bindable.name}`, () => {
it(`should resolve registered dependency`, () => {
const setup = new TestSetup(new Container(), dependenciesConfig);
const setup = new TestSetup(InversifyWrapper.create(), dependenciesConfig);

setup.start();

const test = setup.container.get(Test);
const test = setup.container.resolve(Test);
expect(test instanceof ConcreteTest).to.be.true;
expect(test.property === "testProperty").to.be.true;
});

it(`should throw if dependency factory is not registered`, () => {
const setup = new TestSetupNoFactory(new Container(), dependenciesConfig);
const setup = new TestSetupNoFactory(
InversifyWrapper.create(),
dependenciesConfig
);

const testedFunction = () =>
setup.useBindings(ConcreteTestDependencyBindings);
Expand All @@ -87,7 +90,7 @@ describe(`${Bindable.name}`, () => {

it(`should throw if testName dependency is not registered`, () => {
const setup = new TestSetupNoDefaultDependencies(
new Container(),
InversifyWrapper.create(),
dependenciesConfig
);

Expand All @@ -102,7 +105,7 @@ describe(`${Bindable.name}`, () => {

it(`should throw if testName does not support named dependency instances`, () => {
const setup = new TestSetup(
new Container(),
InversifyWrapper.create(),
dependenciesConfigWithMultipleInstances
);

Expand All @@ -117,29 +120,29 @@ describe(`${Bindable.name}`, () => {

it(`should resolve registered named dependency instance`, () => {
const setup = new TestSetupWithNamedInstances(
new Container(),
InversifyWrapper.create(),
dependenciesConfigWithOneInstance
);

setup.start();

const test = setup.container.getNamed(Test, "instanceName");
const test = setup.container.resolveNamed(Test, "instanceName");
validateTestObject(test, testConfigWithOneInstance[0]);
});

it(`should resolve multiple registered named dependency instances by name`, () => {
const setup = new TestSetupWithNamedInstances(
new Container(),
InversifyWrapper.create(),
dependenciesConfigWithMultipleInstances
);

setup.start();

const test = setup.container.getNamed(
const test = setup.container.resolveNamed(
Test,
testConfigWithMultipleInstances[0].instanceName!
);
const test2 = setup.container.getNamed(
const test2 = setup.container.resolveNamed(
Test,
testConfigWithMultipleInstances[1].instanceName!
);
Expand All @@ -150,13 +153,13 @@ describe(`${Bindable.name}`, () => {

it(`should resolve multiple registered named dependency instances as array`, () => {
const setup = new TestSetupWithNamedInstances(
new Container(),
InversifyWrapper.create(),
dependenciesConfigWithMultipleInstances
);

setup.start();

const tests: NamedInstance<Test>[] = setup.container.getAll(
const tests: NamedInstance<Test>[] = setup.container.resolveAll(
NamedInstance<Test>
);

Expand Down
Loading

0 comments on commit 35c5556

Please sign in to comment.