UNPKG

@furystack/inject

Version:
170 lines 6.83 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var Injector_1; import { isAsyncDisposable, isDisposable } from '@furystack/utils'; import { Injectable, getInjectableOptions } from './injectable.js'; import { getDependencyList } from './injected.js'; import { withInjectorReference } from './with-injector-reference.js'; const hasInitMethod = (obj) => { return typeof obj.init === 'function'; }; export class InjectorAlreadyDisposedError extends Error { constructor() { super('Injector already disposed'); } } const lifetimesToCache = ['singleton', 'scoped', 'explicit']; export class CannotInstantiateExplicitLifetimeError extends Error { /** * */ constructor(ctor) { super(`Cannot instantiate an instance of '${ctor.name}' as it's lifetime is set to 'explicit'. Ensure to initialize it properly`); } } let Injector = Injector_1 = class Injector { isDisposed = false; /** * Disposes the Injector object and all its disposable injectables */ async [Symbol.asyncDispose]() { if (this.isDisposed) { throw new InjectorAlreadyDisposedError(); } this.isDisposed = true; const singletons = Array.from(this.cachedSingletons.entries()).map((e) => e[1]); const disposeRequests = singletons .filter((s) => s !== this) .map(async (s) => { if (isDisposable(s)) { s[Symbol.dispose](); } if (isAsyncDisposable(s)) { await s[Symbol.asyncDispose](); } }); const result = await Promise.allSettled(disposeRequests); const fails = result.filter((r) => r.status === 'rejected'); if (fails && fails.length) { throw new Error(`There was an error during disposing '${fails.length}' global disposable objects: ${fails .map((f) => f.reason) .join(', ')}`); } this.cachedSingletons.clear(); } /** * Options object for an injector instance */ options = {}; // public static injectableFields: Map<Constructable<any>, { [K: string]: Constructable<any> }> = new Map() cachedSingletons = new Map(); remove = (ctor) => { if (this.isDisposed) { throw new InjectorAlreadyDisposedError(); } this.cachedSingletons.delete(ctor); }; getInstance(ctor) { return withInjectorReference(this.getInstanceInternal(ctor), this); } /** * * @param ctor The constructor object (e.g. MyClass) * @returns The instance of the requested service */ getInstanceInternal(ctor) { if (this.isDisposed) { throw new InjectorAlreadyDisposedError(); } if (ctor === this.constructor) { return this; } const existing = this.cachedSingletons.get(ctor); if (existing) { return existing; } const meta = getInjectableOptions(ctor); const { lifetime } = meta; const fromParent = (lifetime === 'singleton' || lifetime === 'explicit') && this.options.parent && this.options.parent.getInstanceInternal(ctor); if (fromParent) { return fromParent; } if (lifetime === 'explicit') { throw new CannotInstantiateExplicitLifetimeError(ctor); } const dependencies = [...getDependencyList(ctor)]; if (dependencies.includes(ctor)) { throw Error(`Circular dependencies found.`); } if (lifetime === 'singleton') { const invalidDeps = dependencies .map((dep) => ({ meta: getInjectableOptions(dep), dep })) .filter((m) => m.meta && (m.meta.lifetime === 'scoped' || m.meta.lifetime === 'transient')) .map((i) => i.meta && `${i.dep.name}:${i.meta.lifetime}`); if (invalidDeps.length) { throw Error(`Injector error: Singleton type '${ctor.name}' depends on non-singleton injectables: ${invalidDeps.join(',')}`); } } else if (lifetime === 'scoped') { const invalidDeps = dependencies .map((dep) => ({ meta: getInjectableOptions(dep), dep })) .filter((m) => m.meta && m.meta.lifetime === 'transient') .map((i) => i.meta && `${i.dep.name}:${i.meta.lifetime}`); if (invalidDeps.length) { throw Error(`Injector error: Scoped type '${ctor.name}' depends on transient injectables: ${invalidDeps.join(',')}`); } } const newInstance = new ctor(); if (lifetimesToCache.includes(lifetime)) { this.setExplicitInstance(newInstance); } if (hasInitMethod(newInstance)) { withInjectorReference(newInstance, this).init(this); } return newInstance; } /** * Sets explicitliy an instance for a key in the store * @param instance The created instance * @param key The class key to be persisted (optional, calls back to the instance's constructor) */ setExplicitInstance(instance, key) { if (this.isDisposed) { throw new InjectorAlreadyDisposedError(); } const ctor = key || instance.constructor; const { lifetime } = getInjectableOptions(ctor); if (instance.constructor === this.constructor) { throw Error('Cannot set an injector instance as injectable'); } if (!lifetimesToCache.includes(lifetime)) { throw new Error(`Cannot set an instance of '${ctor.name}' as it's lifetime is set to '${lifetime}'`); } this.cachedSingletons.set(ctor, instance); } /** * Creates a child injector instance * @param options Additional injector options * @returns the created Injector */ createChild(options) { if (this.isDisposed) { throw new InjectorAlreadyDisposedError(); } const i = new Injector_1(); i.options = i.options || options; i.options.parent = this; return i; } }; Injector = Injector_1 = __decorate([ Injectable({ lifetime: 'system' }) ], Injector); export { Injector }; //# sourceMappingURL=injector.js.map