epicweb-dev / cachified

🤑 wrap virtually everything that can store by key to act as cache with ttl/max-age, stale-while-validate, parallel fetch protection and type-safety support
MIT License
916 stars 26 forks source link

How to deal with missing values with createBatch? #43

Closed rosswarren closed 1 year ago

rosswarren commented 1 year ago

Hi, thanks for creating this very useful library!

In the following example:

import type { CacheEntry } from 'cachified';
import LRUCache from 'lru-cache';
import { cachified, createBatch } from 'cachified';

type Entry = any;
const lru = new LRUCache<string, CacheEntry<string>>({ max: 1000 });

function getEntries(ids: number[]): Promise<Entry[]> {
  const batch = createBatch(getFreshValues);

  return Promise.all(
    ids.map((id) =>
      cachified({
        key: `entry-${id}`,
        cache: lru,
        getFreshValue: batch.add(id),
      }),
    ),
  );
}

async function getFreshValues(idsThatAreNotInCache: number[]): Entry[] {
  const res = await fetch(
    `https://example.org/api?ids=${idsThatAreNotInCache.join(',')}`,
  );
  const data = await res.json();

  return data as Entry[];
}

Imagine a scenario where some of the IDs that were requested in the fetch request do not return any result and are therefore missing from the data array. How should we deal with these missing values? Should we add null to the array or perhaps undefined?

Thanks!

Xiphe commented 1 year ago

That's an interesting question! And I would like to take some time for discussion and to think about this before presenting a possible "goto" solution.


Here's whats coming to mind. (I don't have time to fully validate this right now, will do in the coming days)

Adding null or undefined as option for the Entry is definitely the way to go. But currently that would also store these values in cache and only revalidate them once their TTL is over. If that's what you intend you're good to go 👍

A scenario that is not possible right now (I think) would be when you want to NOT store the missing null in cache and have getFreshValues try to get them again asap.

Xiphe commented 1 year ago

I've added/updated two sections about this to the readme and added the functionality to fine-tune cache-metadata in batch requests in the latest release v3.2.0.

see Fine-tuning cache metadata based on fresh values (This was already possible but not well documented, though this does not use createBatch the concept will also apply there)

I've also updated Batch requesting values to show the new onValue callback.


With this in mind here's what I'd do.

If you want to not cache empty values or cache them for a much shorter time do something like this:

const batch = createBatch((ids): Promise<(string | null)[]> => {
  /* ... */
});

const values: (string | null)[] = await Promise.all(
  ids.map((id) =>
    cachified({
      /* cache and other options... */
      key: `entry-${id}`,
      ttl: 60_000,
      getFreshValue: batch.add(id, ({ value, metadata }) => {
        if (value === null) {
          /* -1 disables caching, you could also just set to a shorter time */
          metadata.ttl = -1;
        }
      }),
    })
  )
);