container.ts
Version:
Modular application framework
323 lines • 14.4 kB
JavaScript
;
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 __());
};
})();
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
var process = require("process");
var RxJS_1 = require("../../container/RxJS");
var error_1 = require("../error");
var Process_1 = require("./Process");
/** Process message types. */
var EProcessMessageType;
(function (EProcessMessageType) {
EProcessMessageType[EProcessMessageType["Log"] = 0] = "Log";
EProcessMessageType[EProcessMessageType["Metric"] = 1] = "Metric";
EProcessMessageType[EProcessMessageType["CallRequest"] = 2] = "CallRequest";
EProcessMessageType[EProcessMessageType["CallResponse"] = 3] = "CallResponse";
EProcessMessageType[EProcessMessageType["Event"] = 4] = "Event";
EProcessMessageType[EProcessMessageType["Socket"] = 5] = "Socket";
EProcessMessageType[EProcessMessageType["User"] = 6] = "User";
})(EProcessMessageType = exports.EProcessMessageType || (exports.EProcessMessageType = {}));
var ChildProcess = /** @class */ (function (_super) {
__extends(ChildProcess, _super);
function ChildProcess(options) {
var _this = _super.call(this, options) || this;
/** Socket handle received from parent process. */
_this.sockets = {};
/** Messages received from parent process. */
_this.messages$ = new RxJS_1.Subject();
/** Events received from parent process. */
_this.events$ = new RxJS_1.Subject();
_this.currentIdentifier = 0;
// Listen for a socket message to accept handle.
process.once("message", function (type, socket) {
if (type === ChildProcess.EVENT.SOCKET) {
// Configure socket default channel as message receiver.
var channel = ChildProcess.DEFAULT.CHANNEL;
_this.sockets[channel] = ChildProcess.socketConfigure({
socket: socket,
onError: function (error) { return _this.log.error(error); },
onData: function (data) { return _this.messages$.next(data); },
});
}
});
// Process messages.
RxJS_1.Observable.fromEvent(process, "message")
.subscribe(function (message) { return _this.messages$.next(message); });
// Listen for and handle messages from parent process.
_this.messages$
.subscribe(function (message) { return _this.handleMessage(message); });
// Forward log and metric messages to parent process only.
// Do not pass channel into send, defaults to parent.
_this.container.logs$
.subscribe(function (log) { return _this.send(EProcessMessageType.Log, log); });
_this.container.metrics$
.subscribe(function (metric) { return _this.send(EProcessMessageType.Metric, metric); });
// Send status event on interval.
RxJS_1.Observable.interval(ChildProcess.DEFAULT.STATUS_INTERVAL)
.subscribe(function () { return _this.event(ChildProcess.EVENT.STATUS, { data: _this.status }); });
return _this;
}
/** Configure socket for interprocess communication. */
ChildProcess.socketConfigure = function (options) {
// Set encoding to receive serialised string data.
options.socket.setEncoding(ChildProcess.DEFAULT.ENCODING);
// Socket observable events.
// TODO(LOW): Fix fromEvent emitter types.
var close$ = RxJS_1.Observable.fromEvent(options.socket, "close");
var data$ = RxJS_1.Observable.fromEvent(options.socket, "data");
// Subscribe to socket events until closed.
data$.takeUntil(close$)
.subscribe(function (data) {
ChildProcess.socketDeserialise(data)
.map(function (message) { return options.onData(message); });
});
return options.socket;
};
/** Serialise input data for socket. */
ChildProcess.socketSerialise = function (data) {
try {
return JSON.stringify(data) + "\n";
}
catch (error) {
throw new Process_1.ProcessError(error);
}
};
/** Deserialise input data from socket. */
ChildProcess.socketDeserialise = function (data) {
try {
var packets = data.split(/\n/);
var messages = [];
if (packets.length > 1) {
for (var i = 0; i < (packets.length - 1); i++) {
messages.push(JSON.parse(packets[i]));
}
}
return messages;
}
catch (error) {
throw new Process_1.ProcessError(error);
}
};
/** Extract serialisable error properties to object. */
ChildProcess.serialiseError = function (error) {
return new Process_1.ProcessError(error).serialise();
};
/** Convert serialised error to error instance. */
ChildProcess.deserialiseError = function (error) {
return error_1.ErrorChain.deserialise(error) || new Process_1.ProcessError();
};
/** Send call request to process. */
ChildProcess.sendCallRequest = function (emitter, mod, target, method, id, options) {
if (options === void 0) { options = {}; }
var timeout = options.timeout || ChildProcess.DEFAULT.TIMEOUT;
var args = options.args || [];
// Send call request to process.
var sendData = { id: id, target: target, method: method, args: args };
emitter.send(EProcessMessageType.CallRequest, sendData, options.channel);
return ChildProcess.handleCallResponse(emitter.messages$, id, args, timeout);
};
/** Handle method call requests. */
ChildProcess.handleCallRequest = function (emitter, mod, data, channel) {
var type = EProcessMessageType.CallResponse;
var responseData = { id: data.id };
try {
// Retrieve target module and make subscribe call to method.
var targetMod = mod.container.resolve(data.target);
var method = targetMod[data.method].bind(targetMod);
method.apply(void 0, data.args).subscribe({
next: function (next) {
var nextData = Object.assign({ next: next }, responseData);
emitter.send(type, nextData, channel);
},
error: function (error) {
error = ChildProcess.serialiseError(error);
var errorData = Object.assign({ error: error }, responseData);
emitter.send(type, errorData, channel);
},
complete: function () {
var completeData = Object.assign({ complete: true }, responseData);
emitter.send(type, completeData, channel);
},
});
}
catch (error) {
error = ChildProcess.serialiseError(error);
var errorData = Object.assign({ error: error }, responseData);
emitter.send(type, errorData, channel);
}
};
/** Handle method call responses. */
ChildProcess.handleCallResponse = function (messages$, id, args, timeout) {
return messages$
.filter(function (message) {
// Filter by message type and identifier.
if (message.type === EProcessMessageType.CallResponse) {
var data = message.data;
return data.id === id;
}
return false;
})
.map(function (message) {
// Cast message data to type.
var data = message.data;
return data;
})
.timeout(timeout)
.takeWhile(function (data) {
// Complete observable when complete message received.
return !data.complete;
})
.mergeMap(function (data) {
// Throw error or emit next data.
if (data.error != null) {
var error = ChildProcess.deserialiseError(data.error);
return RxJS_1.Observable.throw(error);
}
else {
return RxJS_1.Observable.of(data.next);
}
});
};
/** Send event to process. */
ChildProcess.sendEvent = function (emitter, mod, name, options) {
if (options === void 0) { options = {}; }
// Send event request to child process.
var sendData = __assign({ name: name }, options);
emitter.send(EProcessMessageType.Event, sendData, options.channel);
};
/** Listen for events from process. */
ChildProcess.listenForEvent = function (events$, name, channel) {
return events$
.filter(function (event) {
return (name === event.name) && (channel === event.channel);
})
.map(function (event) { return event.data; });
};
/** Send message to channel process. */
ChildProcess.prototype.send = function (type, data, channel) {
var socket = this.sockets[channel || ChildProcess.DEFAULT.CHANNEL];
if (socket != null) {
socket.write(ChildProcess.socketSerialise({ type: type, data: data }));
}
else if (process.send != null) {
process.send({ type: type, data: data });
}
};
/** Make call to module.method in parent process. */
ChildProcess.prototype.call = function (target, method, options) {
if (options === void 0) { options = {}; }
return ChildProcess.sendCallRequest(this, this, target, method, this.nextIdentifier, options);
};
/** Send event with optional data to parent process. */
ChildProcess.prototype.event = function (name, options) {
if (options === void 0) { options = {}; }
ChildProcess.sendEvent(this, this, name, options);
};
/** Listen for event sent by parent process. */
ChildProcess.prototype.listen = function (name, channel) {
return ChildProcess.listenForEvent(this.events$, name, channel);
};
Object.defineProperty(ChildProcess.prototype, "nextIdentifier", {
/** Incrementing counter for unique identifiers. */
get: function () { return ++this.currentIdentifier; },
enumerable: true,
configurable: true
});
/** Handle messages received from parent process. */
ChildProcess.prototype.handleMessage = function (message) {
var _this = this;
switch (message.type) {
// Call request received from parent.
case EProcessMessageType.CallRequest: {
ChildProcess.handleCallRequest(this, this, message.data, message.channel);
break;
}
// Send event on internal event bus.
case EProcessMessageType.Event: {
var event_1 = message.data;
this.events$.next(event_1);
break;
}
// Socket event received from parent.
case EProcessMessageType.Socket: {
var channel_1 = message.data;
var listener_1 = function (type, socket) {
if (type === ChildProcess.EVENT.SOCKET) {
var socketChannel = _this.sockets[channel_1];
// End socket channel if it exists.
if (socketChannel != null) {
socketChannel.end();
_this.sockets[channel_1] = undefined;
}
// Configure socket as channel.
socketChannel = ChildProcess.socketConfigure({
socket: socket,
onError: function (error) { return _this.log.error(error); },
onData: function (data) {
data.channel = channel_1;
_this.messages$.next(data);
},
});
// Remove process listener, send event to parent.
_this.sockets[channel_1] = socketChannel;
process.removeListener("message", listener_1);
_this.event(ChildProcess.EVENT.CHANNEL, { data: message.data });
}
};
// Listen for a socket message to accept handle.
process.on("message", listener_1);
break;
}
}
};
/** Override process down handler to close socket if present. */
ChildProcess.prototype.onSignal = function (signal) {
var _this = this;
Object.keys(this.sockets).map(function (channel) {
var socket = _this.sockets[channel];
if (socket != null) {
socket.end();
}
_this.sockets[channel] = undefined;
});
return _super.prototype.onSignal.call(this, signal);
};
/** Default module name. */
ChildProcess.moduleName = "ChildProcess";
/** Default values. */
ChildProcess.DEFAULT = {
/** Default call method timeout (10s). */
TIMEOUT: 10000,
/** Default interval to send status events (1m). */
STATUS_INTERVAL: 60000,
/** Default socket channel. */
CHANNEL: "_",
/** Socket data encoding. */
ENCODING: "utf8",
};
/** Class event names. */
ChildProcess.EVENT = {
SOCKET: "socket",
CHANNEL: "channel",
STATUS: "status",
};
return ChildProcess;
}(Process_1.Process));
exports.ChildProcess = ChildProcess;
//# sourceMappingURL=ChildProcess.js.map