cachified

wrap virtually everything that can store by key to act as cache with ttl/ma...

README

@epic-web/cachified


A simple API to make your app faster.

Cachified allows you to cache values with support for time-to-live (ttl),stale-while-revalidate (swr), cache value validation, batching, and type-safety.

  1. ```
  2. npm install @epic-web/cachified
  3. ```

Install


  1. ```sh
  2. npm install @epic-web/cachified
  3. # yarn add @epic-web/cachified
  4. ```

Usage



  1. ```ts
  2. import { LRUCache } from 'lru-cache';
  3. import { cachified, CacheEntry } from '@epic-web/cachified';

  4. /* lru cache is not part of this package but a simple non-persistent cache */
  5. const lru = new LRUCache<string, CacheEntry>({ max: 1000 });

  6. function getUserById(userId: number) {
  7.   return cachified({
  8.     key: `user-${userId}`,
  9.     cache: lru,
  10.     async getFreshValue() {
  11.       /* Normally we want to either use a type-safe API or `checkValue` but
  12.          to keep this example simple we work with `any` */
  13.       const response = await fetch(
  14.         `https://jsonplaceholder.typicode.com/users/${userId}`,
  15.       );
  16.       return response.json();
  17.     },
  18.     /* 5 minutes until cache gets invalid
  19.      * Optional, defaults to Infinity */
  20.     ttl: 300_000,
  21.   });
  22. }

  23. // Let's get through some calls of `getUserById`:

  24. console.log(await getUserById(1));
  25. // > logs the user with ID 1
  26. // Cache was empty, `getFreshValue` got invoked and fetched the user-data that
  27. // is now cached for 5 minutes

  28. // 2 minutes later
  29. console.log(await getUserById(1));
  30. // > logs the exact same user-data
  31. // Cache was filled an valid. `getFreshValue` was not invoked

  32. // 10 minutes later
  33. console.log(await getUserById(1));
  34. // > logs the user with ID 1 that might have updated fields
  35. // Cache timed out, `getFreshValue` got invoked to fetch a fresh copy of the user
  36. // that now replaces current cache entry and is cached for 5 minutes
  37. ```

Options



  1. ```ts
  2. interface CachifiedOptions<Value> {
  3.   /**
  4.    * Required
  5.    *
  6.    * The key this value is cached by
  7.    * Must be unique for each value
  8.    */
  9.   key: string;
  10.   /**
  11.    * Required
  12.    *
  13.    * Cache implementation to use
  14.    *
  15.    * Must conform with signature
  16.    *  - set(key: string, value: object): void | Promise
  17.    *  - get(key: string): object | Promise
  18.    *  - delete(key: string): void | Promise
  19.    */
  20.   cache: Cache;
  21.   /**
  22.    * Required
  23.    *
  24.    * Function that is called when no valid value is in cache for given key
  25.    * Basically what we would do if we wouldn't use a cache
  26.    *
  27.    * Can be async and must return fresh value or throw
  28.    *
  29.    * receives context object as argument
  30.    *  - context.metadata.ttl?: number
  31.    *  - context.metadata.swr?: number
  32.    *  - context.metadata.createdTime: number
  33.    *  - context.background: boolean
  34.    */
  35.   getFreshValue: GetFreshValue<Value>;
  36.   /**
  37.    * Time To Live; often also referred to as max age
  38.    *
  39.    * Amount of milliseconds the value should stay in cache
  40.    * before we get a fresh one
  41.    *
  42.    * Setting any negative value will disable caching
  43.    * Can be infinite
  44.    *
  45.    * Default: `Infinity`
  46.    */
  47.   ttl?: number;
  48.   /**
  49.    * Amount of milliseconds that a value with exceeded ttl is still returned
  50.    * while a fresh value is refreshed in the background
  51.    *
  52.    * Should be positive, can be infinite
  53.    *
  54.    * Default: `0`
  55.    */
  56.   staleWhileRevalidate?: number;
  57.   /**
  58.    * Alias for staleWhileRevalidate
  59.    */
  60.   swr?: number;
  61.   /**
  62.    * Validator that checks every cached and fresh value to ensure type safety
  63.    *
  64.    * Can be a zod schema or a custom validator function
  65.    *
  66.    * Value considered ok when:
  67.    *  - zod schema.parseAsync succeeds
  68.    *  - validator returns
  69.    *    - true
  70.    *    - migrate(newValue)
  71.    *    - undefined
  72.    *    - null
  73.    *
  74.    * Value considered bad when:
  75.    *  - zod schema.parseAsync throws
  76.    *  - validator:
  77.    *    - returns false
  78.    *    - returns reason as string
  79.    *    - throws
  80.    *
  81.    * A validator function receives two arguments:
  82.    *  1. the value
  83.    *  2. a migrate callback, see https://github.com/epicweb-dev/cachified#migrating-values
  84.    *
  85.    * Default: `undefined` - no validation
  86.    */
  87.   checkValue?: CheckValue<Value> | Schema<Value, unknown>;
  88.   /**
  89.    * Set true to not even try reading the currently cached value
  90.    *
  91.    * Will write new value to cache even when cached value is
  92.    * still valid.
  93.    *
  94.    * Default: `false`
  95.    */
  96.   forceFresh?: boolean;
  97.   /**
  98.    * Whether or not to fall back to cache when getting a forced fresh value
  99.    * fails
  100.    *
  101.    * Can also be a positive number as the maximum age in milliseconds that a
  102.    * fallback value might have
  103.    *
  104.    * Default: `Infinity`
  105.    */
  106.   fallbackToCache?: boolean | number;
  107.   /**
  108.    * Amount of time in milliseconds before revalidation of a stale
  109.    * cache entry is started
  110.    *
  111.    * Must be positive and finite
  112.    *
  113.    * Default: `0`
  114.    */
  115.   staleRefreshTimeout?: number;
  116.   /**
  117.    * A reporter receives events during the runtime of
  118.    * cachified and can be used for debugging and monitoring
  119.    *
  120.    * Default: `undefined` - no reporting
  121.    */
  122.   reporter?: CreateReporter<Value>;
  123. }
  124. ```

  125. Adapters


    There are some build-in adapters for common caches, using them makes sure
    the used caches cleanup outdated values themselves.

    Adapter for lru-cache



    1. ```ts
    2. import { LRUCache } from 'lru-cache';
    3. import { cachified, lruCacheAdapter, CacheEntry } from '@epic-web/cachified';

    4. const lru = new LRUCache<string, CacheEntry>({ max: 1000 });
    5. const cache = lruCacheAdapter(lru);

    6. await cachified({
    7.   cache,
    8.   key: 'user-1',
    9.   getFreshValue() {
    10.     return 'user@example.org';
    11.   },
    12. });
    13. ```

    Adapter for redis



    1. ```ts
    2. import { createClient } from 'redis';
    3. import { cachified, redisCacheAdapter } from '@epic-web/cachified';

    4. const redis = createClient({
    5.   /* ...opts */
    6. });
    7. const cache = redisCacheAdapter(redis);

    8. await cachified({
    9.   cache,
    10.   key: 'user-1',
    11.   getFreshValue() {
    12.     return 'user@example.org';
    13.   },
    14. });
    15. ```

    Adapter for redis@3



    1. ```ts
    2. import { createClient } from 'redis';
    3. import { cachified, redis3CacheAdapter } from '@epic-web/cachified';

    4. const redis = createClient({
    5.   /* ...opts */
    6. });
    7. const cache = redis3CacheAdapter(redis);

    8. const data = await cachified({
    9.   cache,
    10.   key: 'user-1',
    11.   getFreshValue() {
    12.     return 'user@example.org';
    13.   },
    14. });
    15. ```