UNPKG

dioma

Version:

Elegant dependency injection container for vanilla JavaScript and TypeScript

216 lines (215 loc) 6.89 kB
class DependencyCycleError extends Error { constructor() { super("Circular dependency detected"); } } class AsyncDependencyCycleError extends Error { constructor() { super("Circular dependency detected in async resolution"); } } class ArgumentsError extends Error { constructor(scope, className) { super(`Arguments are not supported for ${scope} of ${className}`); } } class TokenNotRegisteredError extends Error { constructor() { super("Token is not registered in the container"); } } const _Scopes = class _Scopes { static Singleton() { return function SingletonScope(descriptor, args) { if (args.length > 0) { throw new ArgumentsError(SingletonScope.name, descriptor.class.name); } return globalContainer.$getInstance(descriptor); }; } static Transient() { return function TransientScope(descriptor, args, container) { return container.$getInstance(descriptor, args, false); }; } static Container() { return function ContainerScope(descriptor, args, container) { if (args.length > 0) { throw new ArgumentsError(ContainerScope.name, descriptor.class.name); } return container.$getInstance(descriptor); }; } static Resolution() { return function ResolutionScope(descriptor, args, _, resolutionContainer) { return resolutionContainer.$getInstance(descriptor, args); }; } }; _Scopes.Scoped = _Scopes.Container; let Scopes = _Scopes; class Token { constructor(name) { this.name = name; } } const MAX_LOOP_COUNT = 100; class Container { constructor(parentContainer = null, name) { this.parentContainer = parentContainer; this.name = name; this.instances = /* @__PURE__ */ new WeakMap(); this.resolutionContainer = null; this.resolutionSet = /* @__PURE__ */ new Set(); this.pendingPromiseMap = /* @__PURE__ */ new Map(); this.tokenDescriptorMap = /* @__PURE__ */ new Map(); this.resolutionsLoopCounter = 0; this.childContainer = (name2) => { return new Container(this, name2); }; this.inject = (cls, ...args) => { return this.injectImpl(cls, args, void 0); }; this.injectAsync = (cls, ...args) => { const resolutionContainer = this.resolutionContainer; this.resolutionsLoopCounter += 1; if (this.resolutionsLoopCounter > MAX_LOOP_COUNT) { throw new AsyncDependencyCycleError(); } if (this.pendingPromiseMap.has(cls)) { return this.pendingPromiseMap.get(cls); } if (this.instances.has(cls)) { return Promise.resolve(this.instances.get(cls)); } const promise = Promise.resolve().then(() => { try { return this.injectImpl(cls, args, resolutionContainer); } finally { this.pendingPromiseMap.delete(cls); } }); this.pendingPromiseMap.set(cls, promise); return promise; }; this.waitAsync = async () => { return new Promise((resolve) => setTimeout(resolve, 0)); }; this.unregister = (token) => { const descriptor = this.getTokenDescriptor(token); this.tokenDescriptorMap.delete(token); this.instances.delete(token); if (descriptor && "class" in descriptor) { this.tokenDescriptorMap.delete(descriptor.class); this.instances.delete(descriptor.class); } }; this.reset = () => { this.instances = /* @__PURE__ */ new WeakMap(); this.resolutionSet.clear(); this.pendingPromiseMap.clear(); this.tokenDescriptorMap.clear(); this.resolutionContainer = null; this.resolutionsLoopCounter = 0; }; this.register = this.register.bind(this); } $getInstance(descriptor, args = [], cache = true) { let instance = null; let container = this; const cls = descriptor.class; while (!instance && container) { instance = container.instances.get(cls); container = container.parentContainer; } if (!instance) { if (descriptor.beforeCreate) { descriptor.beforeCreate(this, descriptor, args); } instance = new cls(...args); if (cache) { this.instances.set(cls, instance); } } return instance; } getTokenDescriptor(clsOrToken) { let tokenDescriptor; let container = this; while (!tokenDescriptor && container) { tokenDescriptor = container.tokenDescriptorMap.get(clsOrToken); container = container.parentContainer; } return tokenDescriptor; } injectImpl(clsOrToken, args, resolutionContainer = this.resolutionContainer) { this.resolutionContainer = resolutionContainer || new Container(); try { if (this.resolutionSet.has(clsOrToken)) { throw new DependencyCycleError(); } let cls = clsOrToken; let scope; let container = this; let descriptor = this.getTokenDescriptor(clsOrToken); this.resolutionSet.add(clsOrToken); if (!descriptor) { if (clsOrToken instanceof Token) { throw new TokenNotRegisteredError(); } cls = clsOrToken; scope = cls.scope || Scopes.Transient(); container = this; descriptor = { class: cls, container: this }; } else { if (descriptor.beforeInject) { descriptor.beforeInject(container, descriptor, args); } if ("class" in descriptor) { cls = descriptor.class; scope = descriptor.scope || cls.scope || Scopes.Transient(); container = descriptor.container; } else if ("value" in descriptor) { return descriptor.value; } else if ("factory" in descriptor) { return descriptor.factory(container, ...args); } else { throw new Error("Invalid descriptor"); } } return scope(descriptor, args, container, this.resolutionContainer); } finally { this.resolutionSet.delete(clsOrToken); this.resolutionContainer = resolutionContainer; if (!resolutionContainer) { this.resolutionsLoopCounter = 0; } } } register(tokenDescriptor) { const token = tokenDescriptor.token || tokenDescriptor.class; const descriptorWithContainer = { ...tokenDescriptor, container: this }; this.tokenDescriptorMap.set(token, descriptorWithContainer); if (tokenDescriptor.class) { this.tokenDescriptorMap.set(token.class, descriptorWithContainer); } } } const globalContainer = new Container(null, "Global container"); const inject = globalContainer.inject; const injectAsync = globalContainer.injectAsync; const childContainer = globalContainer.childContainer; export { ArgumentsError, AsyncDependencyCycleError, Container, DependencyCycleError, Scopes, Token, TokenNotRegisteredError, childContainer, globalContainer, inject, injectAsync }; //# sourceMappingURL=dioma.js.map