UNPKG

@akala/core

Version:
187 lines (161 loc) 6.58 kB
import * as di from './global-injector' import orchestrator from 'orchestrator' import { EventEmitter } from 'events' import { Injector, InjectableWithTypedThis, InjectableAsyncWithTypedThis, Injectable } from './injector'; process.hrtime = process.hrtime || require('browser-process-hrtime'); export class ExtendableEvent { private promises: PromiseLike<any>[] = []; public waitUntil<T>(p: PromiseLike<T>): void { this.promises.push(p) } public complete() { return Promise.all(this.promises).finally(() => this._done = true); } public get done() { return this._done; } private _done: boolean; } export class Module extends Injector { constructor(public name: string, public dep?: string[]) { super(moduleInjector); var existingModule = moduleInjector.resolve<Module>(name); if (existingModule && typeof (dep) != 'undefined') throw new Error('the module can be registered only once with dependencies'); if (existingModule) { if (typeof (dep) != 'undefined') { delete Module.o.tasks[name + '#activate']; delete Module.o.tasks[name + '#ready']; delete Module.o.tasks[name]; existingModule.dep = dep; moduleInjector.unregister(name); Module.registerModule(existingModule); } return existingModule; } Module.registerModule(this); this.emitter.setMaxListeners(0); } private emitter = new EventEmitter(); private static o = new orchestrator(); public readonly activateEvent = new ExtendableEvent(); public readonly readyEvent = new ExtendableEvent(); public static registerModule(m: Module) { var emitter = m.emitter; if (typeof m.dep == 'undefined') m.dep = []; Module.o.add(m.name + '#activate', m.dep.map(dep => dep + '#activate'), function (done) { emitter.emit('activate', m.activateEvent); m.activateEvent.complete().then(() => { done(); }, done); }); Module.o.add(m.name + '#ready', [m.name + '#activate'].concat(m.dep.map(dep => dep + '#ready')), function (done) { emitter.emit('ready', m.readyEvent); m.readyEvent.complete().then(() => { done(); }, done); }); Module.o.add(m.name, [m.name + '#ready'], function () { }); moduleInjector.register(m.name, m); } public ready(toInject: string[], f: InjectableWithTypedThis<any, ExtendableEvent>) public ready(toInject: string[]): (f: InjectableWithTypedThis<any, ExtendableEvent>) => this public ready(toInject: string[], f?: InjectableWithTypedThis<any, ExtendableEvent>) { if (!f) return (f: InjectableWithTypedThis<any, ExtendableEvent>) => this.ready(toInject, f); if (this.readyEvent.done) this.injectWithName(toInject, f)(); else this.emitter.on('ready', this.injectWithName(toInject, f)); return this; } public readyAsync(toInject: string[], f: InjectableWithTypedThis<any, ExtendableEvent>) public readyAsync(toInject: string[]): (f: InjectableWithTypedThis<any, ExtendableEvent>) => this public readyAsync(toInject: string[], f?: InjectableAsyncWithTypedThis<any, ExtendableEvent>) { if (!f) return (f: InjectableWithTypedThis<any, ExtendableEvent>) => this.readyAsync(toInject, f); if (this.readyEvent.done) return this.injectWithNameAsync(toInject, f.bind(this.readyEvent) as InjectableAsyncWithTypedThis<any, ExtendableEvent>); else this.emitter.on('ready', (ev) => { this.injectWithNameAsync(toInject, f.bind(ev) as InjectableAsyncWithTypedThis<any, ExtendableEvent>) }); return this; } public activate(toInject: string[], f: InjectableWithTypedThis<any, ExtendableEvent>) public activate(toInject: string[]): (f: InjectableWithTypedThis<any, ExtendableEvent>) => this public activate(toInject: string[], f?: InjectableWithTypedThis<any, ExtendableEvent>) { if (!f) return (f: InjectableWithTypedThis<any, ExtendableEvent>) => this.activate(toInject, f); if (this.activateEvent.done) this.injectWithName(toInject, f)(); else this.emitter.on('activate', this.injectWithName(toInject, f)); return this; } public activateAsync(toInject: string[], f: InjectableWithTypedThis<any, ExtendableEvent>) public activateAsync(toInject: string[]): (f: InjectableWithTypedThis<any, ExtendableEvent>) => this public activateAsync(toInject: string[], f?: InjectableAsyncWithTypedThis<any, ExtendableEvent>) { if (!f) return (f: InjectableAsyncWithTypedThis<any, ExtendableEvent>) => this.activateAsync(toInject, f); if (this.readyEvent.done) return this.injectWithNameAsync(toInject, f.bind(this.readyEvent) as InjectableAsyncWithTypedThis<any, ExtendableEvent>); else this.emitter.on('activate', (ev: ExtendableEvent) => { this.injectWithNameAsync(toInject, f.bind(ev) as InjectableAsyncWithTypedThis<any, ExtendableEvent>) }); return this; } public activateNew(...toInject: string[]) { return <T>(ctor: new (...args: any[]) => T) => { return this.activate(toInject, ctor.bind(ctor)); } } public activateNewAsync(...toInject: string[]) { return function <T>(ctor: new (...args: any[]) => T) { return this.activateAsync(toInject, ctor.bind(ctor)); } } public readyNew(...toInject: string[]) { return <T>(ctor: new (...args: any[]) => T) => { return this.ready(toInject, ctor.bind(ctor)); } } public readyNewAsync(...toInject: string[]) { return function <T>(ctor: new (...args: any[]) => T) { return this.readyAsync(toInject, ctor.bind(ctor)); } } public start(toInject?: string[], f?: Injectable<any>) { if (arguments.length > 0) Module.o.on('stop', this.injectWithName(toInject, f)); else Module.o.start(this.name); } } var moduleInjector = di.resolve<Injector>('$modules'); if (!moduleInjector) { moduleInjector = new Injector(); di.register('$modules', moduleInjector); }