better code structure in injector/decorator and implemented initialization priorities
This commit is contained in:
+14
-7
@@ -1,21 +1,28 @@
|
||||
import { Injector } from "./Injector";
|
||||
import { Type, GenericClassDecorator, Constructor } from "./Types";
|
||||
import { Type, GenericClassDecorator, Constructor } from "./Internals";
|
||||
|
||||
/**
|
||||
* @returns {GenericClassDecorator<Type<any>>}
|
||||
* @constructor
|
||||
*/
|
||||
export function Singleton(_interface?: Constructor<any>): GenericClassDecorator<Type<any>> {
|
||||
export function Singleton(config?: {
|
||||
interface?: Constructor<any>,
|
||||
initializationPriority?: number
|
||||
}): GenericClassDecorator<Type<any>> {
|
||||
return (clazz: Type<any>) => {
|
||||
Injector['modules'].push({
|
||||
implements: _interface ?? clazz,
|
||||
Injector['singletonDefinitions'].push({
|
||||
initializationPriority: config ?. initializationPriority,
|
||||
ctor: clazz
|
||||
})
|
||||
|
||||
if(config && config.interface){
|
||||
Injector['tokenLookupTable'][config.interface.name] = clazz
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function Inject(clazz: Constructor<any>) {
|
||||
return function (instance: Object, key: string) {
|
||||
Injector['injectionQueue'].push({ injectionType: clazz, instance: instance, injectIntoKey: key })
|
||||
export function Inject(token: Constructor<any>) {
|
||||
return function (receiver: Object, key: string) {
|
||||
Injector['injectionQueue'].push({ token, receiver, key })
|
||||
}
|
||||
}
|
||||
+44
-27
@@ -1,69 +1,86 @@
|
||||
import 'reflect-metadata';
|
||||
import { Constructor, Type } from './Types';
|
||||
import { ERR_NO_INITIALIZE_WITH_PRIORITY, ERR_NO_INJECTION_TOKEN } from './Strings';
|
||||
import { Constructor, Type, Module as SingletonDefinition, InjectionError, InjectionResolutionError } from './Internals';
|
||||
|
||||
class _Injector {
|
||||
|
||||
private injectionQueue: any[] = []
|
||||
private modules: { implements?: Constructor<any>, ctor: Type<any> }[] = []
|
||||
private moduleObjs: { [key in string]: any } = {}
|
||||
private singletonDefinitions: SingletonDefinition[] = []
|
||||
|
||||
private singletonObjects: { [classname in string]: any } = {}
|
||||
private tokenLookupTable: { [token in string]: Constructor<any> } = {}
|
||||
|
||||
private initialized = false
|
||||
|
||||
/**
|
||||
* Resolves instances by injecting required services
|
||||
* @param {Type<any>} target
|
||||
* @param {Type<any>} request
|
||||
* @returns {T}
|
||||
*/
|
||||
public resolve<T>(target: Constructor<T>): T {
|
||||
public resolve<T>(request: Constructor<T>): T {
|
||||
if (!this.initialized) {
|
||||
this.initialize()
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
return this.moduleObjs[target.name] as any
|
||||
return this.singletonObjects[request.name] as any
|
||||
}
|
||||
|
||||
public async resolveAsync<T>(target: Type<T>): Promise<T> {
|
||||
if (!this.initialized) {
|
||||
await this.initialize()
|
||||
this.initialize()
|
||||
this.initialized = true
|
||||
}
|
||||
|
||||
return this.moduleObjs[target.name] as any
|
||||
return this.singletonObjects[target.name] as any
|
||||
}
|
||||
|
||||
private initialize = async (async?: boolean) => {
|
||||
private initialize = () => {
|
||||
this.createSingletons()
|
||||
this.injectDependencies()
|
||||
if (async)
|
||||
await this.initializeSingletons()
|
||||
else
|
||||
this.initializeSingletons()
|
||||
this.initializeSingletons()
|
||||
this.cleanup()
|
||||
}
|
||||
|
||||
private createSingletons = () => {
|
||||
//instantiate all non-root modules
|
||||
this.modules.forEach(m => {
|
||||
const module = new m.ctor()
|
||||
if (m.implements)
|
||||
this.moduleObjs[m.implements.name] = module
|
||||
this.moduleObjs[m.ctor.name] = module
|
||||
this.singletonDefinitions.forEach(def => {
|
||||
const obj = new def.ctor()
|
||||
|
||||
if (def.initializationPriority != undefined && !obj.initialize) {
|
||||
throw new InjectionError(ERR_NO_INITIALIZE_WITH_PRIORITY(def.ctor))
|
||||
}
|
||||
|
||||
this.singletonObjects[def.ctor.name] = obj
|
||||
})
|
||||
}
|
||||
|
||||
private injectDependencies = () => {
|
||||
while (this.injectionQueue.length > 0) {
|
||||
const inj = this.injectionQueue.shift()
|
||||
this.injectionQueue.forEach(inj => {
|
||||
|
||||
if (this.moduleObjs[inj.injectionType.name]) {
|
||||
this.moduleObjs[inj.instance.constructor.name][inj.injectIntoKey] = this.moduleObjs[inj.injectionType.name]
|
||||
} else {
|
||||
throw new Error("Cannot resolve injection token " + inj.injectionType.name)
|
||||
if (inj.token.name in this.tokenLookupTable) { //injection alias was used
|
||||
inj.token = this.tokenLookupTable[inj.token.name]
|
||||
}
|
||||
}
|
||||
|
||||
if (this.singletonObjects[inj.token.name]) {
|
||||
this.singletonObjects[inj.receiver.constructor.name][inj.key] = this.singletonObjects[inj.token.name]
|
||||
} else {
|
||||
throw new InjectionResolutionError(ERR_NO_INJECTION_TOKEN(inj.injectionType))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private initializeSingletons = () => {
|
||||
Object.values(this.moduleObjs).forEach(element => element.initialize ? element.initialize() : undefined);
|
||||
this.singletonDefinitions
|
||||
.sort((a, b) => (a.initializationPriority ?? 0) - (b.initializationPriority ?? 0))
|
||||
.map(def => this.singletonObjects[def.ctor.name])
|
||||
.forEach(obj => obj.initialize ? obj.initialize() : undefined)
|
||||
}
|
||||
|
||||
private cleanup = () => {
|
||||
while (this.singletonDefinitions.length > 0)
|
||||
this.singletonDefinitions.pop()
|
||||
while (this.injectionQueue.length > 0)
|
||||
this.injectionQueue.pop()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
export interface Initializable {
|
||||
initialize: () => void
|
||||
}
|
||||
|
||||
export interface AsyncInitializable {
|
||||
initialize: () => void | Promise<void>
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Type for what object is instances of. Also applicable to "Constructor of T" as Types/Classes/Constructors are interchangable in TS.
|
||||
*/
|
||||
export interface Type<T> {
|
||||
new(...args: any[]): T;
|
||||
}
|
||||
|
||||
export type Constructor<T> = Function & { prototype: T }
|
||||
|
||||
/**
|
||||
* Generic `ClassDecorator` type
|
||||
*/
|
||||
export type GenericClassDecorator<T> = (target: T) => void;
|
||||
|
||||
export type Module = {
|
||||
initializationPriority?: number //Priority of initializing this object after creation
|
||||
ctor: Type<any> //Object constructor to make singleton from
|
||||
}
|
||||
|
||||
export class NamedError extends Error{
|
||||
constructor(message: string){
|
||||
super(message)
|
||||
this.name = this.constructor.name
|
||||
}
|
||||
}
|
||||
|
||||
export class InjectionError extends NamedError{
|
||||
constructor(message: string){
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
|
||||
export class InjectionResolutionError extends InjectionError{
|
||||
constructor(message: string){
|
||||
super(message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
import { Constructor } from "./Internals";
|
||||
|
||||
export const ERR_NO_INITIALIZE_WITH_PRIORITY = (ctor: Constructor<any>) => `The singleton class '${ctor.name}' specified an initialization priority but has no initialize() function. Either remove the 'initializationPriority' parameter or add a function of the signature 'public initialize():void'.`
|
||||
export const ERR_NO_INJECTION_TOKEN = (ctor: Constructor<any>) => `Could not resolve a singleton for '${ctor.name}'. Make sure the class is marked as '@Injectable()'. If a resolution token other than the classname is requested make sure it is registered via the 'interface' parameter.`
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* Type for what object is instances of. Also applicable to "Constructor of T" as Types/Classes/Constructors are interchangable in TS.
|
||||
*/
|
||||
export interface Type<T> {
|
||||
new(...args: any[]): T;
|
||||
}
|
||||
|
||||
export type Constructor<T> = Function & { prototype: T }
|
||||
|
||||
/**
|
||||
* Generic `ClassDecorator` type
|
||||
*/
|
||||
export type GenericClassDecorator<T> = (target: T) => void;
|
||||
|
||||
export interface ISingleton{
|
||||
initialize?(): void | Promise<void>
|
||||
}
|
||||
Reference in New Issue
Block a user