import { Module } from './Module'; import { createBinder, InjectorMetaData, Bind, ResolveBinder } from './Binder'; export interface Newable { new (...args: any[]): T; } export type Identifier = Newable | symbol; export const INJECTION_MAP = Symbol(); export const CONTAINER_INSTANCE_PROP = Symbol(); export interface Snapshot { bindings: Map, InjectorMetaData>; staticInjections: Map; } export class Container { private bindings = new Map, InjectorMetaData>(); private staticInjections = new Map(); private _bind: Bind = createBinder( this.bindings.set.bind(this.bindings), this.bindings.has.bind(this.bindings) ); constructor(...modules: Array) { modules.forEach(module => module.init(this._bind)); } async get(identifier: Identifier): Promise { const data = this.bindings.get(identifier); if (data == null) { throw new Error(`no binding found for ${identifier.toString()}`); } if (data.scope === 'transient') { return this.resolve(data); } else if (data.scope === 'singleton') { if (!this.staticInjections.has(data)) { this.staticInjections.set(data, this.resolve(data)); } return this.staticInjections.get(data); } throw `not supported scope ${data.scope}`; } bind(identifier: Identifier): ResolveBinder { return this._bind(identifier); } unbind(identifier: Identifier) { const data = this.bindings.get(identifier); if (data != null) { this.bindings.delete(identifier); this.staticInjections.delete(data); } } isBound(identifier: Identifier) { return this.bindings.has(identifier); } snapshot(): Snapshot { return { bindings: new Map(this.bindings), staticInjections: new Map(this.staticInjections) }; } restore(snapshot: Snapshot) { this.bindings = snapshot.bindings; this.staticInjections = snapshot.staticInjections; } private async resolve(data: InjectorMetaData) { if (data.class != null) { return await this.resolveClass(data.class); } else if (data.factory != null) { return await data.factory(this); } else if (data.value) { return data.value; } throw `no injection target found`; } private async resolveClass(Class: new (...args: any[]) => T): Promise { const s = Reflect.getMetadata(INJECTION_MAP, Class) || []; const instance = new Class( ...(await Promise.all(s.map(this.get.bind(this)))) ); Object.defineProperty(instance, CONTAINER_INSTANCE_PROP, { configurable: false, writable: false, enumerable: false, value: this }); return instance; } }