Skip to main content
Deno 2 is finally here šŸŽ‰ļø
Learn more

Problem

Imagine that you need to make an API, one of the purposes of which, for example, is to give user data.

interface User {
  id: number;
  // ...
}

To do this, you would probably create a handler that fetches user data from the database.

const fetchUser = async (id: number): Promise<User> => {
  // ...
};

The following becomes obvious:

  • the number of queries for the same user can be high at some moment in time;
  • updates are generally rare (but not ruled out), so you get the same data.

Solution

Why not cache the data as soon as it is fetched? That’s exactly what this module does.

Usage

Import the DataCache class and pass in a function that will fetch data using a unique key, and a storage that will keep the results of fetching.

import { DataCache } from "https://deno.land/x/datacache@0.3.0/mod.ts";

const cache = new DataCache<number, User>(
    fetchUser,
    new Map();
);

Now let’s get the data of one of the users.

await cache.load(user_id); // data

If you then try to get the same user’s data again, you will get a cached result.

await cache.load(user_id); // cached data

But what does a cached result really mean? Let’s try to fetch the data of a non-existent user.

await cache.load(wrong_id); // error
await cache.load(wrong_id); // cached error

As you can see, not only the finite values are cached, but also the errors.

Features

Suppose the result is already cached. What if the data in the database needs to be updated? How to synchronize them with the cache? Use a special update method.

cache.update(unique_key, updated_data);

What if the record no longer exists in the database and is irrelevant? Use the delete method.

cache.delete(unique_key);

You can also use your own storage with more functionality and efficiency than Map.

import type {
  Result,
  Storage,
} from "https://deno.land/x/datacache@0.3.0/mod.ts";

interface Entry<Value> {
  value: Value;
  expires: number;
}

class TTLMap<Key, Value> implements Storage<Key, Value> {
  private readonly ttl: number;
  private readonly map: Map<Key, Entry<Value>>;

  constructor(ms: number) {
    this.ttl = ms;
    this.map = new Map();
  }

  get(key: Key): Value | undefined {
    const unit = this.map.get(key);
    if (unit) {
      if (unit.expires > Date.now()) {
        return unit.value;
      }
      this.map.delete(key);
    }
    return undefined;
  }

  set(key: Key, value: Value): void {
    const expires = Date.now() + this.ttl;
    this.map.set(key, { value, expires });
  }

  delete(key: Key): void {
    this.map.delete(key);
  }
}

const storage = new TTLMap<number, Result<User>>(60 * 60 * 1000);