Initial Commit

This commit is contained in:
2025-10-28 20:29:46 -04:00
commit 3a7f5fe75d
12 changed files with 1024 additions and 0 deletions

115
src/di/DependencyManager.ts Normal file
View File

@@ -0,0 +1,115 @@
type Registration = {
Class: new (...args: any[]) => any;
dependencies: string[];
lifecycle: Lifecycle;
};
/**
* Lifecycle types:
* - 'singleton': A single shared instance cached for the lifetime of the manager
* - 'instance': A new instance created on each resolve (transient)
* - 'service': A new instance created on each resolve (transient, alias for 'instance')
*/
type Lifecycle = 'instance' | 'service' | 'singleton';
export class DependencyManager {
private static readonly _supportedLifecycles: string[] = ['instance', 'service', 'singleton'];
private readonly _resolving: Set<string>;
private readonly _registrations: Map<string, Registration>;
private readonly _singletons: Map<string, unknown>;
constructor() {
this._resolving = new Set();
this._registrations = new Map();
this._singletons = new Map();
}
/**
* Registers a class with the dependency manager.
* @param name - Unique identifier for the registration
* @param Class - The class constructor to instantiate
* @param dependencies - Array of dependency names to inject
* @param lifecycle - Lifecycle of the instance ('singleton', 'instance', or 'service'). Default: 'singleton'
* @throws Error if name is already registered or lifecycle is invalid
*/
public register(name: string, Class: new (...args: any[]) => any, dependencies: string[], lifecycle = 'singleton') {
if (this._registrations.has(name)) {
throw new Error(`Error: [${name}] is already registered`);
}
if (!DependencyManager._supportedLifecycles.includes(lifecycle)) {
throw new Error(`Invalid lifecycle [${lifecycle}]. Supported lifecycles [${DependencyManager._supportedLifecycles.join(',')}]`);
}
this._registrations.set(name, {
Class,
dependencies,
lifecycle: lifecycle as Lifecycle
});
console.debug(`Registered [${name}] with dependencies [${dependencies.join(',')}]`);
}
/**
* Resolves and returns an instance of the registered dependency.
* @param name - The name of the dependency to resolve
* @returns The resolved instance
* @throws Error if dependency is not registered or circular dependency is detected
*/
public resolve<T = any>(name: string): T {
if (!this._registrations.has(name)) {
throw new Error(`Error: Unable to resolve unregistered [${name}]`);
}
const registration = this._registrations.get(name);
const {Class, dependencies, lifecycle} = registration!;
if (lifecycle === 'singleton' && this._singletons.has(name)) {
console.debug(`↻ Using cached singleton [${name}]`);
return this._singletons.get(name) as T;
}
// Check for circular dependencies
if (this._resolving.has(name)) {
throw new Error(`Circular dependency detected: [${name}]`);
}
console.debug(`→ Resolving dependencies for [${name}] with lifecycle [${lifecycle}]`);
this._resolving.add(name);
const resolvedDependencies: unknown[] = dependencies.map((dependency: string) => {
console.debug(` ↳ Needs [${dependency}]`);
return this.resolve(dependency);
});
const instance: object = new Class(...resolvedDependencies);
this._resolving.delete(name);
if (lifecycle === 'singleton') {
this._singletons.set(name, instance);
console.debug(` ⚡ Cached as singleton: ${name}`);
}
return instance as T;
}
/**
* Checks if a dependency is registered.
* @param name - The name of the dependency to check
* @returns True if the dependency is registered, false otherwise
*/
public has(name: string): boolean {
return this._registrations.has(name);
}
/**
* Clears all cached singleton instances.
* Useful for testing or resetting the dependency manager state.
*/
public clearSingletons(): void {
this._singletons.clear();
}
}

0
src/index.ts Normal file
View File