UNPKG

@iprokit/service

Version:

Powering distributed systems with simplicity and speed.

279 lines 9.25 kB
"use strict"; /** * @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