UNPKG

@cap-js-community/event-queue

Version:

An event queue that enables secure transactional processing of asynchronous and periodic events, featuring instant event processing with Redis Pub/Sub and load distribution across all application instances.

149 lines (141 loc) 4.62 kB
"use strict"; const DEFAULT_SEPARATOR = "##"; const DEFAULT_EXPIRATION_GAP = 60 * 1000; // 60 seconds class LazyCache { constructor({ separator = DEFAULT_SEPARATOR } = {}) { this.__data = Object.create(null); this.__separator = separator; } _separator() { return this.__separator; } _data() { return this.__data; } async _dataSettled() { return await Object.entries(this.__data).reduce(async (result, [key, value]) => { (await result)[key] = await value; return result; }, Promise.resolve({})); } _key(keyOrKeys) { return Array.isArray(keyOrKeys) ? keyOrKeys.join(this.__separator) : keyOrKeys; } has(keyOrKeys) { return Object.prototype.hasOwnProperty.call(this.__data, this._key(keyOrKeys)); } get(keyOrKeys) { return this.__data[this._key(keyOrKeys)]; } set(keyOrKeys, value) { this.__data[this._key(keyOrKeys)] = value; return this; } setCb(keyOrKeys, callback) { const resultOrPromise = callback(); return this.set( keyOrKeys, resultOrPromise instanceof Promise ? resultOrPromise.catch((err) => { this.delete(keyOrKeys); return Promise.reject(err); }) : resultOrPromise ); } getSetCb(keyOrKeys, callback) { const key = this._key(keyOrKeys); if (!this.has(key)) { this.setCb(key, callback); } return this.get(key); } count() { return Object.keys(this.__data).length; } delete(keyOrKeys) { Reflect.deleteProperty(this.__data, this._key(keyOrKeys)); return this; } clear() { this.__data = Object.create(null); } } class ExpiringLazyCache extends LazyCache { constructor({ separator = DEFAULT_SEPARATOR, expirationGap = DEFAULT_EXPIRATION_GAP } = {}) { super({ separator }); this.__expirationGap = expirationGap; } _expiringGap() { return this.__expirationGap; } _isValid(expirationTime, currentTime = Date.now()) { return expirationTime && currentTime <= expirationTime; } has(keyOrKeys, currentTime = Date.now()) { if (!super.has(keyOrKeys)) { return false; } const [expirationTime] = super.get(keyOrKeys) ?? []; return this._isValid(expirationTime, currentTime); } get(keyOrKeys, currentTime = Date.now()) { const [expirationTime, value] = super.get(keyOrKeys) ?? []; return this._isValid(expirationTime, currentTime) ? value : undefined; } // NOTE the expiration gap is substracted here, because we want to expire a // little earlier than necessary. // NOTE if the expiration is _less_ than the gap, the value is never valid, // we still need to call set, because we want getSetCb to always return // when the callback is used. set(keyOrKeys, expiration, value, currentTime = Date.now()) { return super.set(keyOrKeys, [currentTime + expiration - this.__expirationGap, value]); } static _extract(result, extractor) { if (!extractor) { return result; } const { expiration, expiry, value, result: extractedResult } = extractor(result); return [expiration ?? expiry, value ?? extractedResult]; } // NOTE callback can either return a pair [expiration, value] or use an extractor to extract the right values from // the callback's result setCb(keyOrKeys, callback, { currentTime = Date.now(), extractor } = {}) { const resultOrPromise = callback(); if (!(resultOrPromise instanceof Promise)) { const [expiration, value] = ExpiringLazyCache._extract(resultOrPromise, extractor); return this.set(keyOrKeys, expiration, value, currentTime); } return this.set( keyOrKeys, Infinity, resultOrPromise .catch((err) => { this.delete(keyOrKeys); return Promise.reject(err); }) .then((result) => { const [expiration, value] = ExpiringLazyCache._extract(result, extractor); this.set(keyOrKeys, expiration, value, currentTime); return value; }) ); } // NOTE callback can either return a pair [expiration, value] or use an extractor to extract the right values from // the callback's result getSetCb(keyOrKeys, callback, { currentTime = Date.now(), extractor } = {}) { const key = this._key(keyOrKeys); if (!this.has(key, currentTime) || !super.has(key)) { this.setCb(key, callback, { currentTime, extractor }); const [, value] = super.get(key); return value; } return this.get(key, currentTime); } } module.exports = { DEFAULT_EXPIRATION_GAP, DEFAULT_SEPARATOR, LazyCache, ExpiringLazyCache, };