All files / src/InMemoryCache WarmCache.ts

96.66% Statements 29/30
84.61% Branches 11/13
100% Functions 8/8
96.66% Lines 29/30

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 9936x   36x                 36x   15x           15x   15x 10x         1x     27x   27x 20x     7x 7x   7x                       7x     36x 10x 10x 2x                       17x 17x 17x     17x   17x                     17x     17x 17x 13x   4x        
import { AsyncAction, InMemoryCache, Key } from './InMemoryCache';
import { Seconds } from '@cardano-sdk/core';
import NodeCache from 'node-cache';
 
interface WarmCacheItem<T> {
  asyncAction: AsyncAction<T>;
  value: T;
  ttl: number;
  updateTime: number;
}
 
export class WarmCache extends InMemoryCache {
  constructor(ttl: Seconds, expireCheckPeriod: Seconds) {
    const cache = new NodeCache({
      checkperiod: expireCheckPeriod,
      deleteOnExpire: true,
      stdTTL: ttl,
      useClones: false
    });
    super(ttl, cache);
 
    this.cache.on('expired', (key, value) => {
      this.#warm(key, value, this.cache);
    });
  }
 
  public mockCache(cache: NodeCache) {
    this.cache = cache;
  }
  public async get<T>(key: Key, asyncAction: AsyncAction<T>, ttl = this.ttlDefault): Promise<T> {
    const cachedValue: WarmCacheItem<T> | undefined = this.cache.get(key);
 
    if (cachedValue && cachedValue.value) {
      return cachedValue.value;
    }
 
    const updateTime = Date.now();
    const promise = this.#setWarmCacheItem<T>(key, asyncAction, ttl, this.cache, updateTime);
 
    this.cache.set(
      key,
      {
        asyncAction,
        ttl,
        updateTime,
        value: promise
        // value: _resolved ? Promise.resolve(value) : Promise.reject(value)
      } as WarmCacheItem<T>,
      ttl
    );
 
    return promise;
  }
 
  #warm<T>(key: string, item: WarmCacheItem<T> | undefined, cacheNode: NodeCache) {
    if (item && item.asyncAction) {
      this.#setWarmCacheItem(key, item.asyncAction, item.ttl, cacheNode, Date.now()).catch(
        () => 'rejected in the background'
      );
    }
  }
 
  async #setWarmCacheItem<T>(
    key: Key,
    asyncAction: AsyncAction<T>,
    ttl: number,
    cacheNode: NodeCache,
    updateTime: number
  ) {
    const handleValue = (value: T, _resolved = true) => {
      const item = this.cache.get(key) as WarmCacheItem<T>;
      Iif (item && item.updateTime > updateTime) {
        return item.value;
      }
      const promise = _resolved ? Promise.resolve(value) : Promise.reject(value);
 
      cacheNode.set(
        key,
        {
          asyncAction,
          ttl,
          updateTime,
          value: promise
          // value: _resolved ? Promise.resolve(value) : Promise.reject(value)
        } as WarmCacheItem<T>,
        ttl
      );
      return promise;
    };
 
    try {
      const value = await asyncAction();
      return handleValue(value, true);
    } catch (error) {
      return handleValue(error as T, false);
    }
  }
}