@nasriya/cachify
Version:
A lightweight, extensible in-memory caching library for storing anything, with built-in TTL and customizable cache types.
136 lines (135 loc) • 5.59 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const stream_1 = require("stream");
const helpers_1 = __importDefault(require("./helpers"));
const DecryptStream_1 = __importDefault(require("./streams/DecryptStream"));
const StreamLinesParser_1 = __importDefault(require("./streams/StreamLinesParser"));
const restoreQueue_1 = __importDefault(require("./restoreQueue"));
class RestoreStream {
#_client;
#_streams;
#_userErrorHandler;
#_errorHandler = (err) => {
if (typeof this.#_userErrorHandler === 'function') {
this.#_userErrorHandler(err);
}
else {
throw err;
}
};
constructor(client) {
this.#_client = client;
this.#_streams = Object.freeze({
lineParser: new StreamLinesParser_1.default(),
decryptor: (() => {
const encryptionKey = helpers_1.default.getEncryptionKey();
if (encryptionKey) {
return new DecryptStream_1.default(encryptionKey);
}
})(),
handler: new stream_1.Writable({
decodeStrings: false, // Disable automatic string decoding
defaultEncoding: 'utf8',
write: (chunk, _enc, cb) => {
chunk = chunk.trim();
try {
// Only process RECORD lines
if (!chunk.startsWith('RECORD '))
return cb();
const firstSpace = chunk.indexOf(' ');
const recordData = chunk.slice(firstSpace + 1);
const data = JSON.parse(recordData);
const setAction = helpers_1.default.setRecord(data, this.#_client);
if (!setAction) {
return cb();
} // It means the record is expired
restoreQueue_1.default.addTask({
type: data.flavor,
action: async () => await setAction,
onReject(error) {
const err = new AggregateError([error, new Error(`Failed to restore record: ${data.key}`)]);
console.error(err);
},
});
cb();
}
catch (error) {
cb(error);
}
},
final: async (cb) => {
try {
await restoreQueue_1.default.untilComplete();
cb();
}
catch (error) {
cb(error);
}
}
})
});
this.#_helpers.setErrorHandlers();
}
#_helpers = {
setErrorHandlers: () => {
this.#_streams.decryptor?.on('error', this.#_errorHandler);
this.#_streams.lineParser.on('error', this.#_errorHandler);
this.#_streams.handler.on('error', this.#_errorHandler);
},
pipe: (input) => {
const { decryptor, lineParser, handler } = this.#_streams;
const hasDecryptor = decryptor instanceof DecryptStream_1.default;
let current = input;
if (hasDecryptor) {
current.pipe(decryptor);
current = decryptor;
}
current.pipe(lineParser).pipe(handler);
}
};
/**
* Sets the error handler for the persistence stream.
*
* If an error occurs on the readable or writable stream (i.e. when writing to the stream or when the stream is piped to a destination),
* then the handler will be called with the error as an argument.
* @param handler - The error handler to call when an error occurs.
* @throws {TypeError} If the provided handler is not a function.
* @since v1.0.0
*/
#onError(handler) {
if (typeof handler !== 'function') {
throw new TypeError(`The provided handler (${handler}) is not a function.`);
}
this.#_userErrorHandler = handler;
}
/**
* Pipes the internal readable stream to the provided writable stream.
*
* This method returns a promise that resolves when the writable stream is finished consuming data.
* If an error occurs on either the readable or writable stream, then the promise will reject with the error.
* The writable stream is destroyed when an error occurs.
*
* @param input - The readable stream to pipe the internal readable stream to.
* @returns A promise that resolves when the writable stream is finished consuming data.
* @since v1.0.0
*/
async streamFrom(input) {
return new Promise((resolve, reject) => {
const handleError = (err) => {
input.destroy();
this.#_streams.decryptor?.destroy();
this.#_streams.lineParser.destroy();
this.#_streams.handler.destroy();
reject(err);
};
this.#onError(handleError);
input.on('error', handleError);
this.#_streams.handler.on('finish', resolve);
this.#_helpers.pipe(input);
});
}
}
exports.default = RestoreStream;