UNPKG

@safaricom-mxl/nextjs-turbo-redis-cache

Version:

Next.js redis cache handler

108 lines (99 loc) 3.57 kB
import type { SyncedMap } from "./SyncedMap"; import { debugVerbose } from "./utils/debug"; export class DeduplicatedRequestHandler< T extends (...args: any[]) => Promise<K>, K, > { private readonly inMemoryDeduplicationCache: SyncedMap<Promise<K>>; private readonly cachingTimeMs: number; private readonly fn: T; constructor( fn: T, cachingTimeMs: number, inMemoryDeduplicationCache: SyncedMap<Promise<K>> ) { this.fn = fn; this.cachingTimeMs = cachingTimeMs; this.inMemoryDeduplicationCache = inMemoryDeduplicationCache; } // Method to manually seed a result into the cache seedRequestReturn(key: string, value: K): void { const resultPromise = new Promise<K>((res) => res(value)); this.inMemoryDeduplicationCache.set(key, resultPromise); debugVerbose( "DeduplicatedRequestHandler.seedRequestReturn() seeded result ", key, (value as string).substring(0, 200) ); setTimeout(() => { this.inMemoryDeduplicationCache.delete(key); }, this.cachingTimeMs); } // Method to handle deduplicated requests deduplicatedFunction = (key: string): T => { debugVerbose( "DeduplicatedRequestHandler.deduplicatedFunction() called with", key ); //eslint-disable-next-line @typescript-eslint/no-this-alias const self = this; const dedupedFn = async (...args: any[]): Promise<K> => { // If there's already a pending request with the same key, return it debugVerbose( "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn called with", key ); if (self.inMemoryDeduplicationCache?.has(key)) { debugVerbose( "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ", key, "found key in inMemoryDeduplicationCache" ); const res = await self.inMemoryDeduplicationCache .get(key) ?.then((v) => structuredClone(v)); debugVerbose( "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ", key, "found key in inMemoryDeduplicationCache and served result from there", JSON.stringify(res).substring(0, 200) ); return res; } // If no pending request, call the original function and store the promise const promise = self.fn(...args); self.inMemoryDeduplicationCache.set(key, promise); debugVerbose( "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ", key, "did not found key in inMemoryDeduplicationCache. Setting it now and waiting for promise to resolve" ); try { const ts = performance.now(); const result = await promise; debugVerbose( "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ", key, "promise resolved (in ", performance.now() - ts, "ms). Returning result", JSON.stringify(result).substring(0, 200) ); return structuredClone(result); } finally { // Once the promise is resolved/rejected and caching timeout is over, remove it from the map setTimeout(() => { debugVerbose( "DeduplicatedRequestHandler.deduplicatedFunction().dedupedFn ", key, "deleting key from inMemoryDeduplicationCache after ", self.cachingTimeMs, "ms" ); self.inMemoryDeduplicationCache.delete(key); }, self.cachingTimeMs); } }; return dedupedFn as T; }; }