UNPKG

@kovalenko/http-request-cache

Version:

TS decorator for caching logic of API calls.

105 lines (98 loc) 3.58 kB
import { Subject, merge, NEVER, startWith, switchMap, tap, shareReplay, filter, finalize } from 'rxjs'; class DefaultStorage { constructor() { this.storage = new Map(); } getItem(key) { return this.storage.get(key); } setItem(key, item) { this.storage.set(key, item); } deleteItem(key) { this.storage.delete(key); } } class RequestTimes { constructor() { this.storage = new Map(); } getItem(key) { return this.storage.get(key); } setItem(key, item) { this.storage.set(key, item); } deleteItem(key) { this.storage.delete(key); } } const HttpRequestCache = (optionsHandler) => { return (target, methodName, descriptor) => { if (!(descriptor?.value instanceof Function)) { throw Error(`'@HttpRequestCache' can be applied only to the class method which returns an Observable`); } const cacheKeyPrefix = `${target.constructor.name}_${methodName}`; const originalMethod = descriptor.value; const working = {}; let subscribers = 0; descriptor.value = function (...args) { const options = optionsHandler?.call(this, this, ...args); if (!options?.storage && !target._____storage_____) { target._____storage_____ = new DefaultStorage(); } if (options?.ttl && !target._____ttl_storage_____) { target._____ttl_storage_____ = new RequestTimes(); } const storage = options?.storage ?? target._____storage_____; const key = `${cacheKeyPrefix}_${JSON.stringify(args)}`; let ttl = undefined; if (options?.ttl) { ttl = target._____ttl_storage_____.getItem(key); if (!ttl) { ttl = { requestTime: Date.now(), subject: new Subject(), }; } else if (ttl.requestTime + options.ttl <= Date.now()) { working[key] = true; ttl.requestTime = Date.now(); ttl.subject.next(); } target._____ttl_storage_____.setItem(key, ttl); } const refreshOn = merge(options?.refreshOn ?? NEVER, ttl?.subject ?? NEVER); let observable = storage.getItem(key); if (!observable) { observable = refreshOn.pipe(startWith(true), switchMap(() => originalMethod.apply(this, [...args])), tap(() => { delete working[key]; }), shareReplay({ bufferSize: 1, refCount: options?.refCount ?? false, windowTime: options?.windowTime ?? Infinity, }), filter(() => { return !working[key]; }), finalize(() => { subscribers--; if (subscribers === 0 && options?.refCount) { storage.deleteItem(key); target._____ttl_storage_____?.deleteItem(key); } })); storage.setItem(key, observable); } subscribers++; return observable; }; return descriptor; }; }; /* * Public API Surface of http-request-cache */ /** * Generated bundle index. Do not edit. */ export { DefaultStorage, HttpRequestCache }; //# sourceMappingURL=kovalenko-http-request-cache.mjs.map