Add Dependency Injection example and core interfaces. Implement Inject class for managing scopes and instances, along with InstanceProvider, IntegerConstantProvider, and StringConstantProvider for dependency resolution.
This commit is contained in:
3
examples/di-example.ts
Normal file
3
examples/di-example.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import {DependencyManager} from '../src';
|
||||||
|
|
||||||
|
const dependencyManager = new DependencyManager(async path => await import(path));
|
||||||
3
src/di/IConfigurationLoader.ts
Normal file
3
src/di/IConfigurationLoader.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface IConfigurationLoader {
|
||||||
|
load(name: string): Promise<unknown>;
|
||||||
|
}
|
||||||
10
src/di/IInject.ts
Normal file
10
src/di/IInject.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type IDisposable from '../lang/IDisposable';
|
||||||
|
import {Type} from './Type';
|
||||||
|
|
||||||
|
export interface IInject extends IDisposable {
|
||||||
|
scope(): Promise<IInject>;
|
||||||
|
defineInstance(type: string, instance: unknown): void;
|
||||||
|
instantiate<T = unknown>(type: string): Promise<T>;
|
||||||
|
start(): Promise<unknown[]>;
|
||||||
|
getAvailableTypes(): Promise<Type[]>;
|
||||||
|
}
|
||||||
83
src/di/Inject.ts
Normal file
83
src/di/Inject.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import type {IDependencyManager} from './IDependencyManager';
|
||||||
|
import type {IInject} from './IInject';
|
||||||
|
import DisposableList from '../lang/DisposableList';
|
||||||
|
import {InstanceProvider} from './InstanceProvider';
|
||||||
|
import {Type} from './Type';
|
||||||
|
|
||||||
|
export class Inject implements IInject {
|
||||||
|
private readonly _dependencyManager: IDependencyManager;
|
||||||
|
private readonly _scope: DisposableList;
|
||||||
|
private readonly _endedCallbacks: (() => void)[];
|
||||||
|
private _activeChildScope: Inject | null = null;
|
||||||
|
|
||||||
|
constructor(dependencyManager: IDependencyManager) {
|
||||||
|
this._dependencyManager = dependencyManager;
|
||||||
|
this._scope = new DisposableList();
|
||||||
|
this._endedCallbacks = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async scope(): Promise<IInject> {
|
||||||
|
// Only one scope can be active at any given time
|
||||||
|
if (!this._activeChildScope) {
|
||||||
|
const scope = new Inject(this._dependencyManager);
|
||||||
|
this._activeChildScope = scope;
|
||||||
|
|
||||||
|
// Unregister scope when it ends
|
||||||
|
scope._endedCallbacks.push(() => {
|
||||||
|
if (this._activeChildScope === scope) {
|
||||||
|
this._activeChildScope = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the current scope to end
|
||||||
|
return new Promise<IInject>(resolve => {
|
||||||
|
this._activeChildScope!._endedCallbacks.push(() => {
|
||||||
|
resolve(this.scope());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
defineInstance(type: string, instance: unknown): void {
|
||||||
|
const disposable = this._dependencyManager.addProvider(new InstanceProvider(new Type(type), instance));
|
||||||
|
this._scope.add(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
async instantiate<T = unknown>(type: string): Promise<T> {
|
||||||
|
const theType = new Type(type);
|
||||||
|
const provider = await this._dependencyManager.resolveProvider(theType);
|
||||||
|
return (await provider.provide(theType)) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
async start(): Promise<unknown[]> {
|
||||||
|
const types = await this._dependencyManager.getEagerTypes();
|
||||||
|
|
||||||
|
const instances = await Promise.all(
|
||||||
|
types.map(async type => {
|
||||||
|
const provider = await this._dependencyManager.resolveProvider(type);
|
||||||
|
return provider.provide(type);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
return instances;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAvailableTypes(): Promise<Type[]> {
|
||||||
|
return this._dependencyManager.getTypes();
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this._scope.dispose();
|
||||||
|
|
||||||
|
// Notify all listeners
|
||||||
|
for (const callback of this._endedCallbacks) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return 'Inject';
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/di/InstanceProvider.ts
Normal file
47
src/di/InstanceProvider.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2025 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {Type} from './Type';
|
||||||
|
import type {IDependencyProvider} from './IDependencyProvider';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dependency provider for pre-existing instances.
|
||||||
|
*/
|
||||||
|
export class InstanceProvider implements IDependencyProvider {
|
||||||
|
private readonly _type: Type;
|
||||||
|
private readonly _instance: unknown;
|
||||||
|
|
||||||
|
constructor(type: Type, instance: unknown) {
|
||||||
|
if (!(type instanceof Type)) {
|
||||||
|
throw new Error('Must provide a valid type');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instance === null || instance === undefined) {
|
||||||
|
throw new Error('Must provide a valid instance');
|
||||||
|
}
|
||||||
|
|
||||||
|
this._type = type;
|
||||||
|
this._instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
canProvide(type: Type): boolean {
|
||||||
|
if (!(type instanceof Type)) {
|
||||||
|
throw new Error('Must provide a valid type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.equals(this._type);
|
||||||
|
}
|
||||||
|
|
||||||
|
async provide(type: Type): Promise<unknown> {
|
||||||
|
if (!(type instanceof Type)) {
|
||||||
|
throw new Error('Must provide a valid type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
toString(): string {
|
||||||
|
return 'InstanceProvider';
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/di/IntegerConstantProvider.ts
Normal file
33
src/di/IntegerConstantProvider.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2025 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {IDependencyProvider} from './IDependencyProvider';
|
||||||
|
import {Type} from './Type';
|
||||||
|
import {NamedType} from './NamedType';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dependency provider for integer constants.
|
||||||
|
* Uses NamedType where the name is parsed as an integer.
|
||||||
|
*/
|
||||||
|
export class IntegerConstantProvider implements IDependencyProvider {
|
||||||
|
public canProvide(type: Type): boolean {
|
||||||
|
if (!(type instanceof Type)) {
|
||||||
|
throw new Error('Must provide a valid type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return type instanceof NamedType && type.getType() === 'di/IntegerConstantProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provide(type: Type): Promise<number> {
|
||||||
|
if (!(type instanceof NamedType)) {
|
||||||
|
throw new Error('Must provide a NamedType');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseInt(type.getName(), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return 'IntegerConstantProvider';
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/di/StringConstantProvider.ts
Normal file
33
src/di/StringConstantProvider.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright 2025 Phenix Real Time Solutions, Inc. Confidential and Proprietary. All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type {IDependencyProvider} from './IDependencyProvider';
|
||||||
|
import {Type} from './Type';
|
||||||
|
import {NamedType} from './NamedType';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dependency provider for string constants.
|
||||||
|
* Uses NamedType where the name is the string value.
|
||||||
|
*/
|
||||||
|
export class StringConstantProvider implements IDependencyProvider {
|
||||||
|
public canProvide(type: Type): boolean {
|
||||||
|
if (!(type instanceof Type)) {
|
||||||
|
throw new Error('Must provide a valid type');
|
||||||
|
}
|
||||||
|
|
||||||
|
return type instanceof NamedType && type.getType() === 'di/StringConstantProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provide(type: Type): Promise<string> {
|
||||||
|
if (!(type instanceof NamedType)) {
|
||||||
|
throw new Error('Must provide a NamedType');
|
||||||
|
}
|
||||||
|
|
||||||
|
return type.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return 'StringConstantProvider';
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user