UNPKG

@inversifyjs/container

Version:

InversifyJs container

190 lines 8.96 kB
import { isPromise, stringifyServiceIdentifier, } from '@inversifyjs/common'; import { CacheBindingInvalidationKind, resolveBindingsDeactivations, resolveServiceDeactivations, } from '@inversifyjs/core'; import { isBindingIdentifier } from '../../binding/calculations/isBindingIdentifier.js'; import { BindToFluentSyntaxImplementation } from '../../binding/models/BindingFluentSyntaxImplementation.js'; import { getFirstIterableResult } from '../../common/calculations/getFirstIterableResult.js'; import { InversifyContainerError } from '../../error/models/InversifyContainerError.js'; import { InversifyContainerErrorKind } from '../../error/models/InversifyContainerErrorKind.js'; export class BindingManager { #deactivationParams; #defaultScope; #planResultCacheManager; #serviceReferenceManager; constructor(deactivationParams, defaultScope, planResultCacheManager, serviceReferenceManager) { this.#deactivationParams = deactivationParams; this.#defaultScope = defaultScope; this.#planResultCacheManager = planResultCacheManager; this.#serviceReferenceManager = serviceReferenceManager; } bind(serviceIdentifier) { return new BindToFluentSyntaxImplementation((binding) => { this.#setBinding(binding); }, undefined, this.#defaultScope, serviceIdentifier); } isBound(serviceIdentifier, options) { const bindings = this.#serviceReferenceManager.bindingService.get(serviceIdentifier); return this.#isAnyValidBinding(serviceIdentifier, bindings, options); } isCurrentBound(serviceIdentifier, options) { const bindings = this.#serviceReferenceManager.bindingService.getNonParentBindings(serviceIdentifier); return this.#isAnyValidBinding(serviceIdentifier, bindings, options); } async rebindAsync(serviceIdentifier) { await this.unbindAsync(serviceIdentifier); return this.bind(serviceIdentifier); } rebind(serviceIdentifier) { this.unbind(serviceIdentifier); return this.bind(serviceIdentifier); } async unbindAsync(identifier) { await this.#unbindAsync(identifier); } async unbindAllAsync() { await this.#unbindAll(); } unbindAll() { const result = this.#unbindAll(); if (result !== undefined) { throw new InversifyContainerError(InversifyContainerErrorKind.invalidOperation, 'Unexpected asynchronous deactivation when unbinding all services. Consider using Container.unbindAllAsync() instead.'); } } unbind(identifier) { const result = this.#unbindAsync(identifier); if (result !== undefined) { this.#throwUnexpectedAsyncUnbindOperation(identifier); } } #setBinding(binding) { this.#serviceReferenceManager.bindingService.set(binding); this.#planResultCacheManager.invalidateService({ binding: binding, kind: CacheBindingInvalidationKind.bindingAdded, }); } #throwUnexpectedAsyncUnbindOperation(identifier) { let errorMessage; if (isBindingIdentifier(identifier)) { const bindingsById = this.#serviceReferenceManager.bindingService.getById(identifier.id); const bindingServiceIdentifier = getFirstIterableResult(bindingsById)?.serviceIdentifier; if (bindingServiceIdentifier === undefined) { errorMessage = 'Unexpected asynchronous deactivation when unbinding binding identifier. Consider using Container.unbindAsync() instead.'; } else { errorMessage = `Unexpected asynchronous deactivation when unbinding "${stringifyServiceIdentifier(bindingServiceIdentifier)}" binding. Consider using Container.unbindAsync() instead.`; } } else { errorMessage = `Unexpected asynchronous deactivation when unbinding "${stringifyServiceIdentifier(identifier)}" service. Consider using Container.unbindAsync() instead.`; } throw new InversifyContainerError(InversifyContainerErrorKind.invalidOperation, errorMessage); } #unbindAsync(identifier) { if (isBindingIdentifier(identifier)) { return this.#unbindBindingIdentifier(identifier); } return this.#unbindServiceIdentifier(identifier); } #unbindBindingIdentifier(identifier) { const bindingsIterable = this.#serviceReferenceManager.bindingService.getById(identifier.id); const bindings = bindingsIterable === undefined ? undefined : [...bindingsIterable]; const result = resolveBindingsDeactivations(this.#deactivationParams, bindingsIterable); if (result === undefined) { this.#clearAfterUnbindBindingIdentifier(bindings, identifier); } else { return result.then(() => { this.#clearAfterUnbindBindingIdentifier(bindings, identifier); }); } } #clearAfterUnbindBindingIdentifier(bindings, identifier) { this.#serviceReferenceManager.bindingService.removeById(identifier.id); if (bindings !== undefined) { for (const binding of bindings) { this.#planResultCacheManager.invalidateService({ binding, kind: CacheBindingInvalidationKind.bindingRemoved, }); } } } #unbindAll() { const nonParentBoundServiceIds = [ ...this.#serviceReferenceManager.bindingService.getNonParentBoundServices(), ]; const deactivationResults = nonParentBoundServiceIds.map((serviceId) => resolveServiceDeactivations(this.#deactivationParams, serviceId)); const hasAsyncDeactivations = deactivationResults.some((result) => isPromise(result)); if (hasAsyncDeactivations) { // eslint-disable-next-line @typescript-eslint/await-thenable return Promise.all(deactivationResults).then(() => { this.#clearAfterUnbindAll(nonParentBoundServiceIds); }); } this.#clearAfterUnbindAll(nonParentBoundServiceIds); } #clearAfterUnbindAll(serviceIds) { /* * Removing service related objects here so unbindAll is deterministic. * * Removing service related objects as soon as resolveModuleDeactivations takes * effect leads to module deactivations not triggering previously deleted * deactivations, introducing non determinism depending in the order in which * services are deactivated. */ for (const serviceId of serviceIds) { this.#serviceReferenceManager.activationService.removeAllByServiceId(serviceId); this.#serviceReferenceManager.bindingService.removeAllByServiceId(serviceId); this.#serviceReferenceManager.deactivationService.removeAllByServiceId(serviceId); } this.#serviceReferenceManager.planResultCacheService.clearCache(); } #unbindServiceIdentifier(identifier) { const bindingsIterable = this.#serviceReferenceManager.bindingService.get(identifier); const bindings = bindingsIterable === undefined ? undefined : [...bindingsIterable]; const result = resolveBindingsDeactivations(this.#deactivationParams, bindingsIterable); if (result === undefined) { this.#clearAfterUnbindServiceIdentifier(identifier, bindings); } else { return result.then(() => { this.#clearAfterUnbindServiceIdentifier(identifier, bindings); }); } } #clearAfterUnbindServiceIdentifier(identifier, bindings) { this.#serviceReferenceManager.activationService.removeAllByServiceId(identifier); this.#serviceReferenceManager.bindingService.removeAllByServiceId(identifier); this.#serviceReferenceManager.deactivationService.removeAllByServiceId(identifier); if (bindings !== undefined) { for (const binding of bindings) { this.#planResultCacheManager.invalidateService({ binding, kind: CacheBindingInvalidationKind.bindingRemoved, }); } } } #isAnyValidBinding(serviceIdentifier, bindings, options) { if (bindings === undefined) { return false; } const bindingConstraints = { getAncestor: () => undefined, name: options?.name, serviceIdentifier, tags: new Map(), }; if (options?.tag !== undefined) { bindingConstraints.tags.set(options.tag.key, options.tag.value); } for (const binding of bindings) { if (binding.isSatisfiedBy(bindingConstraints)) { return true; } } return false; } } //# sourceMappingURL=BindingManager.js.map