@api.global/typedrequest
Version:
A TypeScript library for making typed requests towards APIs, including facilities for handling requests, routing, and virtual stream handling.
369 lines • 30.8 kB
JavaScript
import * as plugins from './plugins.js';
import { TypedRouter } from './classes.typedrouter.js';
const closingBit = '#############CLOSING BIT#############';
/**
* 1. A VirtualStream connects over the network
* 2. It is always paired to one other VirtualStream
* on the other side with the same streamId.
* 3. It has a Readable and Writable side.
* 4. The Writable side is Readable on the other side and vice versa.
*/
export class VirtualStream {
// STATIC
static encodePayloadForNetwork(objectPayload, commFunctions, originalPayload, path = []) {
if (!objectPayload) {
return objectPayload;
}
if (plugins.smartbuffer.isBufferLike(objectPayload)) {
return objectPayload;
}
if (objectPayload instanceof VirtualStream) {
if (!objectPayload.side && commFunctions.sendMethod) {
objectPayload.side = 'requesting';
objectPayload.sendMethod = commFunctions.sendMethod;
}
if (!objectPayload.side && commFunctions.typedrouter) {
objectPayload.side = 'responding';
objectPayload.typedrouter = commFunctions.typedrouter;
commFunctions.typedrouter.registeredVirtualStreams.add(objectPayload);
}
if (!originalPayload.response || path.includes('response')) {
objectPayload.startKeepAliveLoop();
return {
_isVirtualStream: true,
streamId: objectPayload.streamId,
};
}
else {
return {
_OBMITTED_VIRTUAL_STREAM: true,
reason: 'path is under .request: obmitted for deduplication reasons in response cycle.',
};
}
}
else if (Array.isArray(objectPayload)) {
// For arrays, we recurse over each item.
return objectPayload.map((item, index) => VirtualStream.encodePayloadForNetwork(item, commFunctions, originalPayload || objectPayload, path.concat(String(index)) // Convert index to string and concatenate to path
));
}
else if (objectPayload !== null && typeof objectPayload === 'object') {
// For objects, we recurse over each key-value pair.
return Object.entries(objectPayload).reduce((acc, [key, value]) => {
const newPath = path.concat(key); // Concatenate the new key to the path
acc[key] = VirtualStream.encodePayloadForNetwork(value, commFunctions, originalPayload || objectPayload, newPath);
return acc;
}, {});
}
else {
return objectPayload;
}
}
static decodePayloadFromNetwork(objectPayload, commFunctions) {
if (plugins.smartbuffer.isBufferLike(objectPayload)
|| objectPayload instanceof TypedRouter) {
return objectPayload;
}
if (objectPayload !== null && typeof objectPayload === 'object') {
if (objectPayload._isVirtualStream) {
const virtualStream = new VirtualStream();
virtualStream.streamId = objectPayload.streamId;
if (!virtualStream.side && commFunctions.sendMethod) {
virtualStream.side = 'requesting';
virtualStream.sendMethod = commFunctions.sendMethod;
}
if (!virtualStream.side && commFunctions.typedrouter) {
virtualStream.side = 'responding';
virtualStream.typedrouter = commFunctions.typedrouter;
commFunctions.typedrouter.registeredVirtualStreams.add(virtualStream);
}
virtualStream.startKeepAliveLoop();
return virtualStream;
}
else if (Array.isArray(objectPayload)) {
const returnArray = [];
for (const item of objectPayload) {
returnArray.push(VirtualStream.decodePayloadFromNetwork(item, commFunctions));
}
return returnArray;
}
else {
return Object.keys(objectPayload).reduce((acc, key) => {
acc[key] = VirtualStream.decodePayloadFromNetwork(objectPayload[key], commFunctions);
return acc;
}, {});
}
}
else {
return objectPayload;
}
}
constructor() {
this.streamId = plugins.isounique.uni();
// wether to keep the stream alive
this.keepAlive = true;
// backpressured arrays
this.sendBackpressuredArray = new plugins.lik.BackpressuredArray(16);
this.receiveBackpressuredArray = new plugins.lik.BackpressuredArray(16);
}
/**
* takes care of sending
*/
async workOnQueue() {
if (this.workingDeferred) {
return this.workingDeferred.promise;
}
else {
this.workingDeferred = plugins.smartpromise.defer();
}
if (this.side === 'requesting') {
let thisSideIsBackpressured = !this.receiveBackpressuredArray.checkSpaceAvailable();
let otherSideHasNext = false;
let otherSideIsBackpressured = false;
// helper functions
const getFeedback = async () => {
const streamTr = await this.sendMethod({
method: '##VirtualStream##',
request: {
streamId: this.streamId,
cycleId: plugins.isounique.uni(),
cycle: 'request',
mainPurpose: 'feedback',
next: this.sendBackpressuredArray.data.length > 0,
backpressure: !this.receiveBackpressuredArray.checkSpaceAvailable(),
},
response: null,
}).catch(() => {
console.log('stream ended immaturely');
this.keepAlive = false;
});
if (streamTr && streamTr.response) {
otherSideIsBackpressured = streamTr.response.backpressure;
otherSideHasNext = streamTr.response.next;
}
};
await getFeedback();
// do work loop
while (this.sendBackpressuredArray.data.length > 0 || otherSideHasNext) {
if (otherSideIsBackpressured) {
while (otherSideIsBackpressured) {
console.log('waiting for feedback because of backpressure...');
await plugins.smartdelay.delayFor(50);
await getFeedback();
}
}
let dataArg;
if (this.sendBackpressuredArray.data.length > 0) {
dataArg = this.sendBackpressuredArray.shift();
}
let streamTr;
streamTr = await this.sendMethod({
method: '##VirtualStream##',
request: {
streamId: this.streamId,
cycleId: plugins.isounique.uni(),
cycle: 'request',
mainPurpose: dataArg ? 'chunk' : 'read',
backpressure: thisSideIsBackpressured,
next: this.sendBackpressuredArray.data.length > 0,
...dataArg ? { chunkData: dataArg } : {},
},
response: null,
}).catch(() => {
console.log('stream ended immaturely');
this.keepAlive = false;
return null;
});
if (streamTr && streamTr.response && streamTr.response.chunkData) {
this.receiveBackpressuredArray.push(streamTr.response.chunkData);
}
otherSideIsBackpressured = streamTr && streamTr.response && streamTr.response.backpressure;
thisSideIsBackpressured = !this.receiveBackpressuredArray.checkSpaceAvailable();
// lets care about looping
otherSideHasNext = streamTr && streamTr.response && streamTr.response.next;
}
}
this.workingDeferred.resolve();
this.workingDeferred = null;
}
/**
* This method handles the stream only on the responding side
* @param streamTrArg
* @returns
*/
async handleStreamTr(streamTrArg) {
if (streamTrArg.request.keepAlive === true && this.keepAlive === true) {
this.lastKeepAliveEvent = Date.now();
}
else if (streamTrArg.request.keepAlive === false) {
this.keepAlive = false;
}
// keepAlive handling
if (streamTrArg.request.mainPurpose === 'keepAlive') {
// if the main purpose is keepAlive, we answer with a keepAlive
streamTrArg.response = {
streamId: this.streamId,
cycleId: streamTrArg.request.cycleId,
cycle: 'response',
mainPurpose: 'keepAlive',
keepAlive: this.keepAlive,
next: this.sendBackpressuredArray.data.length > 0,
backpressure: !this.receiveBackpressuredArray.checkSpaceAvailable(),
};
}
// feedback handling
if (streamTrArg.request.mainPurpose === 'feedback') {
streamTrArg.response = {
streamId: this.streamId,
cycleId: streamTrArg.request.cycleId,
cycle: 'response',
mainPurpose: 'feedback',
next: this.sendBackpressuredArray.data.length > 0,
backpressure: !this.receiveBackpressuredArray.checkSpaceAvailable(),
};
}
// chunk handling
if (streamTrArg.request.mainPurpose === 'chunk') {
this.receiveBackpressuredArray.push(streamTrArg.request.chunkData);
if (this.sendBackpressuredArray.data.length > 0 && streamTrArg.response.backpressure === false) {
const dataArg = this.sendBackpressuredArray.shift();
streamTrArg.response = {
streamId: this.streamId,
cycleId: streamTrArg.request.cycleId,
cycle: 'response',
mainPurpose: 'chunk',
next: this.sendBackpressuredArray.data.length > 1, // 1 and not 0 because we call shift a few lines down
backpressure: !this.receiveBackpressuredArray.checkSpaceAvailable(),
chunkData: this.sendBackpressuredArray.shift(),
};
}
else {
streamTrArg.response = {
streamId: this.streamId,
cycleId: streamTrArg.request.cycleId,
cycle: 'response',
mainPurpose: 'feedback',
next: this.sendBackpressuredArray.data.length > 0,
backpressure: !this.receiveBackpressuredArray.checkSpaceAvailable(),
};
}
streamTrArg.request = null;
}
return streamTrArg;
}
// lifecycle methods
/**
* closes the virtual stream
*/
async cleanup() {
if (this.typedrouter) {
this.typedrouter.registeredVirtualStreams.remove(this);
}
}
/**
* a keepAlive loop that works across technologies
*/
async startKeepAliveLoop() {
// initially wait for a second
if (this.side === 'responding') {
return;
}
await plugins.smartdelay.delayFor(0);
console.log(`starting keepalive loop on side ${this.side}`);
let counter = 0;
keepAliveLoop: while (this.keepAlive) {
await this.triggerKeepAlive();
await plugins.smartdelay.delayFor(1000);
}
await plugins.smartdelay.delayFor(1000);
await this.cleanup();
console.log(`cleaned up for stream ${this.streamId}`);
}
async triggerKeepAlive() {
if (this.side === 'requesting') {
console.log(`keepalive sent.`);
const streamTr = await this.sendMethod({
method: '##VirtualStream##',
request: {
streamId: this.streamId,
cycleId: plugins.isounique.uni(),
cycle: 'request',
mainPurpose: 'keepAlive',
keepAlive: this.keepAlive,
},
response: null,
}).catch(() => {
this.keepAlive = false;
});
// lets handle keepAlive
if (streamTr && streamTr.response && streamTr.response.keepAlive === false) {
this.keepAlive = false;
}
else {
this.lastKeepAliveEvent = Date.now();
}
if (streamTr && streamTr.response && streamTr.response.next) {
this.workOnQueue();
}
}
if (Date.now() - this.lastKeepAliveEvent > 10000) {
console.log(`closing stream for ${this.streamId}`);
this.keepAlive = false;
}
}
// Data sending and receiving
async sendData(dataArg) {
this.sendBackpressuredArray.push(dataArg);
this.workOnQueue();
await this.sendBackpressuredArray.waitForSpace();
}
async fetchData() {
if (this.receiveBackpressuredArray.hasSpace) {
// do something maybe?
}
await this.receiveBackpressuredArray.waitForItems();
const dataPackage = this.receiveBackpressuredArray.shift();
return dataPackage;
}
/**
* reads from a Readable and sends it to the other side
* @param readableStreamArg
*/
async readFromWebstream(readableStreamArg, closeAfterReading = true) {
const reader = readableStreamArg.getReader();
let streamIsDone = false;
while (!streamIsDone) {
const { value, done } = await reader.read();
if (value) {
await this.sendData(value);
}
streamIsDone = done;
}
if (closeAfterReading) {
await this.close(true);
}
}
async writeToWebstream(writableStreamArg) {
const writer = writableStreamArg.getWriter();
while (this.keepAlive || this.receiveBackpressuredArray.checkHasItems()) {
const value = await this.fetchData();
if (value === closingBit) {
writer.releaseLock();
await writableStreamArg.close();
break;
}
await writer.write(value);
}
}
/**
* closes the stream
* if sendClosingBitArg is true, the stream will send a closing bit
* @param sendClosingBitArg
*/
async close(sendClosingBitArg = false) {
if (sendClosingBitArg) {
this.sendData(closingBit);
}
this.keepAlive = false;
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy52aXJ0dWFsc3RyZWFtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy52aXJ0dWFsc3RyZWFtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUd2RCxNQUFNLFVBQVUsR0FBUSx1Q0FBdUMsQ0FBQztBQVNoRTs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sYUFBYTtJQUN4QixTQUFTO0lBQ0YsTUFBTSxDQUFDLHVCQUF1QixDQUNuQyxhQUFrQixFQUNsQixhQUE2QixFQUM3QixlQUFxQixFQUNyQixJQUFJLEdBQUcsRUFBRTtRQUVULElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUNuQixPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO1FBQ0QsSUFBSSxPQUFPLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxhQUFhLENBQUMsRUFBRSxDQUFDO1lBQ3BELE9BQU8sYUFBYSxDQUFDO1FBQ3ZCLENBQUM7UUFDRCxJQUFJLGFBQWEsWUFBWSxhQUFhLEVBQUUsQ0FBQztZQUMzQyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksSUFBSSxhQUFhLENBQUMsVUFBVSxFQUFFLENBQUM7Z0JBQ3BELGFBQWEsQ0FBQyxJQUFJLEdBQUcsWUFBWSxDQUFDO2dCQUNsQyxhQUFhLENBQUMsVUFBVSxHQUFHLGFBQWEsQ0FBQyxVQUFVLENBQUM7WUFDdEQsQ0FBQztZQUNELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxJQUFJLGFBQWEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDckQsYUFBYSxDQUFDLElBQUksR0FBRyxZQUFZLENBQUM7Z0JBQ2xDLGFBQWEsQ0FBQyxXQUFXLEdBQUcsYUFBYSxDQUFDLFdBQVcsQ0FBQztnQkFDdEQsYUFBYSxDQUFDLFdBQVcsQ0FBQyx3QkFBd0IsQ0FBQyxHQUFH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