@iprokit/service
Version:
Powering distributed systems with simplicity and speed.
375 lines • 11.4 kB
JavaScript
"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