container.ts
Version:
Modular application framework
265 lines • 11 kB
JavaScript
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
var awilix_1 = require("awilix");
var error_1 = require("../lib/error");
var Environment_1 = require("./Environment");
var Log_1 = require("./Log");
var RxJS_1 = require("./RxJS");
/** Container error class. */
var ContainerError = /** @class */ (function (_super) {
__extends(ContainerError, _super);
function ContainerError(name, cause) {
return _super.call(this, { name: name }, cause) || this;
}
return ContainerError;
}(error_1.ErrorChain));
exports.ContainerError = ContainerError;
/** Log message class for stream of module logs. */
var ContainerLogMessage = /** @class */ (function () {
function ContainerLogMessage(level, message, metadata, args) {
this.level = level;
this.message = message;
this.metadata = metadata;
this.args = args;
}
return ContainerLogMessage;
}());
exports.ContainerLogMessage = ContainerLogMessage;
/** Metric message class for stream of module metrics. */
var ContainerMetricMessage = /** @class */ (function () {
function ContainerMetricMessage(type, name, value, tags) {
this.type = type;
this.name = name;
this.value = value;
this.tags = tags;
}
return ContainerMetricMessage;
}());
exports.ContainerMetricMessage = ContainerMetricMessage;
/** Wrapper around awilix library. */
var Container = /** @class */ (function () {
/** Creates a new container in proxy resolution mode. */
function Container(
/** Container name, used to namespace modules. */
name,
/** Container environment. */
environment,
/** Optional command line arguments. */
argv) {
if (environment === void 0) { environment = new Environment_1.Environment(); }
if (argv === void 0) { argv = { _: [], $0: "" }; }
this.name = name;
this.environment = environment;
this.argv = argv;
/** Observable module state. */
this.modules$ = new RxJS_1.BehaviorSubject({});
/** Container module logs. */
this.logs$ = new RxJS_1.Subject();
/** Container module metrics. */
this.metrics$ = new RxJS_1.Subject();
this.environment = environment;
this.container = awilix_1.createContainer({ resolutionMode: awilix_1.ResolutionMode.PROXY });
this.registerValue(Container.REFERENCE, this);
}
Object.defineProperty(Container.prototype, "moduleNames", {
/** Array of registered module names. */
get: function () {
return Object.keys(this.modules$.value);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Container.prototype, "modules", {
/** Array of registered modules. */
get: function () {
var _this = this;
return this.moduleNames.map(function (n) { return _this.container.resolve(n); });
},
enumerable: true,
configurable: true
});
/** Create scope from container. */
Container.prototype.createScope = function () {
return this.container.createScope();
};
/**
* Register a named module in container.
* Throws an error if module of name is already registered.
*/
Container.prototype.registerModule = function (instance) {
if (this.moduleRegistered(instance.moduleName)) {
throw new ContainerError(Container.ERROR.MODULE_REGISTERED);
}
// TODO(MEDIUM): Create separate scope for modules.
var factoryFunction = this.moduleFactory.bind(this, instance);
this.container.register((_a = {}, _a[instance.moduleName] = awilix_1.asFunction(factoryFunction).singleton(), _a));
this.moduleState(instance.moduleName, false);
return this;
var _a;
};
/** Register named modules in container. */
Container.prototype.registerModules = function (modules) {
var _this = this;
modules.map(function (mod) { return _this.registerModule(mod); });
return this;
};
/** Register a value of type in container. */
Container.prototype.registerValue = function (name, value) {
this.container.register((_a = {}, _a[name] = awilix_1.asValue(value), _a));
return this;
var _a;
};
/** Resolve value or module of type from container by name. */
Container.prototype.resolve = function (name) {
return this.container.resolve(name);
};
/** Send log message of level for module. */
Container.prototype.sendLog = function (level, message, metadata, args) {
this.logs$.next(new ContainerLogMessage(level, message, metadata, args));
};
/** Send metric message of type for module. */
Container.prototype.sendMetric = function (type, name, value, tags) {
this.metrics$.next(new ContainerMetricMessage(type, name, value, tags));
};
/** Observable stream of module logs filtered by level. */
Container.prototype.filterLogs = function (level) {
return this.logs$.filter(function (m) { return m.level <= level; });
};
/** Observable stream of module metrics filtered by type. */
Container.prototype.filterMetrics = function (type) {
return this.metrics$.filter(function (m) { return m.type === type; });
};
/** Signal modules to enter operational state. */
Container.prototype.up = function (timeout) {
var _this = this;
var observables$ = this.modules
.map(function (mod) {
return _this.waitUp.apply(_this, _this.moduleDependencies(mod)).switchMap(function () {
var up$ = mod.moduleUp();
if (up$ == null) {
// Module up returned void, set state now.
return _this.moduleState(mod.moduleName, true);
}
// Observable returned, update state on next.
return up$
.switchMap(function () { return _this.moduleState(mod.moduleName, true); });
});
});
return this.containerState(observables$, true, timeout);
};
/** Signal modules to leave operational state. */
Container.prototype.down = function (timeout) {
var _this = this;
var observables$ = this.modules
.map(function (mod) {
return _this.waitDown.apply(_this, _this.moduleDependants(mod)).switchMap(function () {
var down$ = mod.moduleDown();
if (down$ == null) {
// Module down returned void, set state now.
return _this.moduleState(mod.moduleName, false);
}
// Observable returned, update state on next.
return down$
.switchMap(function () { return _this.moduleState(mod.moduleName, false); });
});
});
return this.containerState(observables$, false, timeout);
};
/** Wait for modules to enter operational state before calling next. */
Container.prototype.waitUp = function () {
var modules = [];
for (var _i = 0; _i < arguments.length; _i++) {
modules[_i] = arguments[_i];
}
return this.modules$
.filter(function (states) {
return modules.reduce(function (previous, current) {
return previous && states[current];
}, true);
})
.map(function () { return undefined; })
.take(1);
};
/** Wait for modules to leave operational state before calling next. */
Container.prototype.waitDown = function () {
var modules = [];
for (var _i = 0; _i < arguments.length; _i++) {
modules[_i] = arguments[_i];
}
return this.modules$
.filter(function (states) {
return !modules.reduce(function (previous, current) {
return previous || states[current];
}, false);
})
.map(function () { return undefined; })
.take(1);
};
Container.prototype.moduleFactory = function (instance, opts) {
return new instance({ moduleName: instance.moduleName, opts: opts });
};
Container.prototype.moduleDependencies = function (mod) {
var dependencies = mod.moduleDependencies();
return Object.keys(dependencies).map(function (k) { return dependencies[k].moduleName; });
};
Container.prototype.moduleDependants = function (mod) {
var dependants = [];
this.modules.map(function (m) {
var dependencies = m.moduleDependencies();
var dependant = Object.keys(dependencies).reduce(function (previous, key) {
return previous || (dependencies[key].moduleName === mod.moduleName);
}, false);
if (dependant) {
dependants.push(m.moduleName);
}
});
return dependants;
};
Container.prototype.moduleRegistered = function (name) {
return (this.modules$.value[name] != null);
};
Container.prototype.moduleState = function (name, state) {
this.modules$.value[name] = state;
this.modules$.next(this.modules$.value);
return RxJS_1.Observable.of(undefined);
};
Container.prototype.containerState = function (observables$, state, timeout) {
var _this = this;
if (timeout === void 0) { timeout = 10000; }
return RxJS_1.Observable.forkJoin.apply(RxJS_1.Observable, observables$).timeout(timeout)
.catch(function (error) {
var name = state ? Container.ERROR.UP : Container.ERROR.DOWN;
return RxJS_1.Observable.throw(new ContainerError(name, error));
})
.map(function () {
var message = state ? Container.LOG.UP : Container.LOG.DOWN;
_this.sendLog(Log_1.ELogLevel.Informational, message, { name: _this.name }, []);
});
};
/** Container reference name used internally by modules. */
Container.REFERENCE = "_container";
/** Error names. */
Container.ERROR = {
UP: "ContainerUpError",
DOWN: "ContainerDownError",
MODULE_REGISTERED: "ContainerModuleRegisteredError",
};
/** Log names. */
Container.LOG = {
UP: "ContainerUp",
DOWN: "ContainerDown",
};
return Container;
}());
exports.Container = Container;
//# sourceMappingURL=Container.js.map