UNPKG

@iprokit/service

Version:

Powering distributed systems with simplicity and speed.

375 lines 11.4 kB
"use strict"; /** * @iProKit/Service * Copyright (c) 2019-2025 Rutvik Katuri / iProTechs * SPDX-License-Identifier: Apache-2.0 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RemoteService = void 0; // Import Libs. const events_1 = require("events"); // Import Local. const http_1 = require("./http"); const scp_1 = require("./scp"); Object.defineProperty(exports, "RemoteService", { enumerable: true, get: function () { return scp_1.Client; } }); const sdp_1 = require("./sdp"); /** * A lightweight `Service` for managing HTTP routes and SCP executions. * Ensures smooth communication and coordination by bridging protocols and managing remote service interactions. * * @emits `start` when the service starts. * @emits `stop` when the service stops. */ class Service extends events_1.EventEmitter { /** * Unique identifier of the service. */ identifier; /** * HTTP server instance. */ httpServer; /** * SCP server instance. */ scpServer; /** * SDP server instance. */ sdpServer; /** * Remote service instances. */ remoteServices; /** * State of the service. */ #state; /** * Creates an instance of `Service`. * * @param identifier unique identifier of the service. */ constructor(identifier) { super(); // Initialize options. this.identifier = identifier; // Initialize variables. this.httpServer = new http_1.Server(this.identifier); this.scpServer = new scp_1.Server(this.identifier); this.sdpServer = new sdp_1.Server(this.identifier); this.remoteServices = new Map(); this.#state = 'created'; // Bind listeners. this.onAvailable = this.onAvailable.bind(this); this.onUnavailable = this.onUnavailable.bind(this); // Add listeners. this.sdpServer.addListener('available', this.onAvailable); this.sdpServer.addListener('unavailable', this.onUnavailable); } ////////////////////////////// //////// Gets/Sets ////////////////////////////// /** * HTTP routes registered. */ get routes() { return this.httpServer.routes; } /** * SCP executions registered. */ get executions() { return this.scpServer.executions; } /** * SDP pods discovered. */ get pods() { return this.sdpServer.pods; } /** * `true` if the servers are listening for connections, `false` otherwise. */ get listening() { return { http: this.httpServer.listening, scp: this.scpServer.listening, sdp: this.sdpServer.listening }; } /** * Retrieves the bound address, family, and port of the servers as reported by the operating system. */ address() { return { http: this.httpServer.address(), scp: this.scpServer.address(), sdp: this.sdpServer.address() }; } /** * Multicast group that have been joined. */ get membership() { return this.sdpServer.membership; } /** * Local address of the service. */ get localAddress() { return this.sdpServer.localAddress; } /** * State of the service. */ get state() { return this.#state; } ////////////////////////////// //////// Event Listeners ////////////////////////////// /** * Establishes the connection to a remote service. */ onAvailable(identifier, attributes, host) { const remoteService = this.remoteServices.get(identifier); if (remoteService && !remoteService.connected) { remoteService.connect(Number(attributes['scp']), host); } } /** * Terminates the connection to a remote service. */ onUnavailable(identifier) { const remoteService = this.remoteServices.get(identifier); if (remoteService && remoteService.connected) { remoteService.close(); } } ////////////////////////////// //////// Link ////////////////////////////// /** * Links this service to a remote service. * * No-op if the remote service is already linked. * * @param identifier unique identifier of the remote service. * @param remoteService remote service instance. */ link(identifier, remoteService) { if (!this.remoteServices.has(identifier)) { remoteService.on('close', () => { if (this.#state === 'stopping' || this.#state === 'stopped') return; // Did the pod ghost us? 👻 const foundPod = this.pods.get(identifier); if (foundPod.session === sdp_1.Server.UNAVAILABLE_TOKEN) return; // We assume a rage-quit! 🧐 — reset it. 💤 this.pods.set(identifier, { session: sdp_1.Server.UNAVAILABLE_TOKEN, attributes: null, host: null }); }); this.remoteServices.set(identifier, remoteService); } return this; } ////////////////////////////// //////// IHttpServer ////////////////////////////// /** * Registers a HTTP route for handling GET requests. * * @param path path pattern. * @param handlers request handler functions. */ get(path, ...handlers) { this.httpServer.get(path, ...handlers); return this; } /** * Registers a HTTP route for handling POST requests. * * @param path path pattern. * @param handlers request handler functions. */ post(path, ...handlers) { this.httpServer.post(path, ...handlers); return this; } /** * Registers a HTTP route for handling PUT requests. * * @param path path pattern. * @param handlers request handler functions. */ put(path, ...handlers) { this.httpServer.put(path, ...handlers); return this; } /** * Registers a HTTP route for handling PATCH requests. * * @param path path pattern. * @param handlers request handler functions. */ patch(path, ...handlers) { this.httpServer.patch(path, ...handlers); return this; } /** * Registers a HTTP route for handling DELETE requests. * * @param path path pattern. * @param handlers request handler functions. */ delete(path, ...handlers) { this.httpServer.delete(path, ...handlers); return this; } /** * Registers a HTTP route for handling ALL requests. * * @param path path pattern. * @param handlers request handler functions. */ all(path, ...handlers) { this.httpServer.all(path, ...handlers); return this; } /** * Mounts multiple HTTP routers. * * @param path path pattern. * @param routers routers to mount. */ mount(path, ...routers) { this.httpServer.mount(path, ...routers); return this; } ////////////////////////////// //////// IScpServer ////////////////////////////// /** * Broadcasts the supplied to all remote services. * Returns identifiers of remote services that successfully received broadcast. * * @param operation operation pattern. * @param args arguments to broadcast. */ broadcast(operation, ...args) { return this.scpServer.broadcast(operation, ...args); } /** * Registers a SCP execution for handling REPLY I/O. * * Remote handler function receives a message from a remote service and returns a reply. * * @param operation operation pattern. * @param func function to be executed. */ reply(operation, func) { this.scpServer.reply(operation, func); return this; } /** * Registers a SCP execution for handling CONDUCTOR I/O. * * Remote handler function receives a message from a remote service and coordinates signals. * * @param operation operation pattern. * @param func function to be executed. */ conductor(operation, func) { this.scpServer.conductor(operation, func); return this; } /** * Registers a SCP execution for handling OMNI I/O. * * @param operation operation pattern. * @param handler incoming handler function. */ omni(operation, handler) { this.scpServer.omni(operation, handler); return this; } /** * Attaches a SCP executor. * * @param operation operation pattern. * @param executor executor to attach. */ attach(operation, executor) { this.scpServer.attach(operation, executor); return this; } ////////////////////////////// //////// Start/Stop ////////////////////////////// /** * Starts the service by listening on HTTP, SCP, and SDP servers, connecting to remote services registered. * * @param httpPort local HTTP port. * @param scpPort local SCP port. * @param sdpPort local SDP port. * @param sdpAddress address of the SDP multicast group. * @emits `start` when the service starts. */ async start(httpPort, scpPort, sdpPort, sdpAddress) { this.#state = 'starting'; // HTTP this.httpServer.listen(httpPort); await (0, events_1.once)(this.httpServer, 'listening'); // SCP this.scpServer.listen(scpPort); await (0, events_1.once)(this.scpServer, 'listening'); // SDP this.sdpServer.attributes['http'] = String(httpPort); this.sdpServer.attributes['scp'] = String(scpPort); this.sdpServer.listen(sdpPort, sdpAddress); await (0, events_1.once)(this.sdpServer, 'listening'); // Remote Services const connections = new Array(); for (const remoteService of this.remoteServices.values()) { if (!remoteService.connected) { connections.push((0, events_1.once)(remoteService, 'connect')); } } await Promise.all(connections); this.#state = 'started'; this.emit('start'); return this; } /** * Stops the service by closing all servers and disconnecting from remote services registered. * * @emits `stop` when the service stops. */ async stop() { this.#state = 'stopping'; // HTTP this.httpServer.close(); await (0, events_1.once)(this.httpServer, 'close'); // Remote Services const connections = new Array(); for (const remoteService of this.remoteServices.values()) { if (remoteService.connected) { remoteService.close(); connections.push((0, events_1.once)(remoteService, 'close')); } } await Promise.all(connections); // SCP this.scpServer.close(); await (0, events_1.once)(this.scpServer, 'close'); // SDP this.sdpServer.close(); await (0, events_1.once)(this.sdpServer, 'close'); this.#state = 'stopped'; this.emit('stop'); return this; } } exports.default = Service; //# sourceMappingURL=service.js.map