@litert/televoke
Version:
A simple RPC service framework.
220 lines • 6.46 kB
JavaScript
"use strict";
/**
* Copyright 2025 Angus.Fenying <fenying@litert.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.createServer = createServer;
exports.createConnector = createConnector;
const Shared = require("../../shared");
const node_events_1 = require("node:events");
const Crypto = require("node:crypto");
function createRandomName() {
return Crypto.randomBytes(8).toString('hex');
}
class MemoryExchange extends node_events_1.EventEmitter {
constructor(name) {
super();
this.name = name;
this._aEnded = false;
this._bEnded = false;
}
send(targetEndpoint, data) {
setImmediate(() => this.emit('data_' + targetEndpoint, data));
}
isEnded(endpoint) {
if (endpoint === 'a') {
return this._aEnded;
}
return this._bEnded;
}
end(endpoint) {
if (this._aEnded && this._bEnded) {
return;
}
if (endpoint === 'a') {
if (this._aEnded) {
return;
}
this._aEnded = true;
setImmediate(() => this.emit('end_a'));
}
else {
if (this._bEnded) {
return;
}
this._bEnded = true;
setImmediate(() => this.emit('end_b'));
}
if (this._aEnded && this._bEnded) {
setImmediate(() => this.emit('close'));
}
}
close() {
this.end('a');
this.end('b');
}
}
class MemorySocket extends node_events_1.EventEmitter {
constructor(_exchange, _endpoint) {
super();
this._exchange = _exchange;
this._endpoint = _endpoint;
this.protocol = 'memory';
this._targetEndpoint = _endpoint === 'a' ? 'b' : 'a';
this._exchange
.on('end_' + this._targetEndpoint, () => {
try {
this.emit('end');
}
catch (e) {
this.emit('error', e);
}
})
.on('data_' + this._endpoint, (data) => {
try {
this.emit('frame', data);
}
catch (e) {
this.emit('error', e);
}
})
.on('error', (e) => this.emit('error', e))
.on('close', () => this.emit('close'));
}
get writable() {
return !this._exchange.isEnded(this._endpoint);
}
getProperty(name) {
switch (name) {
case 'remoteAddress': return 'localhost';
case 'remotePort': return 0;
case 'localAddress': return 'localhost';
case 'localPort': return 0;
default: return null;
}
}
getPropertyNames() {
return ['remoteAddress', 'remotePort', 'localAddress', 'localPort'];
}
getAllProperties() {
return {
'remoteAddress': 'localhost',
'remotePort': 0,
'localAddress': 'localhost',
'localPort': 0,
};
}
write(data) {
if (this._exchange.isEnded(this._endpoint)) {
throw new Shared.errors.network_error({ reason: 'conn_lost' });
}
for (let i = 0; i < data.length; ++i) {
if (!(data[i] instanceof Buffer)) {
data[i] = Buffer.from(data[i]);
}
}
this._exchange.send(this._targetEndpoint, data);
}
destroy() {
this._exchange.close();
}
end() {
this._exchange.end(this._endpoint);
}
}
class MemoryGateway extends node_events_1.EventEmitter {
constructor(name, _server) {
super();
this.name = name;
this._server = _server;
this._running = false;
this._exchanges = {};
}
get running() {
return this._running;
}
_createExchange() {
const ex = new MemoryExchange(this._generateExchangeName());
return this._exchanges[ex.name] = ex;
}
_generateExchangeName() {
let name;
do {
name = createRandomName();
} while (this._exchanges[name]);
return name;
}
connect() {
if (!this._running) {
throw new Shared.errors.network_error({ reason: 'conn_refused' });
}
const ex = this._createExchange();
const serverSocket = new MemorySocket(ex, 'a');
const clientSocket = new MemorySocket(ex, 'b');
ex.on('close', () => {
delete this._exchanges[ex.name];
});
this._server.registerChannel(serverSocket);
return clientSocket;
}
start() {
if (!this._running) {
this._running = true;
this.emit('listening');
}
return Promise.resolve();
}
stop() {
if (!this._running) {
return Promise.resolve();
}
for (const ex of Object.values(this._exchanges)) {
ex.close();
}
this.emit('close');
return Promise.resolve();
}
}
const servers = {};
function createServer(opts, listener) {
opts.name ?? (opts.name = createRandomName());
if (servers[opts.name]) {
throw new Shared.errors.network_error({ reason: 'dup_listen' });
}
const server = new MemoryGateway(opts.name, opts.server);
servers[opts.name] = server;
if (listener) {
server.on('connection', listener);
}
server.on('close', () => {
delete servers[opts.name];
});
return server;
}
class MemoryConnector {
constructor(_name) {
this._name = _name;
}
connect() {
if (!servers[this._name]) {
throw new Shared.errors.network_error({ reason: 'unknown_dest' });
}
return Promise.resolve(servers[this._name].connect());
}
}
function createConnector(name) {
return new MemoryConnector(name);
}
//# sourceMappingURL=Memory.Impl.js.map