@appolo/inject
Version:
dependency injection for node js
420 lines (283 loc) • 11.2 kB
text/typescript
"use strict";
import {IOptions} from "../interfaces/IOptions";
import {IFactory} from "../interfaces/IFactory";
import {Class, IDefinition, IParamInject} from "../interfaces/IDefinition";
import {Define} from "../define/define";
import {InjectDefineSymbol, InjectParamSymbol} from "../decorators/decorators";
import {Util} from "../utils/util";
import {Event, IEvent} from "@appolo/events";
import {Events, InjectEvent} from "../events/events";
import {_get, _getFromDefinition} from "../methods/instances/_get";
import {_register} from "../methods/definitions/_register";
import {_loadFactory} from "../methods/factories/_loadFactory";
import {_addDefinitions} from "../methods/definitions/_addDefinitions";
import {_addDefinition} from "../methods/definitions/_addDefinition";
import {_getFactory} from "../methods/factories/_getFactory";
import {_getFactoryValue} from "../methods/factories/getFactoryValue";
import {_initInitMethods} from "../methods/methods/_initInitMethods";
import {_initAlias} from "../methods/alias/_initAlias";
import {_initFactories} from "../methods/factories/_initFactories";
import {_initProperties} from "../methods/properties/_initProperties";
import {_initInstances} from "../methods/instances/_initInstances";
import {_initDefinitions} from "../methods/definitions/_initDefinitions";
import {_initBootstrapMethods} from "../methods/methods/_initBootstrapMethods";
import {_prepareProperties} from "../methods/properties/_prepareProperties";
type keyObject = { [index: string]: Object }
export const IsWiredSymbol = "@__isWired__";
export class Injector {
protected _definitions: { [index: string]: IDefinition };
protected _instances: keyObject;
protected _factoriesObjects: { [index: string]: { [index: string]: { id: string, injector?: Injector } } };
protected _factoriesValues: { [index: string]: any };
protected _alias: { [index: string]: Object[] };
protected _aliasFactory: { [index: string]: Object[] };
protected _factories: string[];
protected _parent: Injector;
protected _children: Injector[];
protected _events = new Events()
protected _options: IOptions;
protected _isInitialized: boolean = false;
constructor() {
this._instances = {};
this._definitions = {};
this._options = {};
this._factoriesObjects = {};
this._alias = {};
this._aliasFactory = {};
this._factoriesValues = {};
this._factories = [];
this._children = []
}
public get parent(): Injector {
return this._parent
}
public set parent(value: Injector) {
this._parent = value;
value.children.push(this);
}
public get options(): IOptions {
return this._options
}
public get children(): Injector[] {
return this._children;
}
public async initialize(options?: IOptions) {
if (this._isInitialized) {
return;
}
this._options = options || {};
let definitions = {};
Object.keys(definitions || {}).forEach(id => this.addDefinition(id, definitions[id]));
//we have parent so we wait until parent.initialize
if (this.parent && !this._options.immediate) {
return;
}
await (this._events.beforeInitialize as Event<void>).fireEventAsync();
await this.initDefinitions();
await this.initFactories();
await this.initInstances();
await this.initProperties();
this.initAlias();
await this.initInitMethods();
await this.initBootstrapMethods();
this._isInitialized = true;
}
protected initDefinitions() {
return _initDefinitions.call(this);
}
protected initInstances() {
return _initInstances.call(this);
}
protected initProperties() {
return _initProperties.call(this);
}
protected initFactories() {
return _initFactories.call(this);
}
protected initAlias() {
return _initAlias.call(this);
}
protected initInitMethods() {
return _initInitMethods.call(this);
}
protected initBootstrapMethods() {
return _initBootstrapMethods.call(this);
}
public getObject<T>(objectID: string | Function, runtimeArgs?: any[]): T {
return this.get<T>(objectID, runtimeArgs)
}
public resolve<T>(objectID: string | Function, runtimeArgs?: any[]): T {
try {
return this.get<T>(objectID, runtimeArgs)
} catch (e) {
return null;
}
}
public async getAsync<T>(objectID: string | Function, runtimeArgs?: any[]): Promise<T> {
let id = Util.getClassId(objectID);
let def = this.getDefinition(id);
let obj = this.get<T>(objectID, runtimeArgs);
if (def.initMethodAsync) {
await obj[def.initMethodAsync]();
}
if (def.bootstrapMethodAsync) {
await obj[def.bootstrapMethodAsync]();
}
return obj;
}
public wire<T extends new (...args: any) => any>(klass: T, runtimeArgs?: ConstructorParameters<T>): InstanceType<T> {
let def = Util.getClassDefinition(klass)
if (!def) {
return new klass(...runtimeArgs as any[])
}
def = def.clone();
_prepareProperties.call(this, def.definition);
let instance = _getFromDefinition.call(this, def.definition, def.definition.id, []) as InstanceType<T>
return instance;
}
public get<T>(objectID: string | Function, runtimeArgs?: any[]): T {
objectID = Util.getClassNameOrId(objectID);
let def = this._definitions[objectID];
if (def && def.factory) {
return this.getFactoryValue(objectID, def);
}
return this._get<T>(objectID as string, runtimeArgs)
}
public getFactoryValue<T>(objectID: string, definitions?: IDefinition): T {
return _getFactoryValue.call(this, objectID, definitions) as T
}
public getFactory<T>(objectID: string | Function, refs?: { ids: {}, paths: string[] }): Promise<T> {
return _getFactory.call(this, objectID, refs) as Promise<T>
}
protected _get<T>(objectID: string, runtimeArgs?: any[]): T {
return _get.call(this, objectID, runtimeArgs) as T
}
public getInstance<T>(id: string): T {
let instance = this._instances[id];
if (instance) {
return instance as T;
}
if (this.parent) {
return this.parent.getInstance(id);
}
return null
}
public hasInstance(id: string) {
return !!this.getInstance(id)
}
public addDefinition(objectId: string, definition: IDefinition): Injector {
return _addDefinition.call(this, objectId, definition);
}
public removeDefinition(objectId: string): Injector {
delete this._definitions[objectId];
return this;
}
public addDefinitions(definitions: { [index: string]: IDefinition } | Map<string, IDefinition>): Injector {
return _addDefinitions.call(this, definitions);
}
public addObject<T>(objectId: string, instance: T, silent?: boolean): Injector {
return this.addInstance(objectId, instance, silent);
}
public addInstance<T>(objectId: string, instance: T, silent?: boolean): Injector {
if (!silent && this._instances[objectId]) {
console.log("Injector:object id already exists overriding: " + objectId);
}
this._instances[objectId] = instance;
return this;
}
public removeInstance(objectId: string): Injector {
delete this._instances[objectId];
return this;
}
public getObjectsByType<T>(type: Function): T[] {
let output = [];
for (let key in this._instances) {
if (this._instances.hasOwnProperty(key) && this._instances[key] instanceof type) {
output.push(this._instances[key])
}
}
return output;
}
public getInstances(): { [index: string]: { [index: string]: any } } {
return this._instances;
}
public getDefinitions(): { [index: string]: IDefinition } {
return this._definitions;
}
public getDefinitionsValue(): IDefinition[] {
return Object.values(this._definitions);
}
public getAliasDefinitions(alias: string): IDefinition[] {
return Object.values(this._definitions).filter(item => (item.alias || []).includes(alias))
}
public getTypes(): Function[] {
return this.getDefinitionsValue().map(item => item.type)
}
public hasDefinition(id: string): boolean {
return !!this.getDefinition(id);
}
public getDefinition(id: string | Function): IDefinition {
id = Util.getClassNameOrId(id);
let def = this.getOwnDefinition(id);
if (def) {
return def
}
if (this.parent) {
return this.parent.getDefinition(id);
}
}
public hasOwnDefinition(id: string): boolean {
return !!this.getOwnDefinition(id);
}
public getOwnDefinition(id: string | Function): IDefinition {
id = Util.getClassNameOrId(id);
let def = this._definitions[id];
if (def) {
return def.injector && def.injector !== this ? def.injector.getDefinition(def.refName || id) : def;
}
}
public addAlias(aliasName: string, value: any) {
this.getAlias(aliasName).push(value)
}
public removeAlias(aliasName: string, value: any) {
Util.removeFromArray(this.getAlias(aliasName), value)
}
public getAlias(aliasName: string): any[] {
return this._alias[aliasName] = this._alias[aliasName] || (this.parent ? this.parent.getAlias(aliasName) : []) || [];
}
public addAliasFactory(aliasName: string, value: any) {
this.getAliasFactory(aliasName).push(value);
}
public removeAliasFactory(aliasName: string, value: any) {
Util.removeFromArray(this.getAliasFactory(aliasName), value);
}
public getAliasFactory(aliasName: string): any[] {
return this._aliasFactory[aliasName] = this._aliasFactory[aliasName] || (this.parent ? this.parent.getAliasFactory(aliasName) : []) || [];
}
public getFactoryMethod(objectId: string | Function): Function {
return Util.createDelegate(this.get, this, [objectId]);
}
public registerMulti(fns: Class[]): this {
for (let i = 0, len = fns.length; i < len; i++) {
this.register(fns[i]);
}
return this;
}
public register(id: string | Class, type?: Class, filePath?: string): Define {
return _register.call(this, id, type, filePath);
}
public get events(): Events {
return this._events;
}
protected loadFactory<T>(objectId: string): Promise<void> {
return _loadFactory.call(this, objectId);
}
public reset() {
this._instances = {};
this._definitions = {};
this._options = {};
this._alias = {};
this._aliasFactory = {};
this._factoriesObjects = {};
}
}