@furystack/inject
Version:
Core FuryStack package
170 lines • 6.83 kB
JavaScript
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