tgrid
Version:
Grid Computing Framework for TypeScript
609 lines • 27.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Communicator = void 0;
var tstl_1 = require("tstl");
var Driver_1 = require("../typings/Driver");
var serializeError_1 = require("../utils/internal/serializeError");
/**
* The basic communicator.
*
* The `Communicator` is an abstract class taking full charge of network communication.
* Protocolized communicators like {@link WebSocketConnector} are realized by extending this
* `Communicator` class.
*
* You want to make your own communicator using special protocol, extends this `Communicator`
* class. After the extending, implement your special communicator by overriding those methods.
*
* - {@link inspectReady}
* - {@link replyData}
* - {@link sendData}
*
* @template Provider Type of features provided for remote system.
* @template Remote Type of features supported by remote system, used for {@link getDriver} function.
* @author Jeongho Nam - https://github.com/samchon
*/
var Communicator = /** @class */ (function () {
/* ----------------------------------------------------------------
CONSTRUCTORS
---------------------------------------------------------------- */
/**
* Initializer Constructor.
*
* @param provider An object providing features for remote system.
*/
function Communicator(provider) {
var _this = this;
// PROVIDER & DRIVER
this.provider_ = provider;
this.driver_ = new Proxy(new Driver_1.Driver(), {
get: function (_a, name) {
if (name === "then")
return null;
else
return _this._Proxy_func(name);
},
});
// OTHER MEMBERS
this.promises_ = new tstl_1.HashMap();
this.join_cv_ = new tstl_1.ConditionVariable();
this.event_listeners_ = new tstl_1.HashMap();
}
/**
* Add invoke event listener.
*
* Add an event listener for the invoke event. The event listener would be called
* when some invoke event has been occured; sending, receiving, completing, or returning.
*
* If you change the requesting parameters or returning value in the event listener,
* it would affect to the RPC (Remote Procedure Call) communication. Therefore, you have
* to be careful when modifying the remote function calling.
*
* Of course, you can utilize the event listener just for monitoring the RPC events.
*
* @param type Type of the event
* @param listener The listener function to enroll
*/
Communicator.prototype.on = function (type, listener) {
this.event_listeners_
.take(type, function () { return new tstl_1.HashSet(); })
.insert(listener);
};
/**
* Erase invoke event listener.
*
* Erase an event listener from the invoke event. The event listener would not be
* called anymore when the specific invoke event has been occured.
*
* @param type Type of the event
* @param listener The listener function to erase
*/
Communicator.prototype.off = function (type, listener) {
var it = this.event_listeners_.find(type);
if (it.equals(this.event_listeners_.end()) === false)
it.second.erase(listener);
if (it.second.empty())
this.event_listeners_.erase(it);
};
/**
* Destroy the communicator.
*
* A destroy function must be called when the network communication has been closed.
* It would destroy all function calls in the remote system (by `Driver<Controller>`),
* which are not returned yet.
*
* The *error* instance would be thrown to those function calls. If the disconnection is
* abnormal, then write the detailed reason why into the *error* instance.
*
* @param error An error instance to be thrown to the unreturned functions.
*/
Communicator.prototype.destructor = function (error) {
return __awaiter(this, void 0, void 0, function () {
var rejectError, _a, _b, entry, reject;
var e_1, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
rejectError = error
? error
: new Error("Connection has been closed.");
try {
for (_a = __values(this.promises_), _b = _a.next(); !_b.done; _b = _a.next()) {
entry = _b.value;
reject = entry.second.reject;
reject(rejectError);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
}
finally { if (e_1) throw e_1.error; }
}
// CLEAR PROMISES
this.promises_.clear();
// RESOLVE JOINERS
return [4 /*yield*/, this.join_cv_.notify_all()];
case 1:
// RESOLVE JOINERS
_d.sent();
return [2 /*return*/];
}
});
});
};
/**
* @hidden
*/
Communicator.prototype._Proxy_func = function (name) {
var _this = this;
var func = function () {
var params = [];
for (var _i = 0; _i < arguments.length; _i++) {
params[_i] = arguments[_i];
}
return _this._Call_function.apply(_this, __spreadArray([name], __read(params), false));
};
return new Proxy(func, {
get: function (_a, newName) {
if (newName === "bind")
return function (thisArg) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
return func.bind.apply(func, __spreadArray([thisArg], __read(args), false));
};
else if (newName === "call")
return function (thisArg) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
return func.call.apply(func, __spreadArray([thisArg], __read(args), false));
};
else if (newName === "apply")
return function (thisArg, args) { return func.apply(thisArg, args); };
return _this._Proxy_func("".concat(name, ".").concat(newName));
},
});
};
/**
* @hidden
*/
Communicator.prototype._Call_function = function (name) {
var _this = this;
var params = [];
for (var _i = 1; _i < arguments.length; _i++) {
params[_i - 1] = arguments[_i];
}
return new Promise(function (resolve, reject) { return __awaiter(_this, void 0, void 0, function () {
var error, invoke, eventSetIterator, event_1, _a, _b, listener;
var e_2, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
error = this.inspectReady("Communicator._Call_fuction");
if (error) {
reject(error);
return [2 /*return*/];
}
invoke = {
uid: ++Communicator.SEQUENCE,
listener: name,
parameters: params.map(function (p) { return ({
type: typeof p,
value: p,
}); }),
};
eventSetIterator = this.event_listeners_.find("send");
if (eventSetIterator.equals(this.event_listeners_.end()) === false) {
event_1 = {
type: "send",
time: new Date(),
function: invoke,
};
try {
for (_a = __values(eventSetIterator.second), _b = _a.next(); !_b.done; _b = _a.next()) {
listener = _b.value;
try {
listener(event_1);
}
catch (_e) { }
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
}
finally { if (e_2) throw e_2.error; }
}
}
// DO SEND WITH PROMISE
this.promises_.emplace(invoke.uid, {
function: invoke,
time: new Date(),
resolve: resolve,
reject: reject,
});
return [4 /*yield*/, this.sendData(invoke)];
case 1:
_d.sent();
return [2 /*return*/];
}
});
}); });
};
/* ----------------------------------------------------------------
ACCESSORS
---------------------------------------------------------------- */
/**
* Set `Provider`
*
* @param obj An object would be provided for remote system.
*/
Communicator.prototype.setProvider = function (obj) {
this.provider_ = obj;
};
/**
* Get current `Provider`.
*
* Get an object providing features (functions & objects) for remote system. The remote
* system would call the features (`Provider`) by using its `Driver<Controller>`.
*
* @return Current `Provider` object
*/
Communicator.prototype.getProvider = function () {
return this.provider_;
};
/**
* Get Driver for RFC (Remote Function Call).
*
* The `Controller` is an interface who defines provided functions from the remote
* system. The `Driver` is an object who makes to call remote functions, defined in
* the `Controller` and provided by `Provider` in the remote system, possible.
*
* In other words, calling a functions in the `Driver<Controller>`, it means to call
* a matched function in the remote system's `Provider` object.
*
* - `Controller`: Definition only
* - `Driver`: Remote Function Call
*
* @template Controller An interface for provided features (functions & objects) from the remote system (`Provider`).
* @template UseParametric Whether to convert type of function parameters to be compatible with their primitive.
* @return A Driver for the RFC.
*/
Communicator.prototype.getDriver = function () {
return this.driver_;
};
Communicator.prototype.join = function (param) {
return __awaiter(this, void 0, void 0, function () {
var error;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
error = this.inspectReady("".concat(this.constructor.name, ".join"));
if (error)
throw error;
if (!(param === undefined)) return [3 /*break*/, 2];
return [4 /*yield*/, this.join_cv_.wait()];
case 1:
_a.sent();
return [3 /*break*/, 6];
case 2:
if (!(param instanceof Date)) return [3 /*break*/, 4];
return [4 /*yield*/, this.join_cv_.wait_until(param)];
case 3: return [2 /*return*/, _a.sent()];
case 4: return [4 /*yield*/, this.join_cv_.wait_for(param)];
case 5: return [2 /*return*/, _a.sent()];
case 6: return [2 /*return*/];
}
});
});
};
/* ================================================================
COMMUNICATORS
- REPLIER
- SENDER
===================================================================
REPLIER
---------------------------------------------------------------- */
/**
* Data Reply Function.
*
* A function should be called when data has come from the remote system.
*
* When you receive a message from the remote system, then parse the message with your
* special protocol and covert it to be an *Invoke* object. After the conversion, call
* this method.
*
* @param invoke Structured data converted by your special protocol.
*/
Communicator.prototype.replyData = function (invoke) {
if (invoke.listener)
this._Handle_function(invoke).catch(function () { });
else
this._Handle_complete(invoke);
};
/**
* @hidden
*/
Communicator.prototype._Handle_function = function (invoke) {
return __awaiter(this, void 0, void 0, function () {
var uid, time, func, thisArg, routes, routes_1, routes_1_1, name_1, eventSetIterator, event_2, _a, _b, closure, parameters, result, exp_1;
var e_3, _c, e_4, _d;
return __generator(this, function (_e) {
switch (_e.label) {
case 0:
uid = invoke.uid;
time = new Date();
_e.label = 1;
case 1:
_e.trys.push([1, 4, , 6]);
//----
// FIND FUNCTION
//----
if (this.provider_ === undefined)
// PROVIDER MUST BE
throw new Error("Error on Communicator._Handle_function(): the provider is not specified yet.");
else if (this.provider_ === null)
throw new Error("Error on Communicator._Handle_function(): the provider would not be.");
func = this.provider_;
thisArg = undefined;
routes = invoke.listener.split(".");
try {
for (routes_1 = __values(routes), routes_1_1 = routes_1.next(); !routes_1_1.done; routes_1_1 = routes_1.next()) {
name_1 = routes_1_1.value;
thisArg = func;
func = thisArg[name_1];
// SECURITY-ERRORS
if (name_1[0] === "_")
throw new Error("Error on Communicator._Handle_function(): RFC does not allow access to a member starting with the underscore: Provider.".concat(invoke.listener, "()"));
else if (name_1[name_1.length - 1] === "_")
throw new Error("Error on Communicator._Handle_function(): RFC does not allow access to a member ending with the underscore: Provider.".concat(invoke.listener, "()."));
else if (name_1 === "toString" && func === Function.toString)
throw new Error("Error on Communicator._Handle_function(): RFC on Function.toString() is not allowed: Provider.".concat(invoke.listener, "()."));
else if (name_1 === "constructor" || name_1 === "prototype")
throw new Error("Error on Communicator._Handle_function(): RFC does not allow access to ".concat(name_1, ": Provider.").concat(invoke.listener, "()."));
}
}
catch (e_3_1) { e_3 = { error: e_3_1 }; }
finally {
try {
if (routes_1_1 && !routes_1_1.done && (_c = routes_1.return)) _c.call(routes_1);
}
finally { if (e_3) throw e_3.error; }
}
func = func.bind(thisArg);
eventSetIterator = this.event_listeners_.find("receive");
if (eventSetIterator.equals(this.event_listeners_.end()) === false) {
event_2 = {
type: "receive",
time: time,
function: invoke,
};
try {
for (_a = __values(eventSetIterator.second), _b = _a.next(); !_b.done; _b = _a.next()) {
closure = _b.value;
try {
closure(event_2);
}
catch (_f) { }
}
}
catch (e_4_1) { e_4 = { error: e_4_1 }; }
finally {
try {
if (_b && !_b.done && (_d = _a.return)) _d.call(_a);
}
finally { if (e_4) throw e_4.error; }
}
}
parameters = invoke.parameters.map(function (p) { return p.value; });
return [4 /*yield*/, func.apply(void 0, __spreadArray([], __read(parameters), false))];
case 2:
result = _e.sent();
return [4 /*yield*/, this._Send_return({
invoke: invoke,
time: time,
return: {
uid: uid,
success: true,
value: result,
},
})];
case 3:
_e.sent();
return [3 /*break*/, 6];
case 4:
exp_1 = _e.sent();
return [4 /*yield*/, this._Send_return({
invoke: invoke,
time: time,
return: {
uid: uid,
success: false,
value: exp_1,
},
})];
case 5:
_e.sent();
return [3 /*break*/, 6];
case 6: return [2 /*return*/];
}
});
});
};
/**
* @hidden
*/
Communicator.prototype._Handle_complete = function (invoke) {
var e_5, _a;
// FIND TARGET FUNCTION CALL
var it = this.promises_.find(invoke.uid);
if (it.equals(this.promises_.end()))
return;
// CALL EVENT LISTENERS
var eventSetIterator = this.event_listeners_.find("complete");
if (eventSetIterator.equals(this.event_listeners_.end()) === false) {
var event_3 = {
type: "complete",
function: it.second.function,
return: invoke,
requested_at: it.second.time,
completed_at: new Date(),
};
try {
for (var _b = __values(eventSetIterator.second), _c = _b.next(); !_c.done; _c = _b.next()) {
var closure = _c.value;
try {
closure(event_3);
}
catch (_d) { }
}
}
catch (e_5_1) { e_5 = { error: e_5_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_5) throw e_5.error; }
}
}
// RETURNS
var func = invoke.success
? it.second.resolve
: it.second.reject;
this.promises_.erase(it);
func(invoke.value);
};
/**
* @hidden
*/
Communicator.prototype._Send_return = function (props) {
return __awaiter(this, void 0, void 0, function () {
var eventSet, event_4, _a, _b, closure;
var e_6, _c;
return __generator(this, function (_d) {
switch (_d.label) {
case 0:
eventSet = this.event_listeners_.find("return");
if (eventSet.equals(this.event_listeners_.end()) === false) {
event_4 = {
type: "return",
function: props.invoke,
return: props.return,
requested_at: props.time,
completed_at: new Date(),
};
try {
for (_a = __values(eventSet.second), _b = _a.next(); !_b.done; _b = _a.next()) {
closure = _b.value;
try {
closure(event_4);
}
catch (_e) { }
}
}
catch (e_6_1) { e_6 = { error: e_6_1 }; }
finally {
try {
if (_b && !_b.done && (_c = _a.return)) _c.call(_a);
}
finally { if (e_6) throw e_6.error; }
}
}
// SPECIAL LOGIC FOR ERROR -> FOR CLEAR JSON ENCODING
if (props.return.success === false && props.return.value instanceof Error)
props.return.value = (0, serializeError_1.serializeError)(props.return.value);
// RETURNS
return [4 /*yield*/, this.sendData(props.return)];
case 1:
// RETURNS
_d.sent();
return [2 /*return*/];
}
});
});
};
/**
* @hidden
*/
Communicator.SEQUENCE = 0;
return Communicator;
}());
exports.Communicator = Communicator;
//# sourceMappingURL=Communicator.js.map