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:
2025-10-25 17:51:44 -04:00
parent 0de4b9314a
commit 00332dc6b1
7 changed files with 212 additions and 0 deletions

3
examples/di-example.ts Normal file
View File

@@ -0,0 +1,3 @@
import {DependencyManager} from '../src';
const dependencyManager = new DependencyManager(async path => await import(path));

View File

@@ -0,0 +1,3 @@
export interface IConfigurationLoader {
load(name: string): Promise<unknown>;
}

10
src/di/IInject.ts Normal file
View 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
View 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';
}
}

View 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';
}
}

View 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';
}
}

View 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';
}
}