@miracledevs/paradigm-web-di
Version:
A minimal dependency injection library for the web
113 lines (90 loc) • 4.39 kB
text/typescript
import "reflect-metadata";
import { ObjectType, getObjectTypeName } from "./object-type";
import { DependencyLifeTime } from "./dependency-life-time";
import { DependencyDescriptor } from "./dependency-descriptor";
import { DependencyCollection } from "./dependency-collection";
export class DependencyContainer
{
private _scopedInstances: Map<ObjectType, any>;
private _collection: DependencyCollection;
private _parent?: DependencyContainer;
public readonly name?: string;
private constructor(parent: DependencyContainer | undefined, collection: DependencyCollection, name: string | undefined)
{
this._scopedInstances = new Map<ObjectType, any>();
this._collection = collection;
this._parent = parent;
this.name = name;
}
public static createFromCollection(collection: DependencyCollection, name?: string)
{
if (!collection)
throw new Error("Can not create a dependency container without a dependency collection.");
return new DependencyContainer(undefined, collection, name);
}
createScopedInjector(name?: string): DependencyContainer
{
return new DependencyContainer(this, this._collection, name);
}
resolve<T = any>(objectType: ObjectType<T>): T
{
try
{
if (objectType as any === DependencyContainer)
return this as any;
// this will throw if the object type is not registered.
const descriptor = this._collection.get(objectType);
switch (descriptor.lifeTime)
{
//////////////////////////////////////////////////////////////
// Singletons
//////////////////////////////////////////////////////////////
// if the descriptor tells is a singleton we try to create
// the instance or we return the instance we have.
case DependencyLifeTime.Singleton:
if (!descriptor.instance)
descriptor.instance = this.createInstance(objectType, descriptor);
return descriptor.instance
//////////////////////////////////////////////////////////////
// Scoped
//////////////////////////////////////////////////////////////
// the scoped instances are scoped singletons, singletons that
// live the scoped injector instead of the global scope
case DependencyLifeTime.Scoped:
if (!this._parent)
throw new Error("Can not instantiate a scoped type in the global container.");
const instance = this.getInstance(objectType);
if (!instance)
{
this._scopedInstances.set(objectType, this.createInstance(objectType, descriptor));
return this._scopedInstances.get(objectType);
}
return instance;
//////////////////////////////////////////////////////////////
// Transient
//////////////////////////////////////////////////////////////
case DependencyLifeTime.Transient:
return this.createInstance(objectType, descriptor);
default:
throw new Error(`Life time parameter not recognized as a valid life time.`);
}
}
catch (e)
{
throw new Error(`Couldn't instantiate the type ${getObjectTypeName(objectType)}.\n - ${(e instanceof Error? e.message : e)}`);
}
}
private createInstance<T>(objectType: ObjectType<T>, descriptor: DependencyDescriptor): T
{
const parameters = [null].concat(descriptor.dependencies.map(x => this.resolve(x))) as any;
return new (Function.prototype.bind.apply(objectType, parameters));
}
private getInstance<T>(objectType: ObjectType<T>): T | undefined
{
if (this._scopedInstances.has(objectType))
return this._scopedInstances.get(objectType);
if (this._parent)
return this._parent.getInstance(objectType);
return undefined;
}
}