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.
- ```
- npm install @epic-web/cachified
- ```
Install
- ```sh
- npm install @epic-web/cachified
- # yarn add @epic-web/cachified
- ```
Usage
- ```ts
- import { LRUCache } from 'lru-cache';
- import { cachified, CacheEntry } from '@epic-web/cachified';
- /* lru cache is not part of this package but a simple non-persistent cache */
- const lru = new LRUCache<string, CacheEntry>({ max: 1000 });
- function getUserById(userId: number) {
- return cachified({
- key: `user-${userId}`,
- cache: lru,
- async getFreshValue() {
- /* Normally we want to either use a type-safe API or `checkValue` but
- to keep this example simple we work with `any` */
- const response = await fetch(
- `https://jsonplaceholder.typicode.com/users/${userId}`,
- );
- return response.json();
- },
- /* 5 minutes until cache gets invalid
- * Optional, defaults to Infinity */
- ttl: 300_000,
- });
- }
- // Let's get through some calls of `getUserById`:
- console.log(await getUserById(1));
- // > logs the user with ID 1
- // Cache was empty, `getFreshValue` got invoked and fetched the user-data that
- // is now cached for 5 minutes
- // 2 minutes later
- console.log(await getUserById(1));
- // > logs the exact same user-data
- // Cache was filled an valid. `getFreshValue` was not invoked
- // 10 minutes later
- console.log(await getUserById(1));
- // > logs the user with ID 1 that might have updated fields
- // Cache timed out, `getFreshValue` got invoked to fetch a fresh copy of the user
- // that now replaces current cache entry and is cached for 5 minutes
- ```
Options
- ```ts
- interface CachifiedOptions<Value> {
- /**
- * Required
- *
- * The key this value is cached by
- * Must be unique for each value
- */
- key: string;
- /**
- * Required
- *
- * Cache implementation to use
- *
- * Must conform with signature
- * - set(key: string, value: object): void | Promise
- * - get(key: string): object | Promise
- * - delete(key: string): void | Promise
- */
- cache: Cache;
- /**
- * Required
- *
- * Function that is called when no valid value is in cache for given key
- * Basically what we would do if we wouldn't use a cache
- *
- * Can be async and must return fresh value or throw
- *
- * receives context object as argument
- * - context.metadata.ttl?: number
- * - context.metadata.swr?: number
- * - context.metadata.createdTime: number
- * - context.background: boolean
- */
- getFreshValue: GetFreshValue<Value>;
- /**
- * Time To Live; often also referred to as max age
- *
- * Amount of milliseconds the value should stay in cache
- * before we get a fresh one
- *
- * Setting any negative value will disable caching
- * Can be infinite
- *
- * Default: `Infinity`
- */
- ttl?: number;
- /**
- * Amount of milliseconds that a value with exceeded ttl is still returned
- * while a fresh value is refreshed in the background
- *
- * Should be positive, can be infinite
- *
- * Default: `0`
- */
- staleWhileRevalidate?: number;
- /**
- * Alias for staleWhileRevalidate
- */
- swr?: number;
- /**
- * Validator that checks every cached and fresh value to ensure type safety
- *
- * Can be a zod schema or a custom validator function
- *
- * Value considered ok when:
- * - zod schema.parseAsync succeeds
- * - validator returns
- * - true
- * - migrate(newValue)
- * - undefined
- * - null
- *
- * Value considered bad when:
- * - zod schema.parseAsync throws
- * - validator:
- * - returns false
- * - returns reason as string
- * - throws
- *
- * A validator function receives two arguments:
- * 1. the value
- * 2. a migrate callback, see https://github.com/epicweb-dev/cachified#migrating-values
- *
- * Default: `undefined` - no validation
- */
- checkValue?: CheckValue<Value> | Schema<Value, unknown>;
- /**
- * Set true to not even try reading the currently cached value
- *
- * Will write new value to cache even when cached value is
- * still valid.
- *
- * Default: `false`
- */
- forceFresh?: boolean;
- /**
- * Whether or not to fall back to cache when getting a forced fresh value
- * fails
- *
- * Can also be a positive number as the maximum age in milliseconds that a
- * fallback value might have
- *
- * Default: `Infinity`
- */
- fallbackToCache?: boolean | number;
- /**
- * Amount of time in milliseconds before revalidation of a stale
- * cache entry is started
- *
- * Must be positive and finite
- *
- * Default: `0`
- */
- staleRefreshTimeout?: number;
- /**
- * A reporter receives events during the runtime of
- * cachified and can be used for debugging and monitoring
- *
- * Default: `undefined` - no reporting
- */
- reporter?: CreateReporter<Value>;
- }
- ```
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
- ```ts
- import { LRUCache } from 'lru-cache';
- import { cachified, lruCacheAdapter, CacheEntry } from '@epic-web/cachified';
- const lru = new LRUCache<string, CacheEntry>({ max: 1000 });
- const cache = lruCacheAdapter(lru);
- await cachified({
- cache,
- key: 'user-1',
- getFreshValue() {
- return 'user@example.org';
- },
- });
- ```
Adapter for redis
- ```ts
- import { createClient } from 'redis';
- import { cachified, redisCacheAdapter } from '@epic-web/cachified';
- const redis = createClient({
- /* ...opts */
- });
- const cache = redisCacheAdapter(redis);
- await cachified({
- cache,
- key: 'user-1',
- getFreshValue() {
- return 'user@example.org';
- },
- });
- ```
Adapter for redis@3
- ```ts
- import { createClient } from 'redis';
- import { cachified, redis3CacheAdapter } from '@epic-web/cachified';
- const redis = createClient({
- /* ...opts */
- });
- const cache = redis3CacheAdapter(redis);
- const data = await cachified({
- cache,
- key: 'user-1',
- getFreshValue() {
- return 'user@example.org';
- },
- });
- ```