container.ts
Version:
Modular application framework
125 lines • 5.58 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
/// <reference types="node" />
const assert = require("assert");
const path = require("path");
const childProcess = require("child_process");
const Observable_1 = require("rxjs/Observable");
require("rxjs/add/observable/of");
require("rxjs/add/observable/fromEvent");
require("rxjs/add/operator/switchMap");
require("rxjs/add/operator/take");
require("rxjs/add/operator/takeUntil");
const container_1 = require("../../container");
const ChildProcess_1 = require("../process/ChildProcess");
// TODO: Validation library.
exports.ENV_SCRIPTS_PATH = "SCRIPTS_PATH";
exports.ENV_SCRIPTS_NAME = "SCRIPTS_NAME";
/** Spawned script process interface. */
class ScriptProcess {
constructor(_scripts, _target, _id, _process, _options = {}) {
this._scripts = _scripts;
this._target = _target;
this._id = _id;
this._process = _process;
this._options = _options;
this._identifier = 0;
this.scripts.debug(`fork '${_target}.${_id}'`);
// Accumulate multiple callback arguments into array.
const accumulator = (...args) => args;
// Listen for process exit, reduce code/signal for next argument.
this._exit = Observable_1.Observable.fromEvent(_process, "exit", accumulator)
.take(1)
.switchMap((args) => {
const [code, signal] = args;
const value = (typeof code === "number") ? code : signal;
return Observable_1.Observable.of(value || 1);
});
this._exit.subscribe((code) => this.scripts.debug(`exit '${_target}.${_id}' '${code}'`));
// Listen for process error, forward to scripts logger.
Observable_1.Observable.fromEvent(_process, "error")
.takeUntil(this._exit)
.subscribe((error) => this.scripts.log.error(error));
// Listen for and handle process messages.
this._message = Observable_1.Observable.fromEvent(_process, "message")
.takeUntil(this._exit);
this._message
.subscribe((message) => this.handleMessage(message));
}
get scripts() { return this._scripts; }
get target() { return this._target; }
get id() { return this._id; }
get process() { return this._process; }
get options() { return this._options; }
get exit() { return this._exit; }
get message() { return this._message; }
/** Incrementing counter for unique identifiers. */
get identifier() { return ++this._identifier; }
/** Send message to child process. */
send(type, data) {
this.process.send({ type, data });
}
/** Make call to module.method in child process. */
call(target, method, options = {}) {
const timeout = options.timeout || ChildProcess_1.ChildProcess.DEFAULT_TIMEOUT;
const args = options.args || [];
const id = this.identifier;
this.scripts.debug(`call '${this.target}.${this.id}.${target}.${method}' '${id}'`);
// Send call request to child process.
const sendData = { id, target, method, args };
this.send(ChildProcess_1.EProcessMessageType.CallRequest, sendData);
return ChildProcess_1.ChildProcess.handleCallResponse(this.message, id, args, timeout);
}
/** Handle messages received from child process. */
handleMessage(message) {
switch (message.type) {
// Send received log and metric messages to container.
case ChildProcess_1.EProcessMessageType.Log: {
const data = message.data;
this.scripts.container.sendLog(data.level, data.message, data.metadata, data.args);
break;
}
case ChildProcess_1.EProcessMessageType.Metric: {
const data = message.data;
this.scripts.container.sendMetric(data.type, data.name, data.value, data.tags);
break;
}
// Call request received from child.
case ChildProcess_1.EProcessMessageType.CallRequest: {
ChildProcess_1.ChildProcess.handleCallRequest(this, this.scripts.container, message.data);
break;
}
}
}
}
exports.ScriptProcess = ScriptProcess;
/** Node.js scripts interface. */
class Scripts extends container_1.ContainerModule {
get path() { return this._path; }
constructor(name, opts) {
super(name, opts);
// Get scripts directory path from environment.
const scriptsPath = this.environment.get(exports.ENV_SCRIPTS_PATH);
assert(scriptsPath != null, "Scripts path is undefined");
this._path = path.resolve(scriptsPath);
this.debug(`path '${this.path}'`);
}
/** Spawn new Node.js process using script file. */
fork(target, options = {}) {
const filePath = path.resolve(this.path, target);
const forkArgs = options.args || [];
const forkEnv = this.environment.copy();
const identifier = this.identifier;
// Use container environment when spawning processes.
// Override name value to prepend application namespace.
const name = `${this.namespace}.${target}.${identifier}`;
forkEnv.set(exports.ENV_SCRIPTS_NAME, name);
const forkOptions = {
env: forkEnv.variables,
};
const process = childProcess.fork(filePath, forkArgs, forkOptions);
return new ScriptProcess(this, target, identifier, process, options);
}
}
exports.Scripts = Scripts;
//# sourceMappingURL=Scripts.js.map