UNPKG

@elsikora/cladi

Version:

Zero-dependency TypeScript DI toolkit with typed tokens and scoped lifecycles.

633 lines (629 loc) 31.2 kB
'use strict'; var captiveDependencyPolicy_enum = require('../../../domain/enum/captive-dependency-policy.enum.js'); var dependencyLifecycle_enum = require('../../../domain/enum/dependency-lifecycle.enum.js'); var diContainerDuplicateProviderPolicy_enum = require('../../../domain/enum/di-container-duplicate-provider-policy.enum.js'); require('../../../domain/enum/logger-log-level.enum.js'); require('../../../domain/enum/provider-type.enum.js'); var error_class = require('../base/error.class.js'); var coordinator_class = require('./cache/coordinator.class.js'); var coordinator_class$4 = require('./disposal/coordinator.class.js'); var manager_class = require('./disposal/manager.class.js'); var coordinator_class$3 = require('./disposer/coordinator.class.js'); var resolution_class = require('./engine/resolution.class.js'); var coordinator_class$2 = require('./graph/coordinator.class.js'); var dispatcher_class = require('./interceptor/dispatcher.class.js'); var coordinator_class$1 = require('./lookup/coordinator.class.js'); var coordinator_class$5 = require('./registration/coordinator.class.js'); var coordinator_class$6 = require('./resolution/coordinator.class.js'); var consoleLogger_service = require('../../service/console-logger.service.js'); var toError_utility = require('../../utility/to-error.utility.js'); /** * Advanced DI container with scoped and async resolution support. */ class DIContainer { id; get isLocked() { return this.isScopeLocked; } ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS; CACHE_COORDINATOR; CAPTIVE_DEPENDENCY_POLICY; CHILD_SCOPES; DEPENDENCY_GRAPH_COORDINATOR; DISPOSAL_COORDINATOR; DISPOSAL_MANAGER; disposePromise; DISPOSER_COORDINATOR; DISPOSERS; DISPOSERS_BY_TOKEN; DUPLICATE_PROVIDER_POLICY; isDisposed; isDisposing; isScopeLocked; LOGGER; LOOKUP_COORDINATOR; PARENT; PENDING_PROVIDER_CLEANUPS; PROVIDERS; REGISTRATION_COORDINATOR; RESOLUTION_COORDINATOR; RESOLUTION_ENGINE; RESOLVE_INTERCEPTOR_DISPATCHER; RESOLVE_INTERCEPTORS; ROOT; ROOT_SCOPE_COUNTER; ROOT_SINGLETON_CACHE; SCOPED_CACHE; constructor(options = {}) { this.PARENT = options.parent; this.ROOT = options.root ?? this; this.ROOT_SCOPE_COUNTER = this.ROOT === this ? { value: 0 } : this.ROOT.ROOT_SCOPE_COUNTER; const normalizedScopeName = options.scopeName?.trim(); this.id = normalizedScopeName && normalizedScopeName.length > 0 ? normalizedScopeName : `scope-${String(++this.ROOT_SCOPE_COUNTER.value)}`; this.LOGGER = options.logger ?? options.parent?.LOGGER ?? new consoleLogger_service.ConsoleLoggerService(); this.ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS = options.asyncResolutionDrainTimeoutMs ?? options.parent?.ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS; this.CAPTIVE_DEPENDENCY_POLICY = options.captiveDependencyPolicy ?? options.parent?.CAPTIVE_DEPENDENCY_POLICY ?? captiveDependencyPolicy_enum.EDiContainerCaptiveDependencyPolicy.WARN; this.DUPLICATE_PROVIDER_POLICY = options.duplicateProviderPolicy ?? options.parent?.DUPLICATE_PROVIDER_POLICY ?? diContainerDuplicateProviderPolicy_enum.EDiContainerDuplicateProviderPolicy.OVERWRITE; this.PROVIDERS = new Map(); this.ROOT_SINGLETON_CACHE = this.ROOT === this ? new Map() : this.ROOT.ROOT_SINGLETON_CACHE; this.SCOPED_CACHE = new Map(); this.CACHE_COORDINATOR = this.ROOT === this ? new coordinator_class.CacheCoordinator() : this.ROOT.CACHE_COORDINATOR; this.LOOKUP_COORDINATOR = this.ROOT === this ? new coordinator_class$1.LookupCoordinator() : this.ROOT.LOOKUP_COORDINATOR; this.DEPENDENCY_GRAPH_COORDINATOR = new coordinator_class$2.DependencyGraphCoordinator({ assertKey: (dependencyKey) => this.assertToken(dependencyKey), captiveDependencyPolicy: this.CAPTIVE_DEPENDENCY_POLICY, describeKey: (dependencyKeySymbol) => this.describeToken(dependencyKeySymbol), getChildScopes: (scopeNode) => [...scopeNode.CHILD_SCOPES], getCurrentScopeId: () => this.id, getParent: (scopeNode) => scopeNode.PARENT, getRegistrationsByToken: (scopeNode) => scopeNode.PROVIDERS, getRegistrationsForScope: (scopeNode, dependencyKeySymbol) => scopeNode.PROVIDERS.get(dependencyKeySymbol), getScopeId: (scopeNode) => scopeNode.id, logger: this.LOGGER, lookupCoordinator: this.LOOKUP_COORDINATOR, }); this.DISPOSER_COORDINATOR = new coordinator_class$3.DisposerCoordinator({ logger: this.LOGGER, stringifyKey: (dependencyKeySymbol) => this.describeToken(dependencyKeySymbol), toError: toError_utility.toError, }); this.CHILD_SCOPES = new Set(); this.DISPOSERS = []; this.DISPOSERS_BY_TOKEN = new Map(); this.PENDING_PROVIDER_CLEANUPS = new Map(); this.RESOLVE_INTERCEPTORS = options.resolveInterceptors ? Object.freeze([...options.resolveInterceptors]) : (options.parent?.RESOLVE_INTERCEPTORS ?? Object.freeze([])); this.RESOLVE_INTERCEPTOR_DISPATCHER = new dispatcher_class.ResolveInterceptorDispatcher({ logger: this.LOGGER, resolveInterceptors: this.RESOLVE_INTERCEPTORS, stringifyKey: (dependencyKeySymbol) => this.describeToken(dependencyKeySymbol), toError: toError_utility.toError, }); this.disposePromise = undefined; this.isDisposed = false; this.isDisposing = false; this.isScopeLocked = false; this.DISPOSAL_MANAGER = new manager_class.DisposalManager({ disposeInternal: async () => { await this.disposeInternal(); }, getAsyncResolutionDrainTimeoutMs: () => this.ASYNC_RESOLUTION_DRAIN_TIMEOUT_MS, getDisposePromise: () => this.disposePromise, isDisposed: () => this.isDisposed, isDisposing: () => this.isDisposing, setDisposePromise: (disposePromise) => { this.disposePromise = disposePromise; }, setIsDisposing: (isDisposing) => { this.isDisposing = isDisposing; }, }); this.DISPOSAL_COORDINATOR = new coordinator_class$4.DisposalCoordinator({ clearChildScopes: () => { this.CHILD_SCOPES.clear(); }, clearDisposers: () => { this.DISPOSERS.length = 0; }, clearDisposersByToken: () => { this.DISPOSERS_BY_TOKEN.clear(); }, clearProviders: () => { this.PROVIDERS.clear(); }, clearRootSingletonCacheIfRootScope: () => { if (this === this.ROOT) { this.ROOT_SINGLETON_CACHE.clear(); } }, clearScopedCache: () => { this.SCOPED_CACHE.clear(); }, detachFromParent: () => { this.PARENT?.CHILD_SCOPES.delete(this); }, getChildScopesInReverse: () => [...this.CHILD_SCOPES.values()].reverse(), getDisposersInReverse: () => [...this.DISPOSERS].reverse(), getScopeId: () => this.id, releaseSingletonCacheForScopeProviders: () => { this.CACHE_COORDINATOR.removeSingletonCacheForOwnerRegistrations(this.ROOT_SINGLETON_CACHE, this, this.PROVIDERS); }, toError: toError_utility.toError, waitForInFlightAsyncResolutions: async () => { await this.DISPOSAL_MANAGER.waitForInFlightAsyncResolutions(); }, }); this.REGISTRATION_COORDINATOR = new coordinator_class$5.RegistrationCoordinator({ assertKey: (dependencyKey) => this.assertToken(dependencyKey), describeKey: (dependencyKeySymbol) => this.describeToken(dependencyKeySymbol), duplicateProviderPolicy: this.DUPLICATE_PROVIDER_POLICY, getProviderRegistrationCount: () => this.getProviderRegistrationCount(), getRegistrationsForProviderKey: (providerKey) => this.PROVIDERS.get(providerKey) ?? [], getScopeId: () => this.id, logger: this.LOGGER, onAfterSingleBindingRegistered: (providerKey, existingRegistrations, registration) => { const cacheKeysToInvalidate = existingRegistrations.length > 0 ? this.collectCacheKeys(existingRegistrations) : [registration.cacheKey]; this.CACHE_COORDINATOR.removeSingletonCacheForOwnerCacheKeys(this.ROOT_SINGLETON_CACHE, this, cacheKeysToInvalidate); this.CACHE_COORDINATOR.clearScopedCachesRecursivelyForCacheKeys(cacheKeysToInvalidate, providerKey, this, (scopeNode) => [...scopeNode.CHILD_SCOPES], (scopeNode) => scopeNode.SCOPED_CACHE, (scopeNode, dependencyKey) => scopeNode.PROVIDERS.has(dependencyKey)); }, onBeforeOverwrite: (providerKey, existingRegistrations) => { if (!this.hasTrackedDisposersForTokenRecursively(this, providerKey)) { return; } const cleanupPromise = this.DISPOSER_COORDINATOR.disposeCachedInstancesForTokenRecursivelyAsync(providerKey, this, { deleteProviderDisposersByToken: (scopeNode, dependencyKeySymbol) => { scopeNode.DISPOSERS_BY_TOKEN.delete(dependencyKeySymbol); }, getChildScopes: (scopeNode) => [...scopeNode.CHILD_SCOPES], getProviderDisposersByToken: (scopeNode, dependencyKeySymbol) => scopeNode.DISPOSERS_BY_TOKEN.get(dependencyKeySymbol), hasLocalProvider: (scopeNode, dependencyKeySymbol) => scopeNode.PROVIDERS.has(dependencyKeySymbol), removeTrackedDisposer: (scopeNode, disposer) => { const disposerIndex = scopeNode.DISPOSERS.indexOf(disposer); if (disposerIndex !== -1) { scopeNode.DISPOSERS.splice(disposerIndex, 1); } }, }); if (!this.hasAsyncCleanupHooks(existingRegistrations)) { void cleanupPromise.catch((error) => { this.logProviderCleanupFailure(providerKey, error); }); return; } this.queueProviderCleanup(providerKey, cleanupPromise); }, setRegistrationsForProviderKey: (providerKey, registrations) => { this.PROVIDERS.set(providerKey, registrations); }, }); this.RESOLUTION_COORDINATOR = new coordinator_class$6.ResolutionCoordinator({ assertKey: (dependencyKey) => this.assertToken(dependencyKey), assertSingleBindingLookup: (tokenSymbol, lookup) => this.DEPENDENCY_GRAPH_COORDINATOR.assertSingleBindingLookup(tokenSymbol, lookup), describeKey: (dependencyKeySymbol) => this.describeToken(dependencyKeySymbol), ensureActive: () => { this.ensureActive(); }, findProvider: (requestScope, dependencyKeySymbol) => this.DEPENDENCY_GRAPH_COORDINATOR.findProvider(requestScope, dependencyKeySymbol), getScopedCacheForLifecycle: (lifecycle, requestScope, providerOwner) => this.CACHE_COORDINATOR.getScopedCacheForLifecycle(lifecycle, requestScope.SCOPED_CACHE, this.ROOT_SINGLETON_CACHE, providerOwner), isProviderCleanupPending: (ownerScope, dependencyKeySymbol) => ownerScope.isProviderCleanupPending(dependencyKeySymbol), isRegistrationCurrent: (ownerScope, dependencyKeySymbol, registration) => this.isRegistrationCurrent(ownerScope, dependencyKeySymbol, registration), registerDisposer: (disposerScope, provider, instance, dependencyKeySymbol, registrationOptions) => this.DISPOSER_COORDINATOR.registerDisposer(disposerScope, provider, instance, dependencyKeySymbol, { getProviderDisposersByToken: (scopeNode, tokenSymbol) => scopeNode.DISPOSERS_BY_TOKEN.get(tokenSymbol), pushDisposer: (scopeNode, disposer) => { scopeNode.DISPOSERS.push(disposer); }, setProviderDisposersByToken: (scopeNode, tokenSymbol, providerDisposers) => { scopeNode.DISPOSERS_BY_TOKEN.set(tokenSymbol, providerDisposers); }, }, registrationOptions), validateCaptiveDependency: (parentRegistration, dependencyToken, resolutionScope) => { this.DEPENDENCY_GRAPH_COORDINATOR.validateCaptiveDependency(parentRegistration, dependencyToken, resolutionScope); }, waitForProviderCleanup: async (ownerScope, dependencyKeySymbol) => { await ownerScope.waitForProviderCleanup(dependencyKeySymbol); }, }); this.RESOLUTION_ENGINE = new resolution_class.ResolutionEngine({ assertKey: (dependencyKey) => this.assertToken(dependencyKey), ensureActive: () => { this.ensureActive(); }, ensureNotDisposing: () => { this.ensureNotDisposing(); }, getScopeId: () => this.id, notifyOnError: (tokenSymbol, isAsync, isResolveAll, isOptional, scopeId, error) => { this.RESOLVE_INTERCEPTOR_DISPATCHER.notifyOnError(tokenSymbol, isAsync, isResolveAll, isOptional, scopeId, error); }, notifyOnStart: (tokenSymbol, isAsync, isResolveAll, isOptional, scopeId) => { this.RESOLVE_INTERCEPTOR_DISPATCHER.notifyOnStart(tokenSymbol, isAsync, isResolveAll, isOptional, scopeId); }, notifyOnSuccess: (tokenSymbol, isAsync, isResolveAll, isOptional, scopeId, result) => { this.RESOLVE_INTERCEPTOR_DISPATCHER.notifyOnSuccess(tokenSymbol, isAsync, isResolveAll, isOptional, scopeId, result); }, onAsyncResolutionEnd: () => { this.DISPOSAL_MANAGER.onAsyncResolutionEnd(); }, onAsyncResolutionStart: () => { this.DISPOSAL_MANAGER.onAsyncResolutionStart(); }, resolveAllAsyncInternal: async (tokenSymbol, path, visitedTokens) => await this.RESOLUTION_COORDINATOR.resolveAllAsyncInternal(tokenSymbol, this, path, visitedTokens), resolveAllSyncInternal: (tokenSymbol, path, visitedTokens) => this.RESOLUTION_COORDINATOR.resolveAllSyncInternal(tokenSymbol, this, path, visitedTokens), resolveAsyncInternal: async (tokenSymbol, path, visitedTokens) => await this.RESOLUTION_COORDINATOR.resolveAsyncInternal(tokenSymbol, this, path, visitedTokens), resolveSyncInternal: (tokenSymbol, path, visitedTokens) => this.RESOLUTION_COORDINATOR.resolveSyncInternal(tokenSymbol, this, path, visitedTokens), }); if (this.PARENT) { this.PARENT.CHILD_SCOPES.add(this); } } async bootstrap(tokens) { this.ensureNotDisposing(); this.ensureActive(); const bootstrapTokens = tokens ?? this.getSingletonProviderTokens(); for (const bootstrapDependency of bootstrapTokens) { const key = this.assertToken(bootstrapDependency); const lookup = this.DEPENDENCY_GRAPH_COORDINATOR.findProvider(this, key); const registrations = lookup.registrations ?? (lookup.registration ? [lookup.registration] : []); if (registrations.length === 0) { throw new error_class.BaseError("Token not found in container", { code: "TOKEN_NOT_FOUND", context: { lookupPath: lookup.lookupPath, token: this.describeToken(key), }, source: "DIContainer", }); } const hasMultiBindingRegistration = registrations.some((registration) => registration.isMultiBinding); if (hasMultiBindingRegistration) { await this.resolveAllAsync(bootstrapDependency); continue; } await this.resolveAsync(bootstrapDependency); } } createScope(name, options) { this.ensureNotDisposing(); this.ensureActive(); return new DIContainer({ asyncResolutionDrainTimeoutMs: options?.asyncResolutionDrainTimeoutMs, captiveDependencyPolicy: options?.captiveDependencyPolicy, duplicateProviderPolicy: options?.duplicateProviderPolicy, logger: this.LOGGER, parent: this, resolveInterceptors: options?.resolveInterceptors, root: this.ROOT, scopeName: name, }); } async dispose() { await this.DISPOSAL_MANAGER.dispose(); } explain(token) { this.ensureNotDisposing(); this.ensureActive(); const key = this.assertToken(token); const lookup = this.DEPENDENCY_GRAPH_COORDINATOR.findProvider(this, key); const registration = lookup.registration; if (!registration) { return { dependencies: [], hasRootSingletonCache: this.CACHE_COORDINATOR.hasSingletonCacheForAnyOwner(this.ROOT_SINGLETON_CACHE, key, (ownerScope, dependencyKey) => ownerScope.PROVIDERS.get(dependencyKey)), hasScopeCache: this.CACHE_COORDINATOR.hasScopedCacheForLookup(lookup, this.SCOPED_CACHE, key), isFound: false, lookupPath: lookup.lookupPath, scopeId: this.id, token: this.describeToken(key), }; } return { dependencies: this.DEPENDENCY_GRAPH_COORDINATOR.getDependencyTokens(registration).map((dependencyToken) => this.describeToken(this.assertToken(dependencyToken))), hasRootSingletonCache: this.CACHE_COORDINATOR.hasSingletonCacheForOwner(this.ROOT_SINGLETON_CACHE, lookup.owner, lookup.registrations), hasScopeCache: this.CACHE_COORDINATOR.hasScopedCacheForLookup(lookup, this.SCOPED_CACHE, key), isAsyncFactory: registration.isAsyncFactory, isFound: true, lifecycle: registration.lifecycle, lookupPath: lookup.lookupPath, providerType: registration.type, scopeId: lookup.owner.id, token: this.describeToken(key), }; } exportGraph() { this.ensureNotDisposing(); this.ensureActive(); const graphNodes = []; const graphEdges = []; for (const [dependencyKeySymbol, registrations] of this.PROVIDERS.entries()) { const keyDescription = this.describeToken(dependencyKeySymbol); for (const registration of registrations) { graphNodes.push({ isMultiBinding: registration.isMultiBinding, lifecycle: registration.lifecycle, providerType: registration.type, scopeId: this.id, token: keyDescription, }); const dependencyTokens = this.DEPENDENCY_GRAPH_COORDINATOR.getDependencyTokens(registration); for (const dependencyToken of dependencyTokens) { graphEdges.push({ from: keyDescription, scopeId: this.id, to: this.describeToken(this.assertToken(dependencyToken)), }); } } } return { edges: graphEdges, nodes: graphNodes, scopeId: this.id, }; } getRegisteredTokens() { this.ensureNotDisposing(); this.ensureActive(); return [...this.PROVIDERS.keys()]; } has(token) { this.ensureNotDisposing(); this.ensureActive(); const key = this.assertToken(token); const lookup = this.DEPENDENCY_GRAPH_COORDINATOR.findProvider(this, key); return lookup.registration !== undefined; } lock() { this.ensureNotDisposing(); this.ensureActive(); this.isScopeLocked = true; } register(providerOrProviders) { this.ensureNotDisposing(); this.ensureActive(); this.ensureNotLocked(); const providers = Array.isArray(providerOrProviders) ? providerOrProviders : [providerOrProviders]; for (const provider of providers) { this.REGISTRATION_COORDINATOR.registerProvider(provider); } } resolve(token) { return this.RESOLUTION_ENGINE.resolve(token); } resolveAll(token) { return this.RESOLUTION_ENGINE.resolveAll(token); } async resolveAllAsync(token) { return await this.RESOLUTION_ENGINE.resolveAllAsync(token); } async resolveAsync(token) { return await this.RESOLUTION_ENGINE.resolveAsync(token); } resolveOptional(token) { return this.RESOLUTION_ENGINE.resolveOptional(token, (tokenSymbol) => this.DEPENDENCY_GRAPH_COORDINATOR.findProvider(this, tokenSymbol).registration !== undefined); } async resolveOptionalAsync(token) { return await this.RESOLUTION_ENGINE.resolveOptionalAsync(token, (tokenSymbol) => this.DEPENDENCY_GRAPH_COORDINATOR.findProvider(this, tokenSymbol).registration !== undefined); } snapshot() { this.ensureNotDisposing(); this.ensureActive(); const dependencyRegistrations = [...this.PROVIDERS.entries()].map(([dependencyKeySymbol, registrations]) => ({ hasMultiBinding: registrations.some((registration) => registration.isMultiBinding), registrationCount: registrations.length, token: this.describeToken(dependencyKeySymbol), })); return { childScopeCount: this.CHILD_SCOPES.size, isDisposed: this.isDisposed, isLocked: this.isScopeLocked, parentScopeId: this.PARENT?.id, providerCount: this.getProviderRegistrationCount(), rootScopeId: this.ROOT.id, scopedCacheSize: this.SCOPED_CACHE.size, scopeId: this.id, singletonCacheSize: this.CACHE_COORDINATOR.getRootSingletonCacheSize(this.ROOT_SINGLETON_CACHE), tokenRegistrations: dependencyRegistrations, tokens: this.getRegisteredTokens().map((token) => this.describeToken(this.assertToken(token))), }; } async unregister(token) { this.ensureNotDisposing(); this.ensureActive(); this.ensureNotLocked(); const key = this.assertToken(token); const existingRegistrations = this.PROVIDERS.get(key); if (!existingRegistrations || existingRegistrations.length === 0) { return false; } this.PROVIDERS.delete(key); const cacheKeysToInvalidate = this.collectCacheKeys(existingRegistrations); this.CACHE_COORDINATOR.removeSingletonCacheForOwnerCacheKeys(this.ROOT_SINGLETON_CACHE, this, cacheKeysToInvalidate); this.CACHE_COORDINATOR.clearScopedCachesRecursivelyForCacheKeys(cacheKeysToInvalidate, key, this, (scopeNode) => [...scopeNode.CHILD_SCOPES], (scopeNode) => scopeNode.SCOPED_CACHE, (scopeNode, dependencyKey) => scopeNode.PROVIDERS.has(dependencyKey)); try { await this.DISPOSER_COORDINATOR.disposeCachedInstancesForTokenRecursivelyAsync(key, this, { deleteProviderDisposersByToken: (scopeNode, dependencyKeySymbol) => { scopeNode.DISPOSERS_BY_TOKEN.delete(dependencyKeySymbol); }, getChildScopes: (scopeNode) => [...scopeNode.CHILD_SCOPES], getProviderDisposersByToken: (scopeNode, dependencyKeySymbol) => scopeNode.DISPOSERS_BY_TOKEN.get(dependencyKeySymbol), hasLocalProvider: (scopeNode, dependencyKeySymbol) => scopeNode.PROVIDERS.has(dependencyKeySymbol), removeTrackedDisposer: (scopeNode, disposer) => { const disposerIndex = scopeNode.DISPOSERS.indexOf(disposer); if (disposerIndex !== -1) { scopeNode.DISPOSERS.splice(disposerIndex, 1); } }, }); } catch (error) { throw new error_class.BaseError("Provider was unregistered with cleanup errors", { cause: toError_utility.toError(error), code: "PROVIDER_UNREGISTER_CLEANUP_FAILED", context: { scopeId: this.id, token: this.describeToken(key), }, source: "DIContainer", }); } return true; } validate() { this.ensureNotDisposing(); this.ensureActive(); this.DEPENDENCY_GRAPH_COORDINATOR.validateScopeTree(this); } assertToken(token) { if (typeof token !== "symbol") { throw new error_class.BaseError("Token must be a symbol", { code: "TOKEN_NOT_SYMBOL", source: "DIContainer", }); } return token; } collectCacheKeys(registrations) { return registrations.map((registration) => registration.cacheKey); } describeToken(tokenSymbol) { return tokenSymbol.description ? `Symbol(${tokenSymbol.description})` : tokenSymbol.toString(); } async disposeInternal() { let shouldMarkScopeDisposed = true; try { await this.DISPOSAL_COORDINATOR.disposeInternal(); } catch (error) { const normalizedError = toError_utility.toError(error); if (normalizedError instanceof error_class.BaseError && normalizedError.code === "SCOPE_DISPOSE_ASYNC_DRAIN_TIMEOUT") { shouldMarkScopeDisposed = false; } throw error; } finally { if (shouldMarkScopeDisposed) { this.isDisposed = true; this.PENDING_PROVIDER_CLEANUPS.clear(); } this.isDisposing = false; this.disposePromise = undefined; } } ensureActive() { if (this.isDisposed) { throw new error_class.BaseError("Scope is already disposed", { code: "SCOPE_DISPOSED", context: { scopeId: this.id }, source: "DIContainer", }); } } ensureNotDisposing() { if (this.isDisposing) { throw new error_class.BaseError("Scope is disposing", { code: "SCOPE_DISPOSING", context: { scopeId: this.id }, source: "DIContainer", }); } } ensureNotLocked() { if (this.isScopeLocked) { throw new error_class.BaseError("Scope is locked for registrations", { code: "SCOPE_LOCKED", context: { scopeId: this.id }, source: "DIContainer", }); } } getProviderRegistrationCount() { let totalRegistrations = 0; for (const registrations of this.PROVIDERS.values()) { totalRegistrations += registrations.length; } return totalRegistrations; } getSingletonProviderTokens() { const singletonTokens = []; for (const [tokenSymbol, registrations] of this.PROVIDERS.entries()) { const hasSingletonRegistration = registrations.some((registration) => registration.lifecycle === dependencyLifecycle_enum.EDependencyLifecycle.SINGLETON); if (hasSingletonRegistration) { singletonTokens.push(tokenSymbol); } } return singletonTokens; } hasAsyncCleanupHooks(existingRegistrations) { return existingRegistrations.some((registration) => { const onDisposeHook = registration.provider.onDispose; if (!onDisposeHook) { return false; } const hookConstructor = onDisposeHook.constructor; if (typeof hookConstructor === "function" && hookConstructor.name === "AsyncFunction") { return true; } return Object.prototype.toString.call(onDisposeHook) === "[object AsyncFunction]"; }); } hasTrackedDisposersForTokenRecursively(scopeNode, dependencyKeySymbol) { const localDisposers = scopeNode.DISPOSERS_BY_TOKEN.get(dependencyKeySymbol); if (localDisposers && localDisposers.length > 0) { return true; } for (const childScope of scopeNode.CHILD_SCOPES) { if (childScope.PROVIDERS.has(dependencyKeySymbol)) { continue; } if (this.hasTrackedDisposersForTokenRecursively(childScope, dependencyKeySymbol)) { return true; } } return false; } isProviderCleanupPending(dependencyKeySymbol) { const cleanupState = this.PENDING_PROVIDER_CLEANUPS.get(dependencyKeySymbol); return cleanupState !== undefined && !cleanupState.isSettled; } isRegistrationCurrent(ownerScope, tokenSymbol, registration) { const currentRegistrations = ownerScope.PROVIDERS.get(tokenSymbol); return currentRegistrations?.includes(registration) ?? false; } logProviderCleanupFailure(dependencyKeySymbol, error) { this.LOGGER.error("Provider cleanup failed during token overwrite", { context: { error: toError_utility.toError(error).message, scopeId: this.id, token: this.describeToken(dependencyKeySymbol), }, source: "DIContainer", }); } queueProviderCleanup(dependencyKeySymbol, cleanupPromise) { const trackedCleanupPromise = cleanupPromise.catch((error) => { this.logProviderCleanupFailure(dependencyKeySymbol, error); throw toError_utility.toError(error); }); const cleanupState = { isSettled: false, promise: trackedCleanupPromise, }; const markCleanupSettled = () => { cleanupState.isSettled = true; if (this.PENDING_PROVIDER_CLEANUPS.get(dependencyKeySymbol) === cleanupState) { this.PENDING_PROVIDER_CLEANUPS.delete(dependencyKeySymbol); } }; this.PENDING_PROVIDER_CLEANUPS.set(dependencyKeySymbol, cleanupState); void trackedCleanupPromise.then(markCleanupSettled, markCleanupSettled); void trackedCleanupPromise.catch(() => undefined); } async waitForProviderCleanup(dependencyKeySymbol) { const cleanupState = this.PENDING_PROVIDER_CLEANUPS.get(dependencyKeySymbol); if (!cleanupState) { return; } await cleanupState.promise; } } exports.DIContainer = DIContainer; //# sourceMappingURL=container.class.js.map