UNPKG

container.ts

Version:
238 lines 9.19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Debug = require("debug"); const awilix_1 = require("awilix"); const Observable_1 = require("rxjs/Observable"); const Subject_1 = require("rxjs/Subject"); const BehaviorSubject_1 = require("rxjs/BehaviorSubject"); require("rxjs/add/observable/of"); require("rxjs/add/observable/throw"); require("rxjs/add/observable/forkJoin"); require("rxjs/add/operator/catch"); require("rxjs/add/operator/filter"); require("rxjs/add/operator/switchMap"); require("rxjs/add/operator/take"); require("rxjs/add/operator/timeout"); const Environment_1 = require("./Environment"); const Log_1 = require("./Log"); const Metric_1 = require("./Metric"); /** Container error class. */ class ContainerError extends Error { constructor(message) { const error = super(message); this.name = error.name = "ContainerError"; this.message = error.message; this.stack = error.stack; } } exports.ContainerError = ContainerError; /** Log message class for stream of module logs. */ class ContainerLogMessage { constructor(level, message, metadata, args) { this.level = level; this.message = message; this.metadata = metadata; this.args = args; } } exports.ContainerLogMessage = ContainerLogMessage; /** Metric message class for stream of module metrics. */ class ContainerMetricMessage { constructor(type, name, value, tags) { this.type = type; this.name = name; this.value = value; this.tags = tags; } } exports.ContainerMetricMessage = ContainerMetricMessage; /** Container reference name used internally by modules. */ exports.CONTAINER_NAME = "_container"; /** Wrapper around awilix library. */ class Container { /** Creates a new container in proxy resolution mode. */ constructor(_name, environment = new Environment_1.Environment()) { this._name = _name; this._modules = new BehaviorSubject_1.BehaviorSubject({}); this._logs = new Subject_1.Subject(); this._metrics = new Subject_1.Subject(); this._environment = environment; this._container = awilix_1.createContainer({ resolutionMode: awilix_1.ResolutionMode.PROXY }); this.registerValue(exports.CONTAINER_NAME, this); } /** Container name, used to namespace modules. */ get name() { return this._name; } /** Container environment reference available to modules. */ get environment() { return this._environment; } /** Array of registered module names. */ get modules() { return Object.keys(this._modules.value); } /** Container logs. */ get logs() { return this._logs; } /** Container metrics. */ get metrics() { return this._metrics; } /** Register a module in container, has singleton lifetime by default. */ registerModule(instance, lifetime = awilix_1.Lifetime.SINGLETON) { const options = {}; options[instance.name] = [this.makeModule.bind(this, instance.name, instance), { lifetime }]; this._container.registerFunction(options); this.reportModuleState(instance.name, false); return this; } /** Register a value in container. */ registerValue(name, value) { this._container.registerValue(name, value); return this; } /** Resolve module in container by name. */ resolve(name) { return this._container.resolve(name); } /** Send log message of level for module. */ sendLog(level, message, metadata, args) { this._logs.next(new ContainerLogMessage(level, message, metadata, args)); } /** Send metric message of type for module. */ sendMetric(type, name, value, tags) { this._metrics.next(new ContainerMetricMessage(type, name, value, tags)); } /** Observable stream of logs filtered by level. */ filterLogs(level) { return this.logs.filter((m) => m.level <= level); } /** Signal modules to enter operational state. */ start(timeout) { return this.setModulesState(true, timeout); } /** Signal modules to leave operational state. */ stop(timeout) { return this.setModulesState(false, timeout); } /** Wait for modules to start before calling next. */ waitStarted(...modules) { return this._modules .filter((states) => { return modules.reduce((previous, current) => { return previous && states[current]; }, true); }) .switchMap(() => Observable_1.Observable.of(undefined)) .take(1); } /** Wait for modules to stop before calling next. */ waitStopped(...modules) { return this._modules .filter((states) => { return modules.reduce((previous, current) => { return previous || states[current]; }, false); }) .switchMap(() => Observable_1.Observable.of(undefined)) .take(1); } /** Factory functions for modules. */ makeModule(name, instance, opts) { return new instance(name, opts); } /** Set modules state by calling start/stop methods. */ setModulesState(state, timeout = 10000) { // Map module methods and report states. const modules = this.modules.map((name) => this._container.resolve(name)); const observables = modules.map((mod) => { const method = mod[state ? "start" : "stop"].bind(mod); return method().switchMap(() => this.reportModuleState(mod.name, state)); }); // Wait for modules to signal state. // Map TimeoutError to ContainerError. return Observable_1.Observable.forkJoin(...observables) .timeout(timeout) .catch((error) => Observable_1.Observable.throw(new ContainerError(error.message))) .switchMap(() => { return Observable_1.Observable.of(undefined); }); } /** Update and report module state via internal subject. */ reportModuleState(name, state) { this._modules.value[name] = state; this._modules.next(this._modules.value); return Observable_1.Observable.of(undefined); } } exports.Container = Container; /** Container module log class. */ class ContainerModuleLog extends Log_1.Log { constructor(_container, _name) { super(); this._container = _container; this._name = _name; } /** * Sends log message to container bus for consumption by modules. * Adds module name to metadata object by default. */ log(level, message, metadata, ...args) { metadata.moduleName = this._name; this._container.sendLog(level, message, metadata, args); } } exports.ContainerModuleLog = ContainerModuleLog; /** Container module metric class. */ class ContainerModuleMetric extends Metric_1.Metric { constructor(_container, _name) { super(); this._container = _container; this._name = _name; } /** * Sends metric message to container bus for consumption by modules. * Adds module name to tags object by default. */ metric(type, name, value, tags) { tags.moduleName = this._name; this._container.sendMetric(type, name, value, tags); } } exports.ContainerModuleMetric = ContainerModuleMetric; /** Base class for container class modules with dependency injection. */ class ContainerModule { constructor(name, opts, depends = {}) { this._identifier = 0; // Set name, resolve container instance and construct log, debug instances. this._name = name; this._container = opts[exports.CONTAINER_NAME]; this._log = new ContainerModuleLog(this._container, this.namespace); this._metric = new ContainerModuleMetric(this._container, this.namespace); this._debug = Debug(this.namespace); // Inject dependency values into instance. // Error is thrown by awilix if resolution failed. Object.keys(depends).map((key) => { const target = depends[key]; this[key] = opts[target]; }); } /** Module container reference. */ get container() { return this._container; } /** Module container environment reference. */ get environment() { return this._container.environment; } /** Module name. */ get name() { return this._name; } /** Module container and module names. */ get namespace() { return `${this.container.name}.${this.name}`; } /** Module log interface. */ get log() { return this._log; } /** Module metric interface. */ get metric() { return this._metric; } /** Module debug interface. */ get debug() { return this._debug; } /** Incrementing counter for unique identifiers. */ get identifier() { return ++this._identifier; } /** Module operational state. */ start() { return Observable_1.Observable.of(undefined); } /** Module non-operational state. */ stop() { return Observable_1.Observable.of(undefined); } } exports.ContainerModule = ContainerModule; //# sourceMappingURL=Container.js.map