### Install @epic-web/cachified Source: https://github.com/epicweb-dev/cachified/blob/main/README.md Install the @epic-web/cachified package using npm or yarn. ```sh npm install @epic-web/cachified # yarn add @epic-web/cachified ``` -------------------------------- ### Example with Custom Reporter Formatting Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Demonstrates how to use the verbose reporter with custom options for duration formatting, logger, and performance timing. ```typescript await cachified( { /* options */ }, verboseReporter({ formatDuration: (ms) => { if (ms < 1000) return `${ms}ms`; return `${(ms / 1000).toFixed(2)}s`; }, logger: myLogger, performance: { now: () => myCustomTimer() } }) ); ``` -------------------------------- ### Basic Caching with Cachified Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/README.md Demonstrates basic caching using a key, cache object, TTL, and a function to get fresh values. ```typescript const user = await cachified({ key: 'user-1', cache: new Map(), ttl: 5 * 60 * 1000, async getFreshValue() { return fetch('/api/users/1').then(r => r.json()); } }); ``` -------------------------------- ### Basic Soft Purge Example Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/softPurge.md Demonstrates how to use softPurge to mark a cache entry as stale. The next call to `cachified` will serve the stale value and trigger a background refresh. ```typescript import { cachified, softPurge } from '@epic-web/cachified'; const cache = new Map(); async function getUser(userId: number) { return cachified({ cache, key: `user-${userId}`, ttl: 5 * 60 * 1000, // 5 minutes async getFreshValue() { const response = await fetch(`/api/users/${userId}`); return response.json(); }, }); } // User calls console.log(await getUser(1)); // Fresh from API // ... later ... console.log(await getUser(1)); // From cache // Event triggers that the user data might be stale await softPurge({ cache, key: 'user-1' }); // Still serves cached value, but triggers background refresh console.log(await getUser(1)); // Cached (stale) // ... next call serves the refreshed value console.log(await getUser(1)); // Fresh from background refresh ``` -------------------------------- ### Using verboseReporter for Logging Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/reporters.md This example demonstrates how to use the built-in verboseReporter with cachified to log cache operations. Ensure cachified and verboseReporter are imported. ```typescript import { cachified, verboseReporter } from '@epic-web/cachified'; const cache = new Map(); const user = await cachified( { cache, key: 'user-1', async getFreshValue() { const response = await fetch('/api/users/1'); return response.json(); }, }, verboseReporter(), ); // Logs: // > Updated the cache value for user-1. Getting a fresh value for this took 123ms. Caching for 5m in Map. ``` -------------------------------- ### Creating a Tracing Reporter Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/reporters.md This example defines and uses a tracing reporter that logs all cache events with their context, including the cache key and event name. This is useful for debugging and understanding cache flow. ```typescript function tracingReporter() { return (context) => { return (event) => { console.log(`[${context.key}] ${event.name}`, event); }; }; } const user = await cachified( { cache, key: 'user-1', async getFreshValue() { return { id: 1 }; }, }, tracingReporter(), ); // Output: // [user-1] getCachedValueStart {} // [user-1] getCachedValueRead { entry: undefined } // [user-1] getCachedValueEmpty {} // [user-1] getFreshValueStart {} // [user-1] getFreshValueSuccess { value: { id: 1 } } // [user-1] writeFreshValueSuccess { metadata: {...}, written: true, migrated: false } // [user-1] done { value: { id: 1 } } ``` -------------------------------- ### Merging Verbose and Custom Reporters Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/reporters.md This example demonstrates how to combine the built-in verboseReporter with a custom reporter using mergeReporters. This allows for simultaneous logging and custom event handling. ```typescript import { cachified, verboseReporter, mergeReporters } from '@epic-web/cachified'; function customReporter() { return () => (event) => { // Custom logic }; } const user = await cachified( { cache, key: 'user-1', async getFreshValue() { const response = await fetch('/api/users/1'); return response.json(); }, }, mergeReporters( verboseReporter(), customReporter(), ), ); ``` -------------------------------- ### Handling Cache.get() Errors Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/errors.md Demonstrates how to use a try-catch block to handle errors thrown by the cache's get() method. This ensures that if the cache fails, the system can fall back to fetching a fresh value. ```typescript const redisCache = { async get(key) { // Redis connection lost throw new Error('Redis connection refused'); }, // ... }; try { const user = await cachified({ key: 'user-1', cache: redisCache, async getFreshValue() { return fetch('/api/users/1').then(r => r.json()); } }); // Falls back to fresh value if getFreshValue succeeds } catch (error) { console.error('Failed to get user:', error); } ``` -------------------------------- ### Creating a Custom Metrics Reporter Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/reporters.md This example shows how to create a custom reporter function that collects cache metrics like hits, misses, and validation errors. The reporter logs these metrics when the 'done' event is triggered. ```typescript function metricsReporter() { return (context) => { const metrics = { cacheHits: 0, cacheMisses: 0, validationErrors: 0, totalTime: 0, }; const startTime = Date.now(); return (event) => { switch (event.name) { case 'getCachedValueSuccess': metrics.cacheHits++; break; case 'getCachedValueEmpty': metrics.cacheMisses++; break; case 'checkFreshValueErrorObj': case 'checkCachedValueErrorObj': metrics.validationErrors++; break; case 'done': metrics.totalTime = Date.now() - startTime; console.log(`Cache metrics for ${context.key}:`, metrics); break; } }; }; } const user = await cachified( { cache, key: 'user-1', async getFreshValue() { const response = await fetch('/api/users/1'); return response.json(); }, }, metricsReporter(), ); ``` -------------------------------- ### Soft Purge with Custom Stale Window Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/softPurge.md Shows how to override the default stale-while-revalidate duration when performing a soft purge. This example sets a custom SWR of 1 minute. ```typescript // Cache entry has TTL 5 minutes, SWR 10 minutes // After 2 minutes, soft purge with 1 minute SWR override await softPurge({ cache, key: 'user-1', staleWhileRevalidate: 60_000, // 1 minute }); // Value is served as stale for 1 minute + 2 minutes (elapsed) = 3 minutes // Then it's considered fully expired ``` -------------------------------- ### Handling getFreshValue Errors Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/errors.md This example shows how to gracefully handle errors that occur within the `getFreshValue` function. It includes a fallback mechanism to use cached data if available and within the specified `fallbackToCache` duration. ```typescript try { const user = await cachified({ key: 'user-1', cache, fallbackToCache: 5 * 60 * 1000, // Fall back to cache up to 5 min old async getFreshValue() { const response = await fetch('/api/users/1'); if (!response.ok) throw new Error(`API error: ${response.status}`); return response.json(); } }); } catch (error) { console.error('Failed to get user:', error); } ``` -------------------------------- ### Handling Cache.set() Errors Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/errors.md Illustrates a scenario where the cache's set() method throws an error, such as 'Cache storage full'. The example shows that the fresh value is still returned to the caller, but subsequent cache operations might not benefit from this write. ```typescript const cache = { get(key) { /* ... */ }, async set(key, value) { // Disk full, quota exceeded, etc. throw new Error('Cache storage full'); }, delete(key) { /* ... */ } }; const user = await cachified({ key: 'user-1', cache, async getFreshValue() { return fetch('/api/users/1').then(r => r.json()); } }); // user is still returned even if cache.set() failed ``` -------------------------------- ### Utilities and Helpers Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/INDEX.md Documentation for utility functions that assist with cache management, reporting, and validation. ```APIDOC ## reporters.md ### Description Documentation for the event reporting system, allowing for monitoring and logging of cache operations. ### Reporter Types - **Reporter**: Interface for custom reporters. - **CreateReporter**: Type for functions that create reporters. ### Event Catalog - **Fresh Value Events**: `start`, `success`, `error`, `fallback` - **Cached Value Events**: `read`, `empty`, `outdated`, `success` - **Validation Events**: `fresh`, `cached` - **Write Events**: `success`, `error` - **Background Refresh Events**: `start`, `success`, `error` - **Completion Event** ### Built-in Reporter - **verboseReporter**: A built-in logger with configurable options. ### Utilities - **mergeReporters**: Utility to combine multiple reporters. ### Examples - Custom reporter implementation - Using `verboseReporter` - Combining reporters - Metrics collection and tracing patterns ### Source Location `src/reporter.ts` ``` ```APIDOC ## cache-helpers.md ### Description Provides utility functions for manual cache operations, including entry creation, validation, and expiration checks. ### Functions - **`createCacheEntry(value, metadata?)`**: Creates a cache entry object with the correct structure. - **`assertCacheEntry(entry, key?)`**: Validates the structure of a cache entry, throwing detailed validation errors. - **`isExpired(metadata)`**: Checks if a cache entry has expired based on its metadata. - **`shouldRefresh(metadata)`**: Deprecated function for checking expiration. - **`totalTtl(metadata?)`**: Calculates the total Time-To-Live (TTL) for a cache entry. - **`staleWhileRevalidate(metadata)`**: Extracts the SWR duration from cache entry metadata. ### Examples - Creating and validating cache entries. - Checking entry expiration status. - Calculating total TTL. ### Error Handling - Detailed validation error messages are provided by `assertCacheEntry`. ### Source Locations - `src/common.ts` - `src/isExpired.ts` - `src/assertCacheEntry.ts` ``` -------------------------------- ### Preventing Batch Add After Submission Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/errors.md Shows an example where `batch.add()` is called after the batch has already been submitted. This will throw an error indicating that no more items can be added to a submitted batch. ```typescript const batch = createBatch(async (ids) => { return fetch(`/api/users?ids=${ids.join(',')}`).then(r => r.json()); }); // Start submissions const promise = cachified({ key: 'user-1', cache, getFreshValue: batch.add(1) // First request triggers submission }); // Too late to add more try { cachified({ key: 'user-2', cache, getFreshValue: batch.add(2) // Error! }); } catch (error) { // Error: Can not add to batch after submission } ``` -------------------------------- ### Using Multiple Independent Batches Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/createBatch.md Illustrates how to create and manage multiple independent batches for different data types (e.g., users and posts) within the same application. Each batch is configured with its own fetcher function. ```typescript const userBatch = createBatch(async (ids) => { const response = await fetch(`/api/users?ids=${ids.join(',')}`); return response.json(); }); const postBatch = createBatch(async (ids) => { const response = await fetch(`/api/posts?ids=${ids.join(',')}`); return response.json(); }); const [users, posts] = await Promise.all([ Promise.all( userIds.map((id) => cachified({ key: `user-${id}`, cache, getFreshValue: userBatch.add(id), }), ), ), Promise.all( postIds.map((id) => cachified({ key: `post-${id}`, cache, getFreshValue: postBatch.add(id), }), ), ), ]); ``` -------------------------------- ### Get Data from Cache Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/cachified.md Retrieves data associated with a given key from the cache. If the data is not found, the provided getter function is executed to fetch and store the data. ```typescript const data = await cache.get('my-key', async () => { // Fetch data from an external source return await fetch('https://example.com/data').then(res => res.json()); }); ``` -------------------------------- ### Soft Purge Triggered by Webhook Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/softPurge.md An example of integrating softPurge with an Express.js webhook to invalidate user data when an update occurs. This ensures cached data is refreshed promptly. ```typescript import express from 'express'; import { cachified, softPurge } from '@epic-web/cachified'; const cache = new Map(); const app = express(); app.post('/webhooks/user-updated', async (req, res) => { const userId = req.body.id; // Soft purge the cached user data await softPurge({ cache, key: `user-${userId}`, }); res.status(200).send('OK'); }); app.get('/users/:id', async (req, res) => { const user = await cachified({ cache, key: `user-${req.params.id}`, ttl: 10 * 60 * 1000, async getFreshValue() { const response = await fetch(`https://api.example.com/users/${req.params.id}`); return response.json(); }, }); res.json(user); }); ``` -------------------------------- ### configure Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/configure.md Creates a pre-configured version of `cachified` with default options, reducing repetition when using the same cache or options across multiple calls. The returned function merges default options with any options passed on each call, with passed options taking precedence. ```APIDOC ## configure ### Description Creates a pre-configured version of `cachified` with default options, reducing repetition when using the same cache or options across multiple calls. The returned function merges default options with any options passed on each call, with passed options taking precedence. ### Function Signature ```typescript export function configure>>(defaultOptions: Opts, defaultReporter?: CreateReporter): (options: PartialOptions, keyof Opts>, reporter?: CreateReporter) => Promise; ``` ### Parameters #### Path Parameters This function does not have path parameters. #### Query Parameters This function does not have query parameters. #### Request Body This function does not have a request body. ### Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | defaultOptions | `Partial>` | Yes | — | Default options to apply to all cachified calls (cache, ttl, checkValue, etc.) | | defaultReporter | `CreateReporter` | No | undefined | Default reporter factory to apply to all calls | ### Return Value Returns a function with the same signature as `cachified`, but with any options in `defaultOptions` now optional in subsequent calls. Options passed to the returned function override defaults. ### Examples #### Configure with Shared Cache ```typescript import { configure, LRUCache } from '@epic-web/cachified'; const cache = new LRUCache({ max: 1000 }); const cachified = configure({ cache, }); const user = await cachified({ key: 'user-1', async getFreshValue() { const response = await fetch('/api/users/1'); return response.json(); }, }); const post = await cachified({ key: 'post-1', async getFreshValue() { const response = await fetch('/api/posts/1'); return response.json(); }, }); ``` #### Configure with Default TTL and SWR ```typescript const cachified = configure({ cache: new Map(), ttl: 5 * 60 * 1000, // 5 minutes staleWhileRevalidate: 10 * 60 * 1000, // 10 minutes }); // All calls now use these defaults const user = await cachified({ key: 'user-1', async getFreshValue() { // ... }, }); ``` #### Configure with Validation Schema ```typescript import z from 'zod'; const userSchema = z.object({ id: z.number(), email: z.string().email(), }); const cachified = configure({ cache: new Map(), checkValue: userSchema, }); const user = await cachified({ key: 'user-1', async getFreshValue() { const response = await fetch('/api/users/1'); return response.json(); }, }); ``` #### Configure with Reporting ```typescript import { verboseReporter } from '@epic-web/cachified'; const cachified = configure( { cache: new Map(), }, verboseReporter(), ); // All calls now log cache events const user = await cachified({ key: 'user-1', async getFreshValue() { // ... }, }); ``` #### Override Defaults in Individual Calls ```typescript const cachified = configure({ cache: new Map(), ttl: 5 * 60 * 1000, }); // Use default TTL const user = await cachified({ key: 'user-1', async getFreshValue() { // ... }, }); // Override with longer TTL const staticPage = await cachified({ key: 'page-home', ttl: 60 * 60 * 1000, // 1 hour async getFreshValue() { // ... }, }); ``` ``` -------------------------------- ### Manual Batch Submission Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/createBatch.md Demonstrates how to use createBatch with `autoSubmit: false` to manually control when the batch is submitted using `batch.submit()`. This allows for more control over the execution timing. ```typescript const batch = createBatch(fetchUsers, false); // autoSubmit: false const promises = ids.map((id) => cachified({ key: `user-${id}`, cache, getFreshValue: batch.add(id), ttl: 60_000, }), ); // Later, when ready to execute the batch await batch.submit(); const results = await Promise.all(promises); ``` -------------------------------- ### Cache Interface Definition Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/types.md Defines the contract that all cache implementations must adhere to. It includes methods for getting, setting, and deleting cache entries. Implementations can be synchronous or asynchronous. ```typescript export interface Cache { name?: string; get: (key: string) => Eventually>; set: (key: string, value: CacheEntry) => unknown | Promise; delete: (key: string) => unknown | Promise; } ``` -------------------------------- ### Pre-configure Cachified Instance Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/MODULES.md Use `configure` to create a cachified instance with default options, making those options optional in subsequent calls. ```typescript import { configure } from 'cachified'; const cachified = configure({ cache, ttl: 5000 }); // Now ttl is optional in all calls const value = await cachified({ key: 'x', getFreshValue: () => 'y' }); ``` -------------------------------- ### Redis Cache Implementation Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Provides a persistent, distributed cache implementation using Redis. This cache supports asynchronous operations for getting, setting, and deleting cache entries. ```typescript import redis from 'redis'; const redisClient = redis.createClient(); const cache = { async get(key) { const json = await redisClient.get(key); return json ? JSON.parse(json) : undefined; }, async set(key, value) { await redisClient.set(key, JSON.stringify(value)); }, async delete(key) { await redisClient.del(key); } }; cachified({ key: 'user-1', cache, // ... }); ``` -------------------------------- ### Real-time Data with SWR Configuration Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Configure a cache for near-real-time data using a very short TTL and a longer stale-while-revalidate window. This pattern reduces blocking on frequent requests. ```typescript { key: `message-${chatId}`, cache, ttl: 500, // Very short TTL (500ms) staleWhileRevalidate: 5000, // But stale window is longer async getFreshValue() { return fetchLatestMessages(chatId); } } ``` -------------------------------- ### Pre-configuring Cachified with Defaults Source: https://github.com/epicweb-dev/cachified/blob/main/README.md Create reusable cachified instances with default configurations, such as a specific cache implementation like LRUCache. ```typescript import { configure } from '@epic-web/cachified'; import { LRUCache } from 'lru-cache'; /* lruCachified now has a default cache */ const lruCachified = configure({ cache: new LRUCache({ max: 1000 }), }); const value = await lruCachified({ key: 'user-1', getFreshValue: async () => 'ONE', }); ``` -------------------------------- ### Core Functions Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/INDEX.md Documentation for the main caching functions and utilities that users can directly invoke. ```APIDOC ## cachified.md ### Description Provides the main caching functionality, handling cache hits, misses, stale-while-revalidate (SWR), and forced refreshes. ### Function Signature `cachified(options)` ### Parameters - **options** (CachifiedOptions) - Required - Configuration object for the cachified function. - `key` (string | undefined) - The cache key. - `getFreshValue` (GetFreshValue) - Function to fetch a fresh value when a cache miss occurs or a refresh is needed. - `options` (CachifiedOptions) - Additional options like `ttl`, `staleWhileRevalidate`, `reporter`, `forceFresh`, `checkFreshness`. ### Behavior - Handles cache hits and misses. - Implements stale-while-revalidate (SWR) for background refreshes. - Supports forced fresh retrieval of values. - Includes a de-duplication mechanism for concurrent requests. ### Examples [Multiple usage examples are available in the source documentation.] ### Source Location `src/cachified.ts` ``` ```APIDOC ## configure.md ### Description A utility function to create pre-configured instances of `cachified` with default options. ### Function Signature `configure(defaultOptions)` ### Parameters - **defaultOptions** (CachifiedOptions) - Required - The default options to apply to all instances created by this configuration. ### Behavior - Returns a function that accepts a `key` and `getFreshValue` (and optional overrides) to create a `cachified` instance. - Simplifies setting up shared cache configurations, default TTLs, validation schemas, and reporters. ### Examples - Shared cache instance - Default TTL configuration - Validation schema setup - Reporter configuration ### Source Location `src/configure.ts` ``` ```APIDOC ## createBatch.md ### Description Facilitates batch loading of multiple cache entries, optimizing data fetching by de-duplicating requests. ### Function Signatures - `createBatch(options)` - `createBatch(options, autoSubmit)` ### Parameters - **options** (object) - Required - Configuration object. - `getFreshValues` (GetFreshValues) - Callback function to fetch multiple fresh values. - `cache` (Cache) - The cache instance to use. - `keys` (string[]) - An array of cache keys to fetch. - `options` (CachifiedOptions) - Optional additional options. - **autoSubmit** (boolean) - Optional - If true, the batch is automatically submitted upon creation. ### Behavior - Supports both auto-submit and manual submit modes. - Handles de-duplication of requests within a batch. - Validates the length of the returned values against the requested keys. ### Examples - Basic batch creation - Batch with callbacks - Manual submit mode - Handling multiple batches ### Source Location `src/createBatch.ts` ``` ```APIDOC ## softPurge.md ### Description Performs a soft invalidation of a cache entry, extending its stale-while-revalidate (SWR) window without immediately removing it. ### Function Signature `softPurge(options)` ### Parameters - **options** (object) - Required - Configuration object. - `cache` (Cache) - The cache instance. - `key` (string) - The key of the cache entry to soft purge. - `staleWhileRevalidate` (number) - Optional - Overrides the default SWR window for this specific purge. ### Behavior - Extends the SWR window of a cache entry. - Allows for graceful cache updates without immediate data loss. - Compares soft purge behavior with hard purge. ### Examples - Basic soft purge - Custom stale window override - Webhook invalidation scenario ### Source Location `src/softPurge.ts` ``` -------------------------------- ### createCacheEntry Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/cache-helpers.md Creates a properly formatted cache entry with metadata, including time-to-live and stale-while-revalidate options. ```APIDOC ## createCacheEntry ### Description Creates a properly formatted cache entry with metadata. ### Function Signature ```typescript export function createCacheEntry(value: Value, metadata?: Partial>): CacheEntry; ``` ### Parameters #### Parameters - **value** (Value) - Required - The value to cache - **metadata** (Partial) - Optional - Optional metadata (ttl, swr, createdTime, traceId) ### Return Value ```typescript interface CacheEntry { metadata: CacheMetadata; value: Value; } ``` ### Metadata Fields - **ttl** (number | null) - Optional - Time-to-live in milliseconds. `null` means infinite - **swr** (number | null) - Optional - Stale-while-revalidate in milliseconds. `null` means Infinity - **createdTime** (number) - Optional - Timestamp when entry was created - **traceId** (any) - Optional - Optional debugging trace ID ### Examples ```typescript import { createCacheEntry } from '@epic-web/cachified'; const cache = new Map(); // Simple entry with infinite TTL cache.set( 'user-1', createCacheEntry({ id: 1, name: 'Alice' }), ); // Entry with TTL and SWR cache.set( 'user-2', createCacheEntry( { id: 2, name: 'Bob' }, { ttl: 5 * 60 * 1000, swr: 10 * 60 * 1000 }, ), ); // Entry with custom createdTime cache.set( 'user-3', createCacheEntry( { id: 3, name: 'Charlie' }, { ttl: 5 * 60 * 1000, createdTime: Date.now() - 2 * 60 * 1000 }, ), ); // Entry with trace ID for debugging cache.set( 'user-4', createCacheEntry( { id: 4, name: 'Diana' }, { ttl: 5 * 60 * 1000, traceId: 'request-abc123' }, ), ); ``` ``` -------------------------------- ### Create Cache Entry with Metadata Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/cache-helpers.md Use `createCacheEntry` to manually construct cache entries. You can specify TTL, SWR, creation time, and trace IDs for advanced cache management. Ensure the `@epic-web/cachified` library is imported. ```typescript import { createCacheEntry } from '@epic-web/cachified'; const cache = new Map(); // Simple entry with infinite TTL cache.set( 'user-1', createCacheEntry({ id: 1, name: 'Alice' }), ); // Entry with TTL and SWR cache.set( 'user-2', createCacheEntry( { id: 2, name: 'Bob' }, { ttl: 5 * 60 * 1000, swr: 10 * 60 * 1000 }, ), ); // Entry with custom createdTime cache.set( 'user-3', createCacheEntry( { id: 3, name: 'Charlie' }, { ttl: 5 * 60 * 1000, createdTime: Date.now() - 2 * 60 * 1000 }, ), ); // Entry with trace ID for debugging cache.set( 'user-4', createCacheEntry( { id: 4, name: 'Diana' }, { ttl: 5 * 60 * 1000, traceId: 'request-abc123' }, ), ); ``` -------------------------------- ### Custom Validator Returning String Reason Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/errors.md Implement a custom validator that returns a string to provide a specific reason for invalidity. This triggers `checkFreshValueErrorObj` or `checkCachedValueErrorObj` events with the provided reason. This example also demonstrates how to use a reporter to log these validation errors. ```typescript const user = await cachified({ key: 'user-1', cache, checkValue(value) { if (typeof value.id !== 'number') { return `User id must be number, got ${typeof value.id}`; } return true; }, async getFreshValue() { return fetch('/api/users/1').then(r => r.json()); } }, reporter((context) => { return (event) => { if (event.name === 'checkFreshValueErrorObj') { console.error(`Validation failed: ${event.reason}`); } }; }) ``` -------------------------------- ### Basic Batch Usage with Cachified Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/createBatch.md Demonstrates how to use createBatch to fetch multiple user IDs in a single API call. Subsequent calls will only fetch missing or expired data. ```typescript import { cachified, createBatch } from '@epic-web/cachified'; const cache = new Map(); async function fetchUsers(ids: number[]) { const response = await fetch(`/api/users?ids=${ids.join(',')}`); return response.json(); // Returns array of user objects } function getUsersWithId(ids: number[]) { const batch = createBatch(fetchUsers); return Promise.all( ids.map((id) => cachified({ key: `user-${id}`, cache, getFreshValue: batch.add(id), ttl: 60_000, }), ), ); } // First call: fetches IDs [1, 2] in single batch API call console.log(await getUsersWithId([1, 2])); // Later call: user 2 is cached, only fetches ID [3] console.log(await getUsersWithId([2, 3])); ``` -------------------------------- ### Schema Validation Errors with Zod Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/errors.md Utilize Zod for schema validation within `checkValue`. If validation fails, `checkFreshValueErrorObj` or `checkCachedValueErrorObj` events are reported with an `issues` array detailing the validation errors. This example shows how to process these issues to log specific error messages. ```typescript import z from 'zod'; const userSchema = z.object({ id: z.number(), email: z.string().email() }); const user = await cachified({ key: 'user-1', cache, checkValue: userSchema, async getFreshValue() { return fetch('/api/users/1').then(r => r.json()); } }, reporter((context) => { return (event) => { if (event.name === 'checkFreshValueErrorObj') { const issues = event.reason; if (Array.isArray(issues)) { issues.forEach(issue => { console.error(`Validation error at ${issue.path?.join('.')}: ${issue.message}`); }); } } }; }) ``` -------------------------------- ### Cachified Import Paths Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/README.md Lists all available exports from the main entry point of the Cachified library, including the main function, configuration utilities, helper functions, reporters, and types. ```typescript import { // Main function cachified, // Pre-configuration configure, // Utilities createBatch, softPurge, isExpired, assertCacheEntry, createCacheEntry, totalTtl, staleWhileRevalidate, // Reporters verboseReporter, mergeReporters, // Types CachifiedOptions, CachifiedOptionsWithSchema, Cache, CacheEntry, CacheMetadata, GetFreshValue, GetFreshValueContext, CheckValue, // Events CacheEvent, Reporter, CreateReporter, } from '@epic-web/cachified'; ``` -------------------------------- ### Collect Cache Performance Metrics Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Implement a metrics reporter to track cache hits, misses, and errors. This helps in understanding cache performance and identifying potential issues. ```typescript function metricsReporter() { return (context) => { const metrics = { hits: 0, misses: 0, errors: 0 }; return (event) => { if (event.name === 'getCachedValueSuccess') metrics.hits++; if (event.name === 'getCachedValueEmpty') metrics.misses++; if (event.name.includes('Error')) metrics.errors++; }; }; } cachified( { /* options */ }, metricsReporter() ); ``` -------------------------------- ### Create Batch Loader with createBatch Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/MODULES.md Use `createBatch` to create a utility for fetching multiple values efficiently. It's designed to be used with `cachified` to manage batch requests. ```typescript const batch = createBatch(async (ids) => { return fetch(`/api/users?ids=${ids.join(',')}`).then(r => r.json()); }); Promise.all( ids.map(id => cachified({ getFreshValue: batch.add(id), // ... })) ); ``` -------------------------------- ### Importing from Cachified Entry Point Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/MODULES.md Users import functions and types from the main entry point '@epic-web/cachified'. All re-exports are managed in 'src/index.ts' to ensure the public API does not expose internal modules. ```typescript import { cachified, configure, createBatch, softPurge, isExpired, assertCacheEntry, createCacheEntry, verboseReporter, // ... types CacheEntry, Cache, GetFreshValue, // ... etc } from '@epic-web/cachified'; ``` -------------------------------- ### createBatch Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/MODULES.md Utility for creating batch operations to efficiently fetch multiple values. It allows adding items to a batch and submitting them for fetching. ```APIDOC ## createBatch ### Description Creates a batch utility for efficient fetching of multiple values. It returns an object with an `add` method to queue values and an optional `submit` method. ### Method Signature `createBatch(getFreshValues: GetFreshValues, autoSubmit?: boolean): { add: AddFn, submit?: () => Promise }` ### Type Exports - `GetFreshValues`: Type for the batch function that fetches fresh values. - `AddFn`: Type for the function used to add items to the batch. - `Deferred`: A wrapper class for Promises. ``` -------------------------------- ### Infinite Cache with SWR Configuration Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Configure an infinite cache that never expires but can still refresh in the background using stale-while-revalidate. This is useful for configuration data that rarely changes. ```typescript { key: 'config', cache, // ttl: Infinity (default) staleWhileRevalidate: 24 * 60 * 60 * 1000, // 1 day async getFreshValue() { /* ... */ } } ``` -------------------------------- ### Basic Caching Configuration Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Configure a cache with a specific time-to-live (TTL). The cache is valid for the specified duration, after which a fresh value will be fetched on the next request. ```typescript { key: 'user-1', cache, ttl: 5 * 60 * 1000, // 5 minutes async getFreshValue() { /* ... */ } } ``` -------------------------------- ### Basic Usage with LRUCache Source: https://github.com/epicweb-dev/cachified/blob/main/README.md Demonstrates how to use cachified with an LRUCache instance for basic caching. The `ttl` option sets the cache duration in milliseconds. ```ts import { LRUCache } from 'lru-cache'; import { cachified, CacheEntry, Cache, totalTtl } from '@epic-web/cachified'; /* lru cache is not part of this package but a simple non-persistent cache */ const lruInstance = new LRUCache({ max: 1000 }); const lru: Cache = { set(key, value) { const ttl = totalTtl(value?.metadata); return lruInstance.set(key, value, { ttl: ttl === Infinity ? undefined : ttl, start: value?.metadata?.createdTime, }); }, get(key) { return lruInstance.get(key); }, delete(key) { return lruInstance.delete(key); }, }; 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 ``` -------------------------------- ### createBatch Function Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/createBatch.md Creates a batch loader to efficiently fetch multiple values in a single operation. This is particularly useful when multiple cache misses require the same bulk API call. ```APIDOC ## createBatch Creates a batch loader to efficiently fetch multiple values in a single operation, useful when multiple cache misses need the same bulk API call. ### Function Signatures ```typescript export function createBatch( getFreshValues: GetFreshValues, autoSubmit: false, ): { submit: () => Promise; add: AddFn; }; export function createBatch( getFreshValues: GetFreshValues, ): { add: AddFn; }; ``` ### Parameters | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | getFreshValues | `GetFreshValues` | Yes | — | Function that receives array of params and metadata, returns array of values | | autoSubmit | `boolean` | No | `true` | When true, batch submits immediately when all requests added; when false, requires manual `submit()` call | ### Return Value An object containing: | Property | Type | Description | |----------|------|-------------| | add | `AddFn` | Function to add an item to the batch, returns a `GetFreshValue` function for use with `cachified` | | submit | `() => Promise` | Optional, only present when `autoSubmit: false`. Manually submits the batch | ### GetFreshValues Signature ```typescript type GetFreshValues = ( params: Param[], metadata: CacheMetadata[], ) => Value[] | Promise; ``` Must return an array of values with the **exact same length and order** as the input params array. ``` -------------------------------- ### User Profile Cache Configuration Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Configure a cache for user profiles with a short TTL and a stale-while-revalidate strategy. This pattern is suitable for medium-lived data that requires quick background refreshes when stale. ```typescript { key: `user-${userId}`, cache, ttl: 10 * 60 * 1000, // 10 minutes fresh staleWhileRevalidate: 30 * 60 * 1000, // 20 minutes stale checkValue: userSchema, async getFreshValue() { return fetch(`/api/users/${userId}`).then(r => r.json()); } } ``` -------------------------------- ### Serverless with waitUntil for Background Refreshes Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md In serverless environments like Cloudflare Workers, use `waitUntil` to ensure background cache refreshes complete before the worker terminates. This prevents stale data from being served. ```typescript // Cloudflare Workers await cachified({ key: 'user-1', cache, staleWhileRevalidate: 10 * 60 * 1000, waitUntil: (promise) => ctx.waitUntil(promise), async getFreshValue() { /* ... */ } }); ``` -------------------------------- ### Override Default Options in Individual Cachified Calls Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/configure.md Demonstrates how to use default options set during configuration while also overriding them for specific calls. This provides flexibility in managing cache behavior. ```typescript const cachified = configure({ cache: new Map(), ttl: 5 * 60 * 1000, }); // Use default TTL const user = await cachified({ key: 'user-1', async getFreshValue() { // ... }, }); // Override with longer TTL const staticPage = await cachified({ key: 'page-home', ttl: 60 * 60 * 1000, // 1 hour async getFreshValue() { // ... }, }); ``` -------------------------------- ### Batch Usage with Callback for Metadata Adjustment Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/api-reference/createBatch.md Shows how to use a callback with batch.add to adjust cache metadata, such as TTL, based on the fetched value. This is useful for caching nulls or other specific conditions for shorter durations. ```typescript const batch = createBatch(async (ids) => { const response = await fetch(`/api/users?ids=${ids.join(',')}`); return response.json(); }); return Promise.all( ids.map((id) => cachified({ key: `user-${id}`, cache, getFreshValue: batch.add(id, ({ value, metadata }) => { // Adjust cache time based on the actual value if (value === null) { metadata.ttl = 5 * 60 * 1000; // Cache nulls for shorter time } }), ttl: 60_000, }), ), ); ``` -------------------------------- ### Multi-tenant Applications with Tenant-Specific Caches Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Implement separate cache instances for each tenant in multi-tenant applications. This ensures data isolation and prevents cross-tenant data leakage. ```typescript const getCacheForTenant = (tenantId) => { if (!cachesByTenant.has(tenantId)) { cachesByTenant.set(tenantId, new Map()); } return cachesByTenant.get(tenantId); }; await cachified({ key: `user-${userId}`, cache: getCacheForTenant(tenantId), ttl: 5 * 60 * 1000, async getFreshValue() { /* ... */ } }); ``` -------------------------------- ### Manual Cache Interactions with Helpers Source: https://github.com/epicweb-dev/cachified/blob/main/README.md Utilize helper functions like `createCacheEntry`, `assertCacheEntry`, and `isExpired` for direct manipulation and inspection of cache entries, useful for testing and maintenance. ```typescript import { createCacheEntry, assertCacheEntry, isExpired, cachified, } from '@epic-web/cachified'; const cache = new Map(); /* Manually set an entry to cache */ cache.set( 'user-1', createCacheEntry( 'someone@example.org', /* Optional CacheMetadata */ { ttl: 300_000, swr: Infinity }, ), ); /* Receive the value with cachified */ const value: string = await cachified({ cache, key: 'user-1', getFreshValue() { throw new Error('This is not called since cache is set earlier'); }, }); console.log(value); // > logs "someone@example.org" /* Manually get a value from cache */ const entry: unknown = cache.get('user-1'); assertCacheEntry(entry); // will throw when entry is not a valid CacheEntry console.log(entry.value); // > logs "someone@example.org" /* Manually check if an entry is expired */ const expired = isExpired(entry.metadata); console.log(expired); // > logs true, "stale" or false /* Manually remove an entry from cache */ cache.delete('user-1'); ``` -------------------------------- ### Verbose Reporter Options Source: https://github.com/epicweb-dev/cachified/blob/main/_autodocs/configuration.md Defines the configuration options for the verbose reporter, allowing customization of duration formatting, logging, and performance timing. ```typescript { formatDuration?: (ms: number) => string; logger?: Pick; performance?: Pick; } ```