UNPKG

ts-ioc-container

Version:
146 lines (145 loc) 5.08 kB
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'); } } }