UNPKG

@apiratorjs/di-container

Version:

A lightweight dependency injection container for JavaScript and TypeScript with powerful features: modular organization with DiModule.create, lazy initialization, automatic circular dependency detection, and multiple service lifecycles (singleton with bot

200 lines 7.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DiConfigurator = void 0; const async_context_1 = require("@apiratorjs/async-context"); const di_container_1 = require("./di-container"); const utils_1 = require("./utils"); const locking_1 = require("@apiratorjs/locking"); const errors_1 = require("./errors"); const DI_CONTAINER_REQUEST_SCOPE_NAMESPACE = "APIRATORJS_DI_CONTAINER_REQUEST_SCOPE_NAMESPACE"; class DiConfigurator { constructor() { this._singletonServices = new Map(); this._singletonServiceFactories = new Map(); this._requestScopeServiceFactories = new Map(); this._transientFactories = new Map(); this._serviceMutexes = new Map(); this._resolutionChains = new Map(); this._registeredModules = new Set(); } addSingleton(token, factory, options) { this._singletonServiceFactories.set(token, { factory, options }); return this; } async disposeSingletons() { await Promise.all(Array.from(this._singletonServices.values()).map(async (service) => { if (service?.onDispose) { await service.onDispose(); } })); this._singletonServices.clear(); } addScoped(token, factory) { this._requestScopeServiceFactories.set(token, { factory }); return this; } addTransient(token, factory) { this._transientFactories.set(token, { factory }); return this; } addModule(module) { if (this._registeredModules.has(module)) { return this; } this._registeredModules.add(module); module.register(this); return this; } async resolve(token) { this.checkForCircularDependency(token); const mutex = this.getMutexFor(token); return await mutex.runExclusive(async () => { try { this.addToResolutionChain(token); return ((await this.tryGetSingleton(token)) ?? (await this.tryGetScoped(token)) ?? (await this.tryGetTransient(token)) ?? (function () { throw new errors_1.UnregisteredDependencyError(token); })()); } finally { this.removeFromResolutionChain(token); } }); } async runWithNewRequestScope(initialStore, callback) { return await async_context_1.AsyncContext.withContext(DI_CONTAINER_REQUEST_SCOPE_NAMESPACE, initialStore ?? new async_context_1.AsyncContextStore(), async () => { try { return await callback(); } finally { await this.disposeScopedServices(); } }); } getRequestScopeContext() { return async_context_1.AsyncContext.getContext(DI_CONTAINER_REQUEST_SCOPE_NAMESPACE); } isInRequestScopeContext() { return !!this.getRequestScopeContext(); } async build() { for (const [token, { options }] of this._singletonServiceFactories.entries()) { if (options?.isLazy) { continue; } await this.tryGetSingleton(token); } return new di_container_1.DiContainer(this); } // ============================ // Private methods // ============================ /** * Lazy initializes and returns a singleton service. */ async tryGetSingleton(token) { if (this._singletonServices.has(token)) { return this._singletonServices.get(token); } const { factory } = this._singletonServiceFactories.get(token) ?? {}; if (!factory) { return; } // Check if service was already initialized by another thread if (this._singletonServices.has(token)) { return this._singletonServices.get(token); } const service = await factory(this); if (service?.onConstruct) { await service.onConstruct(); } this._singletonServices.set(token, service); return service; } async tryGetScoped(token) { if (!this._requestScopeServiceFactories.has(token)) { return; } const store = this.getRequestScopeContext(); if (!store) { throw new errors_1.RequestScopeResolutionError(token); } if (store.has(token)) { return store.get(token); } // Check if service was already initialized by another thread if (store.has(token)) { return store.get(token); } const { factory } = this._requestScopeServiceFactories.get(token) ?? {}; if (!factory) { return; } const service = await factory(this); if (service?.onConstruct) { await service.onConstruct(); } store.set(token, service); return service; } async tryGetTransient(token) { if (!this._transientFactories.has(token)) { return; } const { factory } = this._transientFactories.get(token) ?? {}; if (!factory) { return; } const service = await factory(this); if (service?.onConstruct) { await service.onConstruct(); } return service; } async disposeScopedServices() { const scope = this.getRequestScopeContext(); if (!scope) { return; } for (const service of scope.values()) { if (service?.onDispose) { await service.onDispose(); } } } getMutexFor(token) { if (!this._serviceMutexes.has(token)) { this._serviceMutexes.set(token, new locking_1.Mutex()); } return this._serviceMutexes.get(token); } // ============================ // Circular dependency detection // ============================ getCurrentResolutionChain() { const currentScope = this.getRequestScopeContext(); if (!this._resolutionChains.has(currentScope)) { this._resolutionChains.set(currentScope, new Set()); } return this._resolutionChains.get(currentScope); } addToResolutionChain(token) { this.getCurrentResolutionChain().add(token); } removeFromResolutionChain(token) { this.getCurrentResolutionChain().delete(token); } checkForCircularDependency(token) { const currentChain = this.getCurrentResolutionChain(); if (currentChain.has(token)) { const chainTokens = Array.from(currentChain); const startIndex = chainTokens.findIndex((t) => t === token); const cycle = [...chainTokens.slice(startIndex), token].map((t) => (0, utils_1.tokenToString)(t)); throw new errors_1.CircularDependencyError(token, cycle); } } } exports.DiConfigurator = DiConfigurator; //# sourceMappingURL=di-configurator.js.map