slavery-js
Version:
A simple clustering app that allows you to scale an application on multiple thread, containers or machines
396 lines • 15.7 kB
JavaScript
import {
__publicField
} from "../chunk-V6TY7KAL.js";
import Network from "../network/index.js";
import { ServiceClient } from "../service/index.js";
import { await_interval, execAsyncCode, log } from "../utils/index.js";
import { serializeError, deserializeError } from "serialize-error";
class Node {
// takes and empty parameter or a object with the propertie methods
constructor(params) {
__publicField(this, "mode");
__publicField(this, "id");
__publicField(this, "status", "idle");
__publicField(this, "listeners", []);
__publicField(this, "lastUpdateAt", Date.now());
__publicField(this, "master_host");
__publicField(this, "master_port");
__publicField(this, "network");
__publicField(this, "servicesConnected", false);
__publicField(this, "hasStartupFinished", false);
// fields when the class is client handler on a service
__publicField(this, "statusChangeCallback", null);
// stash changes functions
__publicField(this, "stashSetFunction", null);
__publicField(this, "stashGetFunction", null);
// fields when the class is a service handler on a node
__publicField(this, "services", []);
__publicField(this, "doneMethods", {});
__publicField(this, "methods", {});
// options
__publicField(this, "options", {
timeout: 1e4
});
/* this function will work on any mode the class is on */
__publicField(this, "getId", () => this.id);
__publicField(this, "getStatus", () => this.status);
__publicField(this, "lastHeardOfIn", () => Date.now() - this.lastUpdateAt);
__publicField(this, "isIdle", () => this.status === "idle");
__publicField(this, "isWorking", () => this.status === "working");
__publicField(this, "isError", () => this.status === "error");
__publicField(this, "updateLastHeardOf", () => this.lastUpdateAt = Date.now());
__publicField(this, "updateStatus", (status) => this.status = status);
__publicField(this, "untilFinish", async () => {
await await_interval({
condition: () => this.isIdle(),
interval: 100
}).catch(() => {
throw new Error("The node is not idle");
});
return true;
});
__publicField(this, "start", async () => {
if (this.mode === "client") return await this.start_client();
else if (this.mode === "server") return await this.start_server();
});
__publicField(this, "run", async (method, parameter) => {
if (this.mode === "client") return await this.run_client({ method, parameter });
else if (this.mode === "server") return await this.run_server({ method, parameter });
});
__publicField(this, "exec", async (method, code) => {
if (this.mode === "client") return await this.exec_client(code);
else if (this.mode === "server") return await this.exec_server(code);
});
__publicField(this, "setServices", async (services) => {
if (this.mode === "client") return await this.setServices_client(services);
else if (this.mode === "server") return await this.setServices_server(services);
});
__publicField(this, "exit", async () => {
if (this.mode === "client") return await this.exit_client();
else if (this.mode === "server") return await this.exit_server();
});
__publicField(this, "ping", async () => {
if (this.mode === "client") return await this.ping_client();
else if (this.mode === "server") return await this.ping_server();
});
// this function will communicate with the master node and set the stash in that moment
__publicField(this, "setStash", async (key, value = null) => await this.send("_set_stash", { key, value }));
__publicField(this, "getStash", async (key = "") => await this.send("_get_stash", key));
/* method synonims */
__publicField(this, "isBusy", this.isWorking);
__publicField(this, "hasFinished", this.hasDone);
__publicField(this, "hasError", this.isError);
__publicField(this, "toFinish", this.untilFinish);
__publicField(this, "set", this.setStash);
__publicField(this, "get", this.getStash);
__publicField(this, "stash", this.setStash);
__publicField(this, "unstash", this.getStash);
this.mode = params.mode;
if (this.mode === "client") {
params = params;
this.master_host = params.master_host;
this.master_port = params.master_port;
this.services = params.services || [];
this.options = params.options || {};
this.addMethods(params.methods);
} else if (this.mode === "server") {
params = params;
this.setStashFunctions({ set: params.stashSetFunction, get: params.stashGetFunction });
this.setNodeConnection(params.connection, params.network);
this.services = params.services;
this.statusChangeCallback = params.statusChangeCallback;
}
}
/* this functions will set the Node.ts as a client handler for the server */
setNodeConnection(connection, network) {
this.id = connection.getTargetId();
this.network = network;
if (this.stashSetFunction === null || this.stashGetFunction === null)
throw new Error("The stash functions have not been set");
this.listeners = [
// this callbacks will run when we recive this event from the client node
{ event: "_set_status", parameters: ["status"], callback: this.handleStatusChange.bind(this) },
{ event: "_ping", parameters: [], callback: () => "_pong" },
{ event: "_set_stash", parameters: ["key", "value"], callback: this.stashSetFunction },
{ event: "_get_stash", parameters: ["key"], callback: this.stashGetFunction },
{ event: "_get_services_address", parameters: [], callback: () => this.services }
];
connection.setListeners(this.listeners);
}
setStatusChangeCallback(callback) {
this.statusChangeCallback = callback;
}
setStashFunctions({ set, get }) {
this.stashSetFunction = ({ key, value }) => set(key, value);
this.stashGetFunction = get;
}
handleStatusChange(status) {
this.updateStatus(status);
this.statusChangeCallback && this.statusChangeCallback(status, this);
}
lastHeardOf() {
this.updateLastHeardOf();
return this.lastHeardOfIn();
}
async start_server() {
let response = await this.setServices_server(this.services);
if (response === true) {
this.servicesConnected = true;
return true;
} else
throw new Error(`slavery-js: [Node][${this.id}] Could not set services on the client node`);
}
async run_server({ method, parameter }) {
this.handleStatusChange("working");
let res = await this.send("_run", { method, parameter });
this.handleStatusChange("idle");
if (res.isError === true)
res.error = deserializeError(res.error);
return res;
}
async exec_server(code) {
try {
this.handleStatusChange("working");
let res = await this.send("_exec", code);
this.handleStatusChange("idle");
if (res.isError === true)
res.error = deserializeError(res.error);
return res;
} catch (error) {
this.handleStatusChange("idle");
log(`[Node][${this.id}] Error in exec_server: ${error}`);
return { isError: true, error: serializeError(error) };
}
}
async setServices_server(services) {
try {
return await this.send("_set_services", services);
} catch (error) {
log(`[Node][${this.id}] Error in setServices_server: ${error}`);
throw error;
}
}
async ping_server() {
try {
let res = await this.send("_ping");
if (res === "pong") this.updateLastHeardOf();
return true;
} catch (error) {
log(`[Node][${this.id}] Error in ping_server: ${error}`);
return false;
}
}
async exit_server() {
try {
let res = await this.send("_exit", null).catch((error) => {
if (error === "timeout") return true;
log(`[Node][${this.id}] Error in exit_server: ${error}`);
return false;
});
return res;
} catch (error) {
log(`[Node][${this.id}] Error in exit_server: ${error}`);
return false;
}
}
async send(method, parameter = null) {
if (this.network === void 0) throw new Error("The network has not been set");
if (this.id === void 0) throw new Error("The id has not been set");
if (this.mode === void 0) throw new Error("The mode has not been set");
let connection = void 0;
if (this.mode === "server")
connection = this.network.getNode(this.id);
else if (this.mode === "client")
connection = this.network.getService("master");
if (connection === void 0)
throw new Error("Could not get the conenction from the network");
return await connection.send(method, parameter);
}
/* this function will be called when the client node tells us that it is working */
async start_client() {
this.id = this.id || Math.random().toString(36).substring(4);
this.network = new Network({
name: "node",
id: this.id,
options: {
timeout: this.options.timeout || 5 * 60 * 1e3
}
});
if (this.master_host === void 0 || this.master_port === void 0)
throw new Error("The master host and port have not been set");
this.network.connect({ host: this.master_host, port: this.master_port, as: "master" });
this.listeners = [
{ event: "_run", parameters: ["method", "parameter"], callback: this.run_client.bind(this) },
{ event: "_exec", parameters: ["code_string"], callback: this.exec_client.bind(this) },
{ event: "_set_services", parameters: ["services"], callback: this.setServices_client.bind(this) },
{ event: "_is_idle", parameters: [], callback: this.isIdle.bind(this) },
{ event: "_is_busy", parameters: [], callback: this.isBusy.bind(this) },
{ event: "_has_done", parameters: ["method"], callback: this.hasDone.bind(this) },
{ event: "_ping", parameters: [], callback: () => "pong" },
{ event: "_exit", parameters: [], callback: this.exit_client.bind(this) }
];
this.network.registerListeners(this.listeners);
await await_interval({
condition: () => this.servicesConnected,
timeout: 1e3
}).catch(async () => {
let services = await this.get_sevices_address();
this.setServices_client(services);
});
await this.run_startup();
}
async run_client({ method, parameter }) {
await await_interval({
condition: () => this.servicesConnected,
timeout: 1e4,
interval: 10
}).catch(() => {
throw new Error(`slavery-js: [Node][${this.id}] run method, because it could not connect to services`);
});
await await_interval({
condition: () => this.hasStartupFinished,
timeout: 60 * 1e3,
interval: 1
}).catch(() => {
throw new Error(`slavery-js: [Node][${this.id}] run method, because the startup method did not finish`);
});
await await_interval({
condition: () => this.isIdle(),
timeout: 60 * 1e3,
interval: 1
}).catch(() => {
throw new Error(`slavery-js: [Node][${this.id}] run method, because the node timed for becoming idle`);
});
try {
this.updateStatus("working");
let services = await this.get_services();
let services_params = { ...services, slave: this, self: this };
const result = await this.methods[method](parameter, services_params);
this.doneMethods[method] = true;
return { result, isError: false };
} catch (error) {
this.updateStatus("error");
return { error: serializeError(error), isError: true };
} finally {
this.updateStatus("idle");
}
}
async exec_client(code_string) {
if (typeof code_string !== "string")
return { isError: true, error: serializeError(new Error("Code string is not a string")) };
await await_interval({
condition: () => this.servicesConnected,
timeout: 20 * 1e3
}).catch(() => {
throw new Error(`slavery-js: [Node][${this.id}] executing code, because it could not connect to services`);
});
let services = await this.get_services();
let parameter = { ...services, slave: this, self: this };
try {
let result = await execAsyncCode(code_string, parameter);
return { result, isError: false };
} catch (e) {
return { isError: true, error: serializeError(e) };
}
}
async run_startup() {
await await_interval({
condition: () => this.servicesConnected,
timeout: 20 * 1e3
}).catch(() => {
throw new Error(`slavery-js: [Node][${this.id}] Could not startup Node becasue it could not connect to services`);
});
if (this.methods["_startup"] === void 0) {
this.hasStartupFinished = true;
return true;
}
try {
let services = await this.get_services();
let parameter = { ...services, slave: this, self: this };
const result = await this.methods["_startup"](null, parameter);
this.doneMethods["_startup"] = true;
this.hasStartupFinished = true;
return { result, isError: false };
} catch (error) {
this.updateStatus("error");
throw new Error(`[Node][${this.id}] Could not run startup method: ${error}`);
}
}
async get_services() {
let services = this.services.map(
(s) => new ServiceClient(s.name, this.network)
).reduce((acc, s) => {
acc[s.name] = s;
return acc;
}, {});
return services;
}
addMethods(methods) {
this.methods = methods;
for (let method in methods)
this.doneMethods[method] = false;
}
async setServices_client(services) {
this.services = services;
for (let service of services) {
let res = await this.connectService(service);
if (!res) {
console.error("slavery-js: [Node] Client could not connect to the service, ", service.name);
return false;
} else
log(`[Node][${this.id}] Connected to the service, ${service.name}`);
}
this.servicesConnected = true;
return true;
}
async connectService({ name, host, port }) {
if (!host || !port)
throw new Error("The service information is not complete");
if (this.network === void 0)
throw new Error("The network has not been set");
return await this.network.connect({ name, host, port });
}
async get_sevices_address() {
let res = await this.send("_get_services_address");
this.services = res;
return res;
}
async ping_client() {
let res = await this.send("_ping");
if (res === "_pong") this.updateLastHeardOf();
return true;
}
async exit_client() {
setTimeout(async () => {
if (this.methods["_cleanup"] !== void 0)
await this.run_client({ method: "_cleanup", parameter: null });
if (this.network !== void 0) this.network.close();
process.exit(0);
}, 1e3);
return true;
}
getListeners() {
if (this.network === void 0) throw new Error("The network has not been set");
if (this.id === void 0) throw new Error("The id has not been set");
let listeners = [];
let connection = void 0;
if (this.mode === "server") {
connection = this.network.getNode(this.id);
listeners = connection.getListeners();
} else if (this.mode === "client") {
connection = this.network.getNode("master");
listeners = connection.getListeners();
if (connection === void 0)
throw new Error("Could not get the conenction from the network");
}
return listeners;
}
hasDone(method) {
return this.doneMethods[method] || false;
}
}
var Node_default = Node;
export {
Node_default as default
};
//# sourceMappingURL=Node.js.map