UNPKG

@nivinjoseph/n-ject

Version:
204 lines (178 loc) 7.95 kB
import { given } from "@nivinjoseph/n-defensive"; import { InvalidOperationException, ObjectDisposedException } from "@nivinjoseph/n-exception"; import { BaseScope } from "./base-scope.js"; import { ChildScope } from "./child-scope.js"; import { ComponentInstaller } from "./component-installer.js"; import { ComponentRegistry } from "./component-registry.js"; import { Lifestyle } from "./lifestyle.js"; import { Registry } from "./registry.js"; import { ReservedKeys } from "./reserved-keys.js"; import { Scope } from "./scope.js"; import { ScopeType } from "./scope-type.js"; /** * The main IoC container class that manages component registration and scope creation. * This is the entry point for using the dependency injection framework. */ export class Container extends BaseScope implements Registry { private _myDisposePromise: Promise<void> | null = null; /** * Creates a new instance of the Container. */ public constructor() { super(ScopeType.Root, new ComponentRegistry(), null); } /** * Registers a component with transient lifecycle. * Creates a new instance each time the component is resolved. * @param key - The unique identifier for the component * @param component - The component class or function to register * @param aliases - Optional aliases for the component * @returns The container instance for method chaining */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type public registerTransient(key: string, component: Function, ...aliases: Array<string>): Registry { this._register(key, component, Lifestyle.Transient, ...aliases); return this; } /** * Registers a component with scoped lifecycle. * Creates one instance per scope. * @param key - The unique identifier for the component * @param component - The component class or function to register * @param aliases - Optional aliases for the component * @returns The container instance for method chaining */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type public registerScoped(key: string, component: Function, ...aliases: Array<string>): Registry { this._register(key, component, Lifestyle.Scoped, ...aliases); return this; } /** * Registers a component with singleton lifecycle. * Creates one instance for the entire container. * @param key - The unique identifier for the component * @param component - The component class or function to register * @param aliases - Optional aliases for the component * @returns The container instance for method chaining */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type public registerSingleton(key: string, component: Function, ...aliases: Array<string>): Registry { this._register(key, component, Lifestyle.Singleton, ...aliases); return this; } /** * Registers a pre-created instance. * Uses the provided instance for all resolutions. * @param key - The unique identifier for the component * @param instance - The pre-created instance to register * @param aliases - Optional aliases for the component * @returns The container instance for method chaining */ public registerInstance(key: string, instance: object, ...aliases: Array<string>): Registry { this._register(key, instance, Lifestyle.Instance, ...aliases); return this; } /** * Installs components using an installer. * @param componentInstaller - The installer to use * @returns The container instance for method chaining * @throws ObjectDisposedException if the container is disposed * @throws InvalidOperationException if the container is bootstrapped */ public install(componentInstaller: ComponentInstaller): Container { if (this.isDisposed) throw new ObjectDisposedException(this); if (this.isBootstrapped) throw new InvalidOperationException("install after bootstrap"); given(componentInstaller, "componentInstaller").ensureHasValue(); componentInstaller.install(this); return this; } /** * Creates a new scope for dependency resolution. * @returns A new scope instance * @throws ObjectDisposedException if the container is disposed * @throws InvalidOperationException if the container is not bootstrapped */ public createScope(): Scope { if (this.isDisposed) throw new ObjectDisposedException(this); if (!this.isBootstrapped) throw new InvalidOperationException("createScope after bootstrap"); return new ChildScope(this.componentRegistry, this); } /** * Bootstraps the container. * Verifies all registrations and prepares the container for use. * @throws ObjectDisposedException if the container is disposed * @throws InvalidOperationException if the container is already bootstrapped */ public override bootstrap(): void { if (this.isDisposed) throw new ObjectDisposedException(this); if (this.isBootstrapped) throw new InvalidOperationException("bootstrap after bootstrap"); this.componentRegistry.verifyRegistrations(); super.bootstrap(); } /** * Disposes the container and its resources. * Cleans up all scopes and releases resources. * @returns A promise that resolves when disposal is complete */ public override dispose(): Promise<void> { if (this._myDisposePromise == null) this._myDisposePromise = super.dispose().then(() => this.componentRegistry.dispose()); return this._myDisposePromise; } /** * Removes a component registration. * @param key - The component key to deregister * @throws ObjectDisposedException if the container is disposed * @throws InvalidOperationException if the container is bootstrapped */ public deregister(key: string): void { if (this.isDisposed) throw new ObjectDisposedException(this); if (this.isBootstrapped) throw new InvalidOperationException("register after bootstrap"); given(key, "key").ensureHasValue(); this.componentRegistry.deregister(key); } /** * Internal method to register a component with the specified lifecycle. * @param key - The unique identifier for the component * @param component - The component class, function, or instance to register * @param lifestyle - The lifecycle to use for the component * @param aliases - Optional aliases for the component * @throws ObjectDisposedException if the container is disposed * @throws InvalidOperationException if the container is bootstrapped */ // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type private _register(key: string, component: Function | object, lifestyle: Lifestyle, ...aliases: Array<string>): void { if (this.isDisposed) throw new ObjectDisposedException(this); if (this.isBootstrapped) throw new InvalidOperationException("register after bootstrap"); given(key, "key").ensureHasValue().ensureIsString() .ensure(t => !ReservedKeys.all.contains(t.trim()), "cannot use reserved key"); given(component, "component").ensureHasValue(); given(lifestyle, "lifestyle").ensureHasValue().ensureIsNumber(); given(aliases, "aliases").ensureHasValue().ensureIsArray() .ensure(t => t.every(u => u !== key), "alias cannot be the same as key") .ensure(t => t.length === t.distinct().length, "duplicates detected"); this.componentRegistry.register(key, component, lifestyle, ...aliases); } }