UNPKG

@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
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy52aXJ0dWFsc3RyZWFtLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvY2xhc3Nlcy52aXJ0dWFsc3RyZWFtLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUd2RCxNQUFNLFVBQVUsR0FBUSx1Q0FBdUMsQ0FBQztBQVNoRTs7Ozs7O0dBTUc7QUFDSCxNQUFNLE9BQU8sYUFBYTtJQUN4QixTQUFTO0lBQ0YsTUFBTSxDQUFDLHVCQUF1QixDQUNuQyxhQUFrQixFQUNsQixhQUE2QixFQUM3QixlQUFxQixFQUNyQixJQUFJLEdBQUcsRUFBRTtRQUVULElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQztZQUNuQixPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO1FBQ0QsSUFBSSxPQUFPLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxhQUFhLENBQUMsRUFBRSxDQUFDO1lBQ3BELE9BQU8sYUFBYSxDQUFDO1FBQ3ZCLENBQUM7UUFDRCxJQUFJLGFBQWEsWUFBWSxhQUFhLEVBQUUsQ0FBQztZQUMzQyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksSUFBSSxhQUFhLENBQUMsVUFBVSxFQUFFLENBQUM7Z0JBQ3BELGFBQWEsQ0FBQyxJQUFJLEdBQUcsWUFBWSxDQUFDO2dCQUNsQyxhQUFhLENBQUMsVUFBVSxHQUFHLGFBQWEsQ0FBQyxVQUFVLENBQUM7WUFDdEQsQ0FBQztZQUNELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxJQUFJLGFBQWEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztnQkFDckQsYUFBYSxDQUFDLElBQUksR0FBRyxZQUFZLENBQUM7Z0JBQ2xDLGFBQWEsQ0FBQyxXQUFXLEdBQUcsYUFBYSxDQUFDLFdBQVcsQ0FBQztnQkFDdEQsYUFBYSxDQUFDLFdBQVcsQ0FBQyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7WUFDeEUsQ0FBQztZQUNELElBQUksQ0FBQyxlQUFlLENBQUMsUUFBUSxJQUFJLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztnQkFDM0QsYUFBYSxDQUFDLGtCQUFrQixFQUFFLENBQUM7Z0JBQ25DLE9BQU87b0JBQ0wsZ0JBQWdCLEVBQUUsSUFBSTtvQkFDdEIsUUFBUSxFQUFFLGFBQWEsQ0FBQyxRQUFRO2lCQUNqQyxDQUFDO1lBQ0osQ0FBQztpQkFBTSxDQUFDO2dCQUNOLE9BQU87b0JBQ0wsd0JBQXdCLEVBQUUsSUFBSTtvQkFDOUIsTUFBTSxFQUFFLCtFQUErRTtpQkFDeEYsQ0FBQztZQUNKLENBQUM7UUFDSCxDQUFDO2FBQU0sSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7WUFDeEMseUNBQXlDO1lBQ3pDLE9BQU8sYUFBYSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxDQUN2QyxhQUFhLENBQUMsdUJBQXVCLENBQ25DLElBQUksRUFDSixhQUFhLEVBQ2IsZUFBZSxJQUFJLGFBQWEsRUFDaEMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxrREFBa0Q7YUFDOUUsQ0FDRixDQUFDO1FBQ0osQ0FBQzthQUFNLElBQUksYUFBYSxLQUFLLElBQUksSUFBSSxPQUFPLGFBQWEsS0FBSyxRQUFRLEVBQUUsQ0FBQztZQUN2RSxvREFBb0Q7WUFDcEQsT0FBTyxNQUFNLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLEdBQUcsRUFBRSxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsRUFBRSxFQUFFO2dCQUNoRSxNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsc0NBQXNDO2dCQUN4RSxHQUFHLENBQUMsR0FBRyxDQUFDLEdBQUcsYUFBYSxDQUFDLHVCQUF1QixDQUM5QyxLQUFLLEVBQ0wsYUFBYSxFQUNiLGVBQWUsSUFBSSxhQUFhLEVBQ2hDLE9BQU8sQ0FDUixDQUFDO2dCQUNGLE9BQU8sR0FBRyxDQUFDO1lBQ2IsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO1FBQ1QsQ0FBQzthQUFNLENBQUM7WUFDTixPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO0lBQ0gsQ0FBQztJQUVNLE1BQU0sQ0FBQyx3QkFBd0IsQ0FBQyxhQUFrQixFQUFFLGFBQTZCO1FBRXRGLElBQ0UsT0FBTyxDQUFDLFdBQVcsQ0FBQyxZQUFZLENBQUMsYUFBYSxDQUFDO2VBQzVDLGFBQWEsWUFBWSxXQUFXLEVBQ3ZDLENBQUM7WUFDRCxPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO1FBQ0QsSUFBSSxhQUFhLEtBQUssSUFBSSxJQUFJLE9BQU8sYUFBYSxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQ2hFLElBQUksYUFBYSxDQUFDLGdCQUFnQixFQUFFLENBQUM7Z0JBQ25DLE1BQU0sYUFBYSxHQUFHLElBQUksYUFBYSxFQUFFLENBQUM7Z0JBQzFDLGFBQWEsQ0FBQyxRQUFRLEdBQUcsYUFBYSxDQUFDLFFBQVEsQ0FBQztnQkFDaEQsSUFBSSxDQUFDLGFBQWEsQ0FBQyxJQUFJLElBQUksYUFBYSxDQUFDLFVBQVUsRUFBRSxDQUFDO29CQUNwRCxhQUFhLENBQUMsSUFBSSxHQUFHLFlBQVksQ0FBQztvQkFDbEMsYUFBYSxDQUFDLFVBQVUsR0FBRyxhQUFhLENBQUMsVUFBVSxDQUFDO2dCQUN0RCxDQUFDO2dCQUNELElBQUksQ0FBQyxhQUFhLENBQUMsSUFBSSxJQUFJLGFBQWEsQ0FBQyxXQUFXLEVBQUUsQ0FBQztvQkFDckQsYUFBYSxDQUFDLElBQUksR0FBRyxZQUFZLENBQUM7b0JBQ2xDLGFBQWEsQ0FBQyxXQUFXLEdBQUcsYUFBYSxDQUFDLFdBQVcsQ0FBQztvQkFDdEQsYUFBYSxDQUFDLFdBQVcsQ0FBQyx3QkFBd0IsQ0FBQyxHQUFHLENBQUMsYUFBYSxDQUFDLENBQUM7Z0JBQ3hFLENBQUM7Z0JBQ0QsYUFBYSxDQUFDLGtCQUFrQixFQUFFLENBQUM7Z0JBQ25DLE9BQU8sYUFBYSxDQUFDO1lBQ3ZCLENBQUM7aUJBQU0sSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxFQUFFLENBQUM7Z0JBQ3hDLE1BQU0sV0FBVyxHQUFHLEVBQUUsQ0FBQztnQkFDdkIsS0FBSyxNQUFNLElBQUksSUFBSSxhQUFhLEVBQUUsQ0FBQztvQkFDakMsV0FBVyxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsd0JBQXdCLENBQUMsSUFBSSxFQUFFLGFBQWEsQ0FBQyxDQUFDLENBQUM7Z0JBQ2hGLENBQUM7Z0JBQ0QsT0FBTyxXQUFXLENBQUM7WUFDckIsQ0FBQztpQkFBTSxDQUFDO2dCQUNOLE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEVBQUU7b0JBQ3BELEdBQUcsQ0FBQyxHQUFHLENBQUMsR0FBRyxhQUFhLENBQUMsd0JBQXdCLENBQUMsYUFBYSxDQUFDLEdBQUcsQ0FBQyxFQUFFLGFBQWEsQ0FBQyxDQUFDO29CQUNyRixPQUFPLEdBQUcsQ0FBQztnQkFDYixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7WUFDVCxDQUFDO1FBQ0gsQ0FBQzthQUFNLENBQUM7WUFDTixPQUFPLGFBQWEsQ0FBQztRQUN2QixDQUFDO0lBQ0gsQ0FBQztJQXlCRDtRQXBCTyxhQUFRLEdBQVcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUUsQ0FBQztRQU1sRCxrQ0FBa0M7UUFDMUIsY0FBUyxHQUFHLElBQUksQ0FBQztRQUd6Qix1QkFBdUI7UUFDZiwyQkFBc0IsR0FDNUIsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixDQUNoQyxFQUFFLENBQ0gsQ0FBQztRQUNJLDhCQUF5QixHQUMvQixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsa0JBQWtCLENBQ2hDLEVBQUUsQ0FDSCxDQUFDO0lBRVcsQ0FBQztJQUloQjs7T0FFRztJQUNLLEtBQUssQ0FBQyxXQUFXO1FBQ3ZCLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRSxDQUFDO1lBQ3pCLE9BQU8sSUFBSSxDQUFDLGVBQWUsQ0FBQyxPQUFPLENBQUM7UUFDdEMsQ0FBQzthQUFNLENBQUM7WUFDTixJQUFJLENBQUMsZUFBZSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUMsS0FBSyxFQUFFLENBQUM7UUFDdEQsQ0FBQztRQUNELElBQUcsSUFBSSxDQUFDLElBQUksS0FBSyxZQUFZLEVBQUUsQ0FBQztZQUM5QixJQUFJLHVCQUF1QixHQUFHLENBQUMsSUFBSSxDQUFDLHlCQUF5QixDQUFDLG1CQUFtQixFQUFFLENBQUM7WUFDcEYsSUFBSSxnQkFBZ0IsR0FBRyxLQUFLLENBQUM7WUFDN0IsSUFBSSx3QkFBd0IsR0FBRyxLQUFLLENBQUM7WUFFckMsbUJBQW1CO1lBQ25CLE1BQU0sV0FBVyxHQUFHLEtBQUssSUFBSSxFQUFFO2dCQUM3QixNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUM7b0JBQ3JDLE1BQU0sRUFBRSxtQkFBbUI7b0JBQzNCLE9BQU8sRUFBRTt3QkFDUCxRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7d0JBQ3ZCLE9BQU8sRUFBRSxPQUFPLENBQUMsU0FBUyxDQUFDLEdBQUcsRUFBRTt3QkFDaEMsS0FBSyxFQUFFLFNBQVM7d0JBQ2hCLFdBQVcsRUFBRSxVQUFVO3dCQUN2QixJQUFJLEVBQUUsSUFBSSxDQUFDLHNCQUFzQixDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQzt3QkFDakQsWUFBWSxFQUFFLENBQUMsSUFBSSxDQUFDLHlCQUF5QixDQUFDLG1CQUFtQixFQUFFO3FCQUNwRTtvQkFDRCxRQUFRLEVBQUUsSUFBSTtpQkFDZixDQUFDLENBQUMsS0FBSyxDQUFDLEdBQUcsRUFBRTtvQkFDWixPQUFPLENBQUMsR0FBRyxDQUFDLHlCQUF5QixDQUFDLENBQUM7b0JBQ3ZDLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO2dCQUN6QixDQUFDLENBQUMsQ0FBQztnQkFDSCxJQUFJLFFBQVEsSUFBSSxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7b0JBQ2xDLHdCQUF3QixHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFBO29CQUN6RCxnQkFBZ0IsR0FBRyxRQUFRLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQztnQkFDNUMsQ0FBQztZQUNILENBQUMsQ0FBQTtZQUNELE1BQU0sV0FBVyxFQUFFLENBQUM7WUFFcEIsZUFBZTtZQUNmLE9BQU8sSUFBSSxDQUFDLHNCQUFzQixDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLGdCQUFnQixFQUFFLENBQUM7Z0JBQ3ZFLElBQUksd0JBQXdCLEVBQUUsQ0FBQztvQkFDN0IsT0FBTyx3QkFBd0IsRUFBRSxDQUFDO3dCQUNoQyxPQUFPLENBQUMsR0FBRyxDQUFDLGlEQUFpRCxDQUFDLENBQUM7d0JBQy9ELE1BQU0sT0FBTyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7d0JBQ3RDLE1BQU0sV0FBVyxFQUFFLENBQUM7b0JBQ3RCLENBQUM7Z0JBQ0gsQ0FBQztnQkFDRCxJQUFJLE9BQW1ELENBQUM7Z0JBQ3hELElBQUksSUFBSSxDQUFDLHNCQUFzQixDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7b0JBQ2hELE9BQU8sR0FBRyxJQUFJLENBQUMsc0JBQXNCLENBQUMsS0FBSyxFQUFFLENBQUM7Z0JBQ2hELENBQUM7Z0JBQ0QsSUFBSSxRQUF1RCxDQUFDO2dCQUM1RCxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDO29CQUMvQixNQUFNLEVBQUUsbUJBQW1CO29CQUMzQixPQUFPLEVBQUU7d0JBQ1AsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO3dCQUN2QixPQUFPLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUU7d0JBQ2hDLEtBQUssRUFBRSxTQUFTO3dCQUNoQixXQUFXLEVBQUUsT0FBTyxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLE1BQU07d0JBQ3ZDLFlBQVksRUFBRSx1QkFBdUI7d0JBQ3JDLElBQUksRUFBRSxJQUFJLENBQUMsc0JBQXNCLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxDQUFDO3dCQUNqRCxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsRUFBRSxTQUFTLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUU7cUJBQ3pDO29CQUNELFFBQVEsRUFBRSxJQUFJO2lCQUNmLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFO29CQUNaLE9BQU8sQ0FBQyxHQUFHLENBQUMseUJBQXlCLENBQUMsQ0FBQztvQkFDdkMsSUFBSSxDQUFDLFNBQVMsR0FBRyxLQUFLLENBQUM7b0JBQ3ZCLE9BQU8sSUFBSSxDQUFDO2dCQUNkLENBQUMsQ0FBQyxDQUFDO2dCQUVILElBQUksUUFBUSxJQUFJLFFBQVEsQ0FBQyxRQUFRLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxTQUFTLEVBQUUsQ0FBQztvQkFDakUsSUFBSSxDQUFDLHlCQUF5QixDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDO2dCQUNuRSxDQUFDO2dCQUNELHdCQUF3QixHQUFHLFFBQVEsSUFBSSxRQUFRLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQyxRQUFRLENBQUMsWUFBWSxDQUFDO2dCQUMzRix1QkFBdUIsR0FBRyxDQUFDLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO2dCQUVoRiwwQkFBMEI7Z0JBQzFCLGdCQUFnQixHQUFHLFFBQVEsSUFBSSxRQUFRLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDO1lBQzdFLENBQUM7UUFFSCxDQUFDO1FBQ0QsSUFBSSxDQUFDLGVBQWUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUMvQixJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztJQUM5QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLEtBQUssQ0FBQyxjQUFjLENBQUMsV0FBMEQ7UUFDcEYsSUFBSSxXQUFXLENBQUMsT0FBTyxDQUFDLFNBQVMsS0FBSyxJQUFJLElBQUksSUFBSSxDQUFDLFNBQVMsS0FBSyxJQUFJLEVBQUUsQ0FBQztZQUN0RSxJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZDLENBQUM7YUFBTSxJQUFJLFdBQVcsQ0FBQyxPQUFPLENBQUMsU0FBUyxLQUFLLEtBQUssRUFBRSxDQUFDO1lBQ25ELElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO1FBQ3pCLENBQUM7UUFFRCxxQkFBcUI7UUFDckIsSUFBSSxXQUFXLENBQUMsT0FBTyxDQUFDLFdBQVcsS0FBSyxXQUFXLEVBQUUsQ0FBQztZQUNwRCwrREFBK0Q7WUFDL0QsV0FBVyxDQUFDLFFBQVEsR0FBRztnQkFDckIsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO2dCQUN2QixPQUFPLEVBQUUsV0FBVyxDQUFDLE9BQU8sQ0FBQyxPQUFPO2dCQUNwQyxLQUFLLEVBQUUsVUFBVTtnQkFDakIsV0FBVyxFQUFFLFdBQVc7Z0JBQ3hCLFNBQVMsRUFBRSxJQUFJLENBQUMsU0FBUztnQkFDekIsSUFBSSxFQUFFLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUM7Z0JBQ2pELFlBQVksRUFBRSxDQUFDLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxtQkFBbUIsRUFBRTthQUNwRSxDQUFDO1FBQ0osQ0FBQztRQUVELG9CQUFvQjtRQUNwQixJQUFJLFdBQVcsQ0FBQyxPQUFPLENBQUMsV0FBVyxLQUFLLFVBQVUsRUFBRSxDQUFDO1lBQ25ELFdBQVcsQ0FBQyxRQUFRLEdBQUc7Z0JBQ3JCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtnQkFDdkIsT0FBTyxFQUFFLFdBQVcsQ0FBQyxPQUFPLENBQUMsT0FBTztnQkFDcEMsS0FBSyxFQUFFLFVBQVU7Z0JBQ2pCLFdBQVcsRUFBRSxVQUFVO2dCQUN2QixJQUFJLEVBQUUsSUFBSSxDQUFDLHNCQUFzQixDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQztnQkFDakQsWUFBWSxFQUFFLENBQUMsSUFBSSxDQUFDLHlCQUF5QixDQUFDLG1CQUFtQixFQUFFO2FBQ3BFLENBQUM7UUFDSixDQUFDO1FBRUQsaUJBQWlCO1FBQ2pCLElBQUksV0FBVyxDQUFDLE9BQU8sQ0FBQyxXQUFXLEtBQUssT0FBTyxFQUFFLENBQUM7WUFDaEQsSUFBSSxDQUFDLHlCQUF5QixDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxDQUFDO1lBQ25FLElBQUksSUFBSSxDQUFDLHNCQUFzQixDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLFdBQVcsQ0FBQyxRQUFRLENBQUMsWUFBWSxLQUFLLEtBQUssRUFBRSxDQUFDO2dCQUMvRixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsc0JBQXNCLENBQUMsS0FBSyxFQUFFLENBQUM7Z0JBQ3BELFdBQVcsQ0FBQyxRQUFRLEdBQUc7b0JBQ3JCLFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUTtvQkFDdkIsT0FBTyxFQUFFLFdBQVcsQ0FBQyxPQUFPLENBQUMsT0FBTztvQkFDcEMsS0FBSyxFQUFFLFVBQVU7b0JBQ2pCLFdBQVcsRUFBRSxPQUFPO29CQUNwQixJQUFJLEVBQUUsSUFBSSxDQUFDLHNCQUFzQixDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLHFEQUFxRDtvQkFDeEcsWUFBWSxFQUFFLENBQUMsSUFBSSxDQUFDLHlCQUF5QixDQUFDLG1CQUFtQixFQUFFO29CQUNuRSxTQUFTLEVBQUUsSUFBSSxDQUFDLHNCQUFzQixDQUFDLEtBQUssRUFBRTtpQkFDL0MsQ0FBQztZQUNKLENBQUM7aUJBQU0sQ0FBQztnQkFDTixXQUFXLENBQUMsUUFBUSxHQUFHO29CQUNyQixRQUFRLEVBQUUsSUFBSSxDQUFDLFFBQVE7b0JBQ3ZCLE9BQU8sRUFBRSxXQUFXLENBQUMsT0FBTyxDQUFDLE9BQU87b0JBQ3BDLEtBQUssRUFBRSxVQUFVO29CQUNqQixXQUFXLEVBQUUsVUFBVTtvQkFDdkIsSUFBSSxFQUFFLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLENBQUM7b0JBQ2pELFlBQVksRUFBRSxDQUFDLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxtQkFBbUIsRUFBRTtpQkFDcEUsQ0FBQztZQUNKLENBQUM7WUFDRCxXQUFXLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztRQUM3QixDQUFDO1FBRUQsT0FBTyxXQUFXLENBQUM7SUFDckIsQ0FBQztJQUVELG9CQUFvQjtJQUNwQjs7T0FFRztJQUNJLEtBQUssQ0FBQyxPQUFPO1FBQ2xCLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQ3JCLElBQUksQ0FBQyxXQUFXLENBQUMsd0JBQXdCLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pELENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsa0JBQWtCO1FBQzlCLDhCQUE4QjtRQUM5QixJQUFJLElBQUksQ0FBQyxJQUFJLEtBQUssWUFBWSxFQUFFLENBQUM7WUFDL0IsT0FBTztRQUNULENBQUM7UUFDRCxNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JDLE9BQU8sQ0FBQyxHQUFHLENBQUMsbUNBQW1DLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1FBQzVELElBQUksT0FBTyxHQUFHLENBQUMsQ0FBQztRQUNoQixhQUFhLEVBQUUsT0FBTyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDckMsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztZQUM5QixNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzFDLENBQUM7UUFDRCxNQUFNLE9BQU8sQ0FBQyxVQUFVLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3hDLE1BQU0sSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3JCLE9BQU8sQ0FBQyxHQUFHLENBQUMseUJBQXlCLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQ3hELENBQUM7SUFFTyxLQUFLLENBQUMsZ0JBQWdCO1FBQzVCLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxZQUFZLEVBQUUsQ0FBQztZQUMvQixPQUFPLENBQUMsR0FBRyxDQUFDLGlCQUFpQixDQUFDLENBQUM7WUFDL0IsTUFBTSxRQUFRLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDO2dCQUNyQyxNQUFNLEVBQUUsbUJBQW1CO2dCQUMzQixPQUFPLEVBQUU7b0JBQ1AsUUFBUSxFQUFFLElBQUksQ0FBQyxRQUFRO29CQUN2QixPQUFPLEVBQUUsT0FBTyxDQUFDLFNBQVMsQ0FBQyxHQUFHLEVBQUU7b0JBQ2hDLEtBQUssRUFBRSxTQUFTO29CQUNoQixXQUFXLEVBQUUsV0FBVztvQkFDeEIsU0FBUyxFQUFFLElBQUksQ0FBQyxTQUFTO2lCQUMxQjtnQkFDRCxRQUFRLEVBQUUsSUFBSTthQUNmLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxFQUFFO2dCQUNaLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO1lBQ3pCLENBQUMsQ0FBQyxDQUFDO1lBRUgsd0JBQXdCO1lBQ3hCLElBQUksUUFBUSxJQUFJLFFBQVEsQ0FBQyxRQUFRLElBQUksUUFBUSxDQUFDLFFBQVEsQ0FBQyxTQUFTLEtBQUssS0FBSyxFQUFFLENBQUM7Z0JBQzNFLElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO1lBQ3pCLENBQUM7aUJBQU0sQ0FBQztnQkFDTixJQUFJLENBQUMsa0JBQWtCLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1lBQ3ZDLENBQUM7WUFDRCxJQUFJLFFBQVEsSUFBSSxRQUFRLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQyxRQUFRLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQzVELElBQUksQ0FBQyxXQUFXLEVBQUUsQ0FBQztZQUNyQixDQUFDO1FBQ0gsQ0FBQztRQUNELElBQUksSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxrQkFBa0IsR0FBRyxLQUFLLEVBQUUsQ0FBQztZQUNqRCxPQUFPLENBQUMsR0FBRyxDQUFDLHNCQUFzQixJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztZQUNuRCxJQUFJLENBQUMsU0FBUyxHQUFHLEtBQUssQ0FBQztRQUN6QixDQUFDO0lBQ0gsQ0FBQztJQUVELDZCQUE2QjtJQUN0QixLQUFLLENBQUMsUUFBUSxDQUFDLE9BQVU7UUFDOUIsSUFBSSxDQUFDLHNCQUFzQixDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUMxQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDbkIsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsWUFBWSxFQUFFLENBQUM7SUFDbkQsQ0FBQztJQUVNLEtBQUssQ0FBQyxTQUFTO1FBQ3BCLElBQUksSUFBSSxDQUFDLHlCQUF5QixDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQzVDLHNCQUFzQjtRQUN4QixDQUFDO1FBQ0QsTUFBTSxJQUFJLENBQUMseUJBQXlCLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDcEQsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLHlCQUF5QixDQUFDLEtBQUssRUFBRSxDQUFDO1FBQzNELE9BQU8sV0FBVyxDQUFDO0lBQ3JCLENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsaUJBQWlCLENBQUMsaUJBQW9DLEVBQUUsaUJBQWlCLEdBQUcsSUFBSTtRQUMzRixNQUFNLE1BQU0sR0FBRyxpQkFBaUIsQ0FBQyxTQUFTLEVBQUUsQ0FBQztRQUM3QyxJQUFJLFlBQVksR0FBRyxLQUFLLENBQUM7UUFDekIsT0FBTSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3BCLE1BQU0sRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLEdBQUcsTUFBTSxNQUFNLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDNUMsSUFBRyxLQUFLLEVBQUUsQ0FBQztnQkFDVCxNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDN0IsQ0FBQztZQUNELFlBQVksR0FBRyxJQUFJLENBQUM7UUFDdEIsQ0FBQztRQUNELElBQUksaUJBQWlCLEVBQUUsQ0FBQztZQUN0QixNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekIsQ0FBQztJQUNILENBQUM7SUFFTSxLQUFLLENBQUMsZ0JBQWdCLENBQUMsaUJBQW9DO1FBQ2hFLE1BQU0sTUFBTSxHQUFHLGlCQUFpQixDQUFDLFNBQVMsRUFBRSxDQUFDO1FBQzdDLE9BQU0sSUFBSSxDQUFDLFNBQVMsSUFBSSxJQUFJLENBQUMseUJBQXlCLENBQUMsYUFBYSxFQUFFLEVBQUUsQ0FBQztZQUN2RSxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNyQyxJQUFJLEtBQUssS0FBSyxVQUFVLEVBQUUsQ0FBQztnQkFDekIsTUFBTSxDQUFDLFdBQVcsRUFBRSxDQUFDO2dCQUNyQixNQUFNLGlCQUFpQixDQUFDLEtBQUssRUFBRSxDQUFDO2dCQUNoQyxNQUFNO1lBQ1IsQ0FBQztZQUNELE1BQU0sTUFBTSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUM1QixDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxLQUFLLENBQUMsS0FBSyxDQUFDLGlCQUFpQixHQUFHLEtBQUs7UUFDMUMsSUFBSSxpQkFBaUIsRUFBRSxDQUFDO1lBQ3RCLElBQUksQ0FBQyxRQUFRLENBQUMsVUFBVSxDQUFDLENBQUM7UUFDNUIsQ0FBQztRQUNELElBQUksQ0FBQyxTQUFTLEdBQUcsS0FBSyxDQUFDO0lBQ3pCLENBQUM7Q0FDRiJ9