UNPKG

ts-ioc-container

Version:

Fast, lightweight TypeScript dependency injection container with a clean API, scoped lifecycles, decorators, tokens, hooks, lazy injection, customizable providers, and no global container objects.

187 lines (186 loc) 6.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Container = void 0; const EmptyContainer_1 = require("./EmptyContainer"); const ContainerDisposedError_1 = require("../errors/ContainerDisposedError"); const MetadataInjector_1 = require("../injector/MetadataInjector"); const AliasMap_1 = require("./AliasMap"); const DependencyNotFoundError_1 = require("../errors/DependencyNotFoundError"); const ContainerNotFoundError_1 = require("../errors/ContainerNotFoundError"); const basic_1 = require("../utils/basic"); const array_1 = require("../utils/array"); class Container { isDisposed = false; parent; scopes = []; instances = []; registrations = []; tags; providers = new Map(); aliases = new AliasMap_1.AliasMap(); injector; onConstructHookList = []; onDisposeHookList = []; constructor(options = {}) { this.injector = options.injector ?? new MetadataInjector_1.MetadataInjector(); this.parent = options.parent ?? new EmptyContainer_1.EmptyContainer(); this.tags = new Set(options.tags ?? []); } register(key, provider, { aliases = [] } = {}) { this.validateContainer(); this.providers.set(key, provider); this.aliases.setAliasesByKey(key, aliases); return this; } resolve(target, { args = [], child = this, lazy } = {}) { this.validateContainer(); if (basic_1.Is.constructor(target)) { return this.injector.resolve(this, target, { args, lazy }); } const provider = this.providers.get(target); return provider?.hasAccess({ invocationScope: child, providerScope: this, args }) ? provider.resolve(this, { args, lazy }) : this.parent.resolve(target, { args, child, lazy }); } resolveByAlias(alias, { args = [], child = this, lazy, excludedKeys = [] } = {}) { this.validateContainer(); const keys = []; const deps = []; for (const key of this.aliases.getKeysByAlias(alias).filter(array_1.Filter.exclude(excludedKeys))) { const provider = this.findProviderByKeyOrFail(key); if (!provider.hasAccess({ invocationScope: child, providerScope: this, args })) { continue; } keys.push(key); deps.push(provider.resolve(this, { args, lazy })); } const parentDeps = this.parent.resolveByAlias(alias, { args, child, lazy, excludedKeys: [...excludedKeys, ...keys], }); return [...deps, ...parentDeps]; } resolveOneByAlias(alias, { args = [], child = this, lazy } = {}) { this.validateContainer(); const [key, ..._] = this.aliases.getKeysByAlias(alias); const provider = key ? this.findProviderByKeyOrFail(key) : undefined; return provider?.hasAccess({ invocationScope: child, providerScope: this, args }) ? provider.resolve(this, { args, lazy }) : this.parent.resolveOneByAlias(alias, { args, child, lazy }); } createScope({ tags } = {}) { this.validateContainer(); const scope = new Container({ injector: this.injector, parent: this, tags }) .addOnConstructHook(...this.onConstructHookList) .addOnDisposeHook(...this.onDisposeHookList); for (const registration of [...this.parent.getRegistrations(), ...this.registrations]) { registration.applyTo(scope); } this.scopes.push(scope); return scope; } dispose() { this.validateContainer(); this.isDisposed = true; // Execute onDispose hooks while (this.onDisposeHookList.length) { const onDispose = this.onDisposeHookList.shift(); onDispose(this); } // Detach from parent this.parent.removeScope(this); this.parent = new EmptyContainer_1.EmptyContainer(); // Reset the state for (const [_, provider] of this.providers) { provider.dispose(); } this.providers.clear(); this.aliases.destroy(); this.instances = []; this.registrations = []; // Clear hooks this.onConstructHookList.splice(0, this.onConstructHookList.length); } addRegistration(registration) { this.registrations.push(registration); registration.applyTo(this); return this; } getRegistrations() { return [...this.parent.getRegistrations(), ...this.registrations]; } hasRegistration(key) { return this.registrations.some((r) => r.getKeyOrFail() === key) || this.parent.hasRegistration(key); } addOnConstructHook(...hooks) { this.onConstructHookList.push(...hooks); return this; } addOnDisposeHook(...hooks) { this.onDisposeHookList.push(...hooks); return this; } addInstance(instance) { this.instances.push(instance); // Execute onConstruct hooks for (const onConstruct of this.onConstructHookList) { onConstruct(instance, this); } } getScopes() { return [...this.scopes]; } hasInstance(instance) { return this.instances.includes(instance); } getScopeByInstanceOrFail(instance) { this.validateContainer(); const queue = [this]; while (queue.length > 0) { const scope = queue.shift(); if (scope.hasInstance(instance)) { return scope; } queue.push(...scope.getScopes()); } throw new ContainerNotFoundError_1.ContainerNotFoundError('Cannot find scope for the given instance'); } removeScope(child) { this.scopes = this.scopes.filter((s) => s !== child); } useModule(module) { module.applyTo(this); return this; } getParent() { return this.parent; } getInstances(cascade = false) { if (!cascade) { return [...this.instances]; } return [...this.instances, ...this.scopes.flatMap((s) => s.getInstances(true))]; } hasTag(tag) { return this.tags.has(tag); } addTags(...tags) { for (const tag of tags) { this.tags.add(tag); } } validateContainer() { if (this.isDisposed) { throw new ContainerDisposedError_1.ContainerDisposedError('Container is already disposed'); } } findProviderByKeyOrFail(key) { if (!this.providers.has(key)) { throw new DependencyNotFoundError_1.DependencyNotFoundError(`Provider ${key.toString()} does not exist`); } return this.providers.get(key); } } exports.Container = Container;