ts-ioc-container
Version:
Typescript IoC container
146 lines (145 loc) • 5.08 kB
JavaScript
import { DEFAULT_CONTAINER_RESOLVER as resolveOne, } from './IContainer';
import { EmptyContainer } from './EmptyContainer';
import { ContainerDisposedError } from '../errors/ContainerDisposedError';
import { MetadataInjector } from '../injector/MetadataInjector';
import { Filter as F } from '../utils';
import { ProviderMap } from './ProviderMap';
import { AliasMap } from './AliasMap';
export class Container {
isDisposed = false;
parent;
scopes = new Set();
instances = new Set();
tags;
providerMap = new ProviderMap();
aliasMap = new AliasMap();
registrations = new Set();
onConstruct;
onDispose;
injector;
constructor(options = {}) {
this.injector = options.injector ?? new MetadataInjector();
this.parent = options.parent ?? new EmptyContainer();
this.tags = new Set(options.tags ?? []);
this.onConstruct = options.onConstruct ?? (() => { });
this.onDispose = options.onDispose ?? (() => { });
}
register(key, provider, { aliases = [] } = {}) {
this.validateContainer();
this.providerMap.register(key, provider);
this.aliasMap.deleteKeyFromAliases(key);
this.aliasMap.addAliases(key, aliases);
return this;
}
addRegistration(registration) {
this.registrations.add(registration);
registration.applyTo(this);
return this;
}
getRegistrations() {
return [...this.parent.getRegistrations(), ...this.registrations];
}
resolveByClass(token, { args = [] } = {}) {
this.validateContainer();
const instance = this.injector.resolve(this, token, { args });
this.instances.add(instance);
this.onConstruct(instance, this);
return instance;
}
resolveOne(keyOrAlias, options) {
return resolveOne(this, keyOrAlias, options);
}
resolveOneByKey(keyOrAlias, { args = [], child = this, lazy } = {}) {
this.validateContainer();
const provider = this.providerMap.findOneByKey(keyOrAlias);
return provider?.hasAccess({ invocationScope: child, providerScope: this })
? provider.resolve(this, { args, lazy })
: this.parent.resolveOneByKey(keyOrAlias, { args, child, lazy });
}
resolveOneByAlias(keyOrAlias, { args = [], child = this, lazy } = {}) {
this.validateContainer();
const key = this.aliasMap.findLastKeyByAlias(keyOrAlias);
const provider = key !== undefined ? this.providerMap.findOneByKeyOrFail(key) : undefined;
return provider?.hasAccess({ invocationScope: child, providerScope: this })
? provider.resolve(this, { args, lazy })
: this.parent.resolveOneByAlias(keyOrAlias, { args, child, lazy });
}
resolveMany(alias, { args = [], child = this, lazy, excludedKeys = new Set() } = {}) {
this.validateContainer();
const keys = [];
const deps = [];
for (const key of this.aliasMap.findManyKeysByAlias(alias).filter(F.exclude(excludedKeys))) {
const provider = this.providerMap.findOneByKeyOrFail(key);
if (!provider.hasAccess({ invocationScope: child, providerScope: this })) {
continue;
}
keys.push(key);
deps.push(provider.resolve(this, { args, lazy }));
}
const parentDeps = this.parent.resolveMany(alias, {
args,
child,
lazy,
excludedKeys: new Set([...excludedKeys, ...keys]),
});
return [...deps, ...parentDeps];
}
createScope({ tags = [] } = {}) {
this.validateContainer();
const scope = new Container({
injector: this.injector,
parent: this,
tags,
onDispose: this.onDispose,
onConstruct: this.onConstruct,
});
scope.applyRegistrationsFrom(this);
this.scopes.add(scope);
return scope;
}
getScopes() {
return [...this.scopes];
}
removeScope(child) {
this.scopes.delete(child);
}
useModule(module) {
module.applyTo(this);
return this;
}
getParent() {
return this.parent;
}
getInstances() {
return [...this.instances];
}
hasTag(tag) {
return this.tags.has(tag);
}
dispose() {
this.validateContainer();
this.isDisposed = true;
// Detach from parent
this.parent.removeScope(this);
this.parent = new EmptyContainer();
// Reset the state
this.providerMap.destroy();
this.aliasMap.destroy();
this.instances.clear();
this.registrations.clear();
this.onDispose(this);
}
/**
* @private
*/
applyRegistrationsFrom(source) {
for (const registration of source.getRegistrations()) {
registration.applyTo(this);
}
}
validateContainer() {
if (this.isDisposed) {
throw new ContainerDisposedError('Container is already disposed');
}
}
}