@hapi/catbox
repository·master·Indexed 19 days ago
https://github.com/hapijs/catboxA 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).
What's inside @hapi/catbox
- catbox is a multi-strategy object caching service. While it is designed to work seamlessly with the hapi web framework, it is a standalone module that can be used with any web framework or independently for object caching needs.
How the Client and Policy interfaces work together
mastercatbox provides two distinct interfaces for interacting with a key-value store:
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.Policy(High-level): A convenient wrapper that applies a global caching policy to every action. It is typically built on top of aClient. It automates complex behaviors like item expiration, stale-while-revalidate (usinggenerateFunc), and concurrent request queuing.
Use a
Clientwhen you need fine-grained control over the cache engine. Use aPolicywhen you want to implement sophisticated caching logic (like automatic regeneration of expired data) with minimal boilerplate.Install catbox caching strategies
mastercatbox 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
- Memory:
Understand CacheKey and CachedObject structures
masterCatbox uses specific interfaces for identifying items and describing retrieved items.
CacheKey Used by the low-level
ClientAPI 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 typeT.stored: The timestamp (in milliseconds) when the item was stored.ttl: The remaining time-to-live in milliseconds.
Use the Policy API for high-level caching
masterThe
Policyobject provides a convenient interface by applying a global policy to every storage action. It is constructed usingnew Policy(options, [cache, segment]).Construction
options: Configuration for expiration and generation logic.cache: A startedClientinstance.segment: The segment name used to isolate items within the cache partition.
Key Features
- Automatic Generation: If
generateFuncis provided,get()will automatically call it to create a value if the cache is empty or stale. - Stale-While-Revalidate: Using
staleInandstaleTimeout, you can return a stale value while a fresh value is being generated in the background. - Decorated Values: If
getDecoratedValueistrue,get()returns{ value, cached, report }instead of just the value.
Methods
await get(id): Retrieves an item. Ifidis an object, it must contain anidkey.await set(id, value, ttl): Stores an item. Usettl: 0to use the policy's default expiration.await drop(id): Removes an item.rules(options): Updates the policy rules after construction.stats: Returns an object containingsets,gets,hits,stales,generates, anderrors.
Events
The
Policyis apodiumevent emitter. It emits errors on two channels:'persist': Errors occurring during cache writes (e.g., during generation).'generate': Errors occurring during the execution ofgenerateFunc.
Note: Errors from
set()anddrop()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 }Use the Client API for low-level caching
masterThe
Clientobject provides a low-level cache abstraction. It is constructed usingnew Client(engine, options).Construction
engine: Either a prototype functionfunction(options)(which catbox will call withnew) or a pre-instantiated object.options: Strategy-specific configuration. A common option ispartition, used to isolate results (e.g., as a key prefix in Redis).
Key Structure
Every method requiring a
keyargument 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 }ornullif not found.await set(key, value, ttl): Stores a value for a specificttl(milliseconds).await drop(key): Removes an item.isReady(): Returnstrueif 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();Configure PolicyOptions for cache behavior
masterWhen creating a
Policy, you can define how items are expired and how missing data is generated usingPolicyOptions<T>.Expiration Options (Use one, not both):
expiresIn: Relative expiration in milliseconds.expiresAt: Time of day inHH:MMformat (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 forgenerateFuncbefore returning a timeout error. Required ifgenerateFuncis present.pendingGenerateTimeout: Delay before a subsequentgenerateFunccall is allowed for the sameid.
Error Handling:
dropOnError: Iftrue(default), an error ingenerateFuncevicts the stale value.generateOnReadError: Iffalse, a cache read error stops theget()from callinggenerateFuncand returns the error instead.generateIgnoreWriteError: Iffalse, a cache write error is passed back with the generated value.
Remove cached items with drop()
masterThe
drop(key)method removes an item from the cache. Unlikeset(),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);Use the Client class for low-level cache access
masterThe
Clientclass provides a low-level abstraction for interacting with a cache engine. You can instantiate it using either a pre-instantiated engine object or anEnginePrototype(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(): Returnstrueif the engine is ready,falseotherwise.
Core API Methods:
get(key): Retrieves aCachedObject<T>ornull. Thekeymust be aCacheKeyobject containing asegmentand anid.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 passClientOptionswhich includes apartitionstring. Thepartitionis 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();Manage Client Lifecycle (start, stop, isReady)
masterThe 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();Use the Policy class for high-level caching logic
masterThe
Policyclass provides a convenient interface that applies global caching rules automatically to every storage action. It is built on top of aClientinstance and a specificsegmentname.Key Features:
- Automatic Generation: If a
get(id)request results in a cache miss, thePolicycan automatically call agenerateFuncto create, store, and return the new value. - Stale-While-Revalidate: Using
staleInandstaleTimeout, thePolicycan return a stale value from the cache while simultaneously triggering a background refresh viagenerateFunc. - Concurrency Control: Multiple concurrent requests for the same
idare queued and processed only once.
Methods:
get(id): Retrieves an item. IfgetDecoratedValue: trueis set in options, it returns aDecoratedResult<T>containing metadata; otherwise, it returnsT | null.set(id, value, ttl?): Stores an item. Passing0forttluses the policy's configured rules.drop(id): Removes an item.rules(options): Updates the policy rules dynamically (does not affect existing items).stats: Provides aCacheStatisticsObjectcontainingsets,gets,hits,stales,generates, anderrors.
// 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 });- Automatic Generation: If a
Store cached items with set()
masterThe
set(key, value, ttl)method stores a value in the cache.- key: An object with
id(string) andsegment(string). - value: The data to be cached.
- ttl: The time-to-live in milliseconds.
Note: If
ttlis less than or equal to0, 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);- key: An object with