@inversifyjs/container
Version:
InversifyJs container
190 lines • 8.96 kB
JavaScript
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