@iprokit/service
Version:
Powering distributed systems with simplicity and speed.
279 lines • 9.25 kB
JavaScript
;
/**
* @iProKit/Service
* Copyright (c) 2019-2025 Rutvik Katuri / iProTechs
* SPDX-License-Identifier: Apache-2.0
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Conductor = void 0;
// Import Libs.
const events_1 = __importStar(require("events"));
const stream_1 = require("stream");
// Import Local.
const signal_1 = __importDefault(require("./signal"));
/**
* `Coordinator` manages the life cycle of multiple `Conductor` instances and coordinates signals.
*/
class Coordinator {
/**
* Conductors registered.
*/
conductors;
/**
* Creates an instance of `Coordinator`.
*/
constructor() {
this.conductors = new Array();
}
//////////////////////////////
//////// Manage
//////////////////////////////
/**
* Manges multiple conductors.
*
* @param conductors conductors to manage.
*/
manage(...conductors) {
this.conductors.push(...conductors);
return this;
}
//////////////////////////////
//////// Read/Write Operations
//////////////////////////////
/**
* Writes a signal to all the conductors and returns a promise that resolves with the result of the signals.
*
* @param event name of the signal.
* @param tags optional tags of the signal.
*/
async signal(event, tags) {
const signals = new Array();
for (const conductor of this.conductors) {
const signal = (async () => {
await conductor.signal(event, tags); // Write.
const [emittedEvent, emittedTags] = (await (0, events_1.once)(conductor, 'signal')); // Read.
return { event: emittedEvent, tags: emittedTags };
})(); // IIFE 🧑🏽💻
signals.push(signal);
}
return await Promise.all(signals);
}
/**
* Ends all the conductors.
*/
async end() {
const ends = Array();
for (const conductor of this.conductors) {
const end = (async () => {
await conductor.end(); // Write.
await (0, events_1.once)(conductor, 'end'); // Read.
})(); // IIFE 🧑🏽💻
ends.push(end);
}
await Promise.all(ends);
}
}
exports.default = Coordinator;
//////////////////////////////
//////// Conductor
//////////////////////////////
/**
* `Conductor` manages the flow of `Payload` and `Signal` between incoming and outgoing streams.
*
* A payload is a unit of data that is wrapped with `SOP` (Start of payload) and `EOP` (End of payload) signals,
* referred to as payload signals, which indicate the payload's boundaries.
*
* @emits `rfi` when RFI is received on the incoming stream.
* @emits `signal` when a signal is received on the incoming stream.
* @emits `payload` when a payload is received on the incoming stream.
* @emits `end` when end is received on the incoming stream.
*/
class Conductor extends events_1.default {
/**
* Incoming stream to read.
*/
incoming;
/**
* Outgoing stream to write.
*/
outgoing;
/**
* Creates an instance of `Conductor`.
*
* @param incoming incoming stream to read.
* @param outgoing outgoing stream to write.
*/
constructor(incoming, outgoing) {
super();
// Initialize options.
this.incoming = incoming;
this.outgoing = outgoing;
// Add listeners.
this.incoming.addListener('rfi', () => this.emit('rfi'));
this.incoming.addListener('end', () => this.emit('end'));
// Trigger reading.
!this.incoming.rfi && this.readSignal();
}
//////////////////////////////
//////// Read Operations
//////////////////////////////
/**
* Asynchronous iterator.
* Reads a `Payload` from the incoming stream.
*/
async *[Symbol.asyncIterator]() {
yield* this.readPayload();
}
/**
* Reads a `Payload` from the incoming stream.
*
* NOTE: When `END` payload signal is encountered, control is passed to `readSignal`.
*
* @yields data chunk of the payload received on the incoming stream.
*/
async *readPayload() {
while (true) {
const chunk = this.incoming.read();
if (!chunk) {
// Ready or not, here we wait! 👀
await (0, events_1.once)(this.incoming, 'readable');
continue;
}
else if (chunk instanceof signal_1.default && chunk.event === Conductor.START) {
continue;
}
else if (typeof chunk === 'string' || chunk instanceof Buffer) {
yield chunk;
}
else if (chunk instanceof signal_1.default && chunk.event === Conductor.END) {
this.readSignal();
return; // Switching to signal reading mode using `readSignal`.
}
}
}
/**
* Reads a `Signal` from the incoming stream.
*
* NOTE: When `START` payload signal is encountered, control is passed to `readPayload`.
*
* @emits `signal` when a signal is received on the incoming stream.
* @emits `payload` when a payload is received on the incoming stream.
*/
async readSignal() {
while (true) {
const chunk = this.incoming.read();
if (!chunk) {
// Waiting for a clearer sign! 🔮
await (0, events_1.once)(this.incoming, 'readable');
continue;
}
else if (chunk instanceof signal_1.default && !(chunk.event === Conductor.START || chunk.event === Conductor.END)) {
this.emit('signal', chunk.event, chunk.tags);
}
else if (chunk instanceof signal_1.default && chunk.event === Conductor.START) {
this.incoming.unshift(chunk);
this.emit('payload');
return; // Switching to payload reading mode using `readPayload`.
}
}
}
//////////////////////////////
//////// Write Operations
//////////////////////////////
/**
* Writes a `Payload` to the outgoing stream.
*
* @param chunk data chunk of the payload.
*/
async deliver(chunk) {
await this.write(new signal_1.default(Conductor.START));
await this.write(chunk);
await this.write(new signal_1.default(Conductor.END));
}
/**
* Writes a `Signal` to the outgoing stream.
*
* @param event name of the signal.
* @param tags optional tags of the signal.
*/
async signal(event, tags) {
await this.write(new signal_1.default(event, tags));
}
/**
* Flushes the stream by writing the RFI frame and an empty data frame into the outgoing stream.
*/
async flush() {
await this.write('');
}
/**
* Writes data to the outgoing stream.
*
* @param chunk chunk to write.
*/
async write(chunk) {
const write = this.outgoing.write(chunk);
if (!write) {
// Ah, backpressure strikes again! 😬
await (0, events_1.once)(this.outgoing, 'drain');
}
}
//////////////////////////////
//////// End
//////////////////////////////
/**
* Ends the outgoing stream.
*/
async end() {
this.outgoing.end();
await stream_1.promises.finished(this.outgoing);
}
//////////////////////////////
//////// Destroy
//////////////////////////////
/**
* Destroy both the incoming and outgoing streams.
*/
destroy() {
this.incoming.destroy();
this.outgoing.destroy();
}
//////////////////////////////
//////// Payload Definitions
//////////////////////////////
/**
* Indicates start of payload.
*/
static START = 'SOP';
/**
* Indicates end of payload.
*/
static END = 'EOP';
}
exports.Conductor = Conductor;
//# sourceMappingURL=coordinator.js.map