@hapi/catbox

repository·master·Indexed 19 days ago

https://github.com/hapijs/catbox

A multi-strategy object caching service designed for the hapi ecosystem but usable independently with any framework. It provides a low-level Client API for direct cache engine abstraction and a high-level Policy API for automated behaviors such as item expiration, stale-while-revalidate, and automatic data regeneration via generateFunc. Supported strategies include Memory (@hapi/catbox-memory), Redis (@hapi/catbox-redis), and Memcached (@hapi/catbox-memcached).

Tokens
4.3K
Snippets
12
Records
17
Agent score
59%

What's inside @hapi/catbox

  1. How the Client and Policy interfaces work together

    master

    catbox provides two distinct interfaces for interacting with a key-value store:

    1. Client (Low-level): A direct abstraction of the cache engine. It handles raw storage, retrieval, and deletion using segments and IDs. You must manually manage the lifecycle (calling .start() and .stop()) and handle data generation logic yourself.
    2. Policy (High-level): A convenient wrapper that applies a global caching policy to every action. It is typically built on top of a Client. It automates complex behaviors like item expiration, stale-while-revalidate (using generateFunc), and concurrent request queuing.

    Use a Client when you need fine-grained control over the cache engine. Use a Policy when you want to implement sophisticated caching logic (like automatic regeneration of expired data) with minimal boilerplate.

  2. Install catbox caching strategies

    master

    catbox does not include external caching strategies by default to minimize dependencies. You must manually install the specific strategy you intend to use via npm.

    Available strategies:

    • Memory: @hapi/catbox-memory
    • Redis: @hapi/catbox-redis
    • Memcached: @hapi/catbox-memcached
  3. Understand CacheKey and CachedObject structures

    master

    Catbox uses specific interfaces for identifying items and describing retrieved items.

    CacheKey Used by the low-level Client API to locate items. It requires:

    • segment: A string used to isolate different sets of items within the same cache partition.
    • id: A unique identifier string for the item within that segment.

    CachedObject<T> Returned by Client.get(). It wraps the actual value with metadata:

    • item: The stored value of type T.
    • stored: The timestamp (in milliseconds) when the item was stored.
    • ttl: The remaining time-to-live in milliseconds.
  4. Use the Policy API for high-level caching

    master

    The Policy object provides a convenient interface by applying a global policy to every storage action. It is constructed using new Policy(options, [cache, segment]).

    Construction

    • options: Configuration for expiration and generation logic.
    • cache: A started Client instance.
    • segment: The segment name used to isolate items within the cache partition.

    Key Features

    • Automatic Generation: If generateFunc is provided, get() will automatically call it to create a value if the cache is empty or stale.
    • Stale-While-Revalidate: Using staleIn and staleTimeout, you can return a stale value while a fresh value is being generated in the background.
    • Decorated Values: If getDecoratedValue is true, get() returns { value, cached, report } instead of just the value.

    Methods

    • await get(id): Retrieves an item. If id is an object, it must contain an id key.
    • await set(id, value, ttl): Stores an item. Use ttl: 0 to use the policy's default expiration.
    • await drop(id): Removes an item.
    • rules(options): Updates the policy rules after construction.
    • stats: Returns an object containing sets, gets, hits, stales, generates, and errors.

    Events

    The Policy is a podium event emitter. It emits errors on two channels:

    • 'persist': Errors occurring during cache writes (e.g., during generation).
    • 'generate': Errors occurring during the execution of generateFunc.

    Note: Errors from set() and drop() are thrown directly and are not emitted via events.

    const Policy = require('@hapi/catbox').Policy;
    const Client = require('@hapi/catbox').Client;
    const Memory = require('@hapi/catbox-memory');
    
    const cache = new Client(Memory, { partition: 'my-app' });
    await cache.start();
    
    const policy = new Policy({
        expiresIn: 60000,
        staleIn: 30000,
        staleTimeout: 100,
        generateFunc: async (id, flags) => {
            // Fetch from DB
            return { id, data: 'fresh' };
        }
    }, cache, 'my-segment');
    
    const result = await policy.get('user-1');
    // If getDecoratedValue is true, result is { value, cached, report }
  5. Use the Client API for low-level caching

    master

    The Client object provides a low-level cache abstraction. It is constructed using new Client(engine, options).

    Construction

    • engine: Either a prototype function function(options) (which catbox will call with new) or a pre-instantiated object.
    • options: Strategy-specific configuration. A common option is partition, used to isolate results (e.g., as a key prefix in Redis).

    Key Structure

    Every method requiring a key argument must receive an object with:

    • segment: A string representing the caching segment.
    • id: A unique identifier string for the item within that segment.

    Methods

    • await start(): Establishes the connection. Must be called before any other method.
    • await stop(): Terminates the connection.
    • await get(key): Returns an object { item, stored, ttl } or null if not found.
    • await set(key, value, ttl): Stores a value for a specific ttl (milliseconds).
    • await drop(key): Removes an item.
    • isReady(): Returns true if the engine is ready.

    Note: Implementations must return deep copies of stored data so that modifications to returned objects do not affect the cache.

    // Example Client usage (pseudo-code depending on engine)
    const Client = require('@hapi/catbox');
    const Memory = require('@hapi/catbox-memory');
    
    const client = new Client(Memory, { partition: 'my-partition' });
    await client.start();
    
    await client.set({ segment: 'users', id: '123' }, { name: 'John' }, 60000);
    const result = await client.get({ segment: 'users', id: '123' });
    console.log(result.item); // { name: 'John' }
    
    await client.stop();
  6. Configure PolicyOptions for cache behavior

    master

    When creating a Policy, you can define how items are expired and how missing data is generated using PolicyOptions<T>.

    Expiration Options (Use one, not both):

    • expiresIn: Relative expiration in milliseconds.
    • expiresAt: Time of day in HH:MM format (local time).

    Generation & Stale Logic:

    • generateFunc: A function (id, flags) => Promise<T> used to create items on cache misses.
    • staleIn: Milliseconds after which an item is considered stale. Can be a number or a function (stored, ttl) => number.
    • staleTimeout: How long to wait for a fresh value before returning the stale one.
    • generateTimeout: How long to wait for generateFunc before returning a timeout error. Required if generateFunc is present.
    • pendingGenerateTimeout: Delay before a subsequent generateFunc call is allowed for the same id.

    Error Handling:

    • dropOnError: If true (default), an error in generateFunc evicts the stale value.
    • generateOnReadError: If false, a cache read error stops the get() from calling generateFunc and returns the error instead.
    • generateIgnoreWriteError: If false, a cache write error is passed back with the generated value.
  7. Remove cached items with drop()

    master

    The drop(key) method removes an item from the cache. Unlike set(), drop() always attempts to remove the key regardless of any caching rules or TTL settings.

    const key = { id: 'user-123', segment: 'sessions' };
    await client.drop(key);
  8. Use the Client class for low-level cache access

    master

    The Client class provides a low-level abstraction for interacting with a cache engine. You can instantiate it using either a pre-instantiated engine object or an EnginePrototype (a constructor function).

    Key Lifecycle Methods:

    • start(): Establishes a connection to the cache server. Must be called before any other method.
    • stop(): Terminates the connection.
    • isReady(): Returns true if the engine is ready, false otherwise.

    Core API Methods:

    • get(key): Retrieves a CachedObject<T> or null. The key must be a CacheKey object containing a segment and an id.
    • set(key, value, ttl): Stores a value for a specified time-to-live (in milliseconds).
    • drop(key): Removes an item from the cache.

    Configuration: When using an EnginePrototype, you can pass ClientOptions which includes a partition string. The partition is used to isolate results (e.g., as a MongoDB database name, a Riak bucket, or a key prefix in Redis/Memcached).

    // Example using an EnginePrototype
    const client = new Client(MyEngine, { partition: 'my-partition' });
    await client.start();
    
    const key = { segment: 'my-segment', id: 'my-id' };
    const result = await client.get(key);
    
    if (result) {
      console.log(result.item);
    }
    
    await client.stop();
  9. Manage Client Lifecycle (start, stop, isReady)

    master

    The Catbox client provides methods to manage the lifecycle of the underlying engine connection:

    • await client.start(): Starts the engine connection.
    • await client.stop(): Stops the engine connection.
    • client.isReady(): Returns a boolean indicating if the connection is currently active and ready for operations.
    await client.start();
    
    if (client.isReady()) {
        // perform operations
    }
    
    await client.stop();
  10. Use the Policy class for high-level caching logic

    master

    The Policy class provides a convenient interface that applies global caching rules automatically to every storage action. It is built on top of a Client instance and a specific segment name.

    Key Features:

    • Automatic Generation: If a get(id) request results in a cache miss, the Policy can automatically call a generateFunc to create, store, and return the new value.
    • Stale-While-Revalidate: Using staleIn and staleTimeout, the Policy can return a stale value from the cache while simultaneously triggering a background refresh via generateFunc.
    • Concurrency Control: Multiple concurrent requests for the same id are queued and processed only once.

    Methods:

    • get(id): Retrieves an item. If getDecoratedValue: true is set in options, it returns a DecoratedResult<T> containing metadata; otherwise, it returns T | null.
    • set(id, value, ttl?): Stores an item. Passing 0 for ttl uses the policy's configured rules.
    • drop(id): Removes an item.
    • rules(options): Updates the policy rules dynamically (does not affect existing items).
    • stats: Provides a CacheStatisticsObject containing sets, gets, hits, stales, generates, and errors.
    // Example Policy usage
    const policy = new Policy(options, client, 'my-segment');
    
    // If getDecoratedValue is true, result contains metadata
    const result = await policy.get('my-id');
    console.log(result.value);
    
    // Update rules at runtime
    policy.rules({ expiresIn: 60000 });
  11. Store cached items with set()

    master

    The set(key, value, ttl) method stores a value in the cache.

    • key: An object with id (string) and segment (string).
    • value: The data to be cached.
    • ttl: The time-to-live in milliseconds.

    Note: If ttl is less than or equal to 0, the operation is ignored and nothing is cached.

    const key = { id: 'user-123', segment: 'sessions' };
    const value = { name: 'John Doe' };
    const ttl = 3600000; // 1 hour
    
    await client.set(key, value, ttl);