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