container.ts
Version:
Modular application framework
238 lines • 9.19 kB
JavaScript
"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