ScopedCache
The `ScopedCache` is a powerful, type-safe, and hierarchical cache manager built on top of the AdonisJS cache provider. It simplifies working with namespaced keys, making it easy to create, manage, and invalidate related cached data.
Features
- Automatic Namespacing: Each
ScopedCache
instance manages its own namespace, preventing key collisions. - Hierarchical Caching: You can
extend
a cache to create child caches, forming a logical tree of cached data (e.g.,user:1
->user:1:posts
). - Type-Safe: It's fully generic, so you get type safety for your cached values.
- Factory Functions: It uses factory functions (
() => Promise<T>
) to fetch and cache data only when it's not already in the cache. - Graceful Invalidation: Provides methods to
delete
a specific cache entry orclearNamespace
to wipe out an entire branch of the cache tree.
Usage
ScopedCache
is powerful on its own, but it integrates very well with the BaseCacher
pattern to organize your application's caching logic.
Creating and Using a Scoped Cache
Here's an example of using ScopedCache
inside a WorkspaceCacher
to cache a workspace's active members.
// In app/cachers/workspace_cacher.ts
import { BaseCacher, ScopedCache } from "@localspace/node-lib";
import Workspace from "#models/workspace";
export class WorkspaceCacher extends BaseCacher<typeof Workspace> {
// ... instantiation via static getter on the model ...
getActiveMembers(workspace: Workspace) {
// Use the cacher's space, namespaced by the workspace ID
const space = this.space({ id: workspace.id });
return ScopedCache.create(space, {
key: "active-members",
ttl: "10m",
factory: async () => {
const members = await workspace
.related("members")
.query()
.whereNull("leftAt");
// It's a good practice to transform data before caching
return Promise.all(
members.map((member) => member.transformer.serialize()),
);
},
});
}
}
// In a controller
const workspace = await Workspace.findOrFail(id);
const cacher = Workspace.cacher.getActiveMembers(workspace);
// Get the members. If not in cache, the factory runs and caches the result.
const members = await cacher.get();
// Later, when a member is added or removed, invalidate the cache.
await cacher.delete();
Hierarchical Caching and Invalidation
The extend
method allows you to build a tree of cached data, making invalidation more granular and logical.
import { ScopedCache } from "@localspace/node-lib";
import cache from "@adonisjs/cache/services/main";
// 1. Create a root cache for a single workspace
const workspaceCache = ScopedCache.create(cache.namespace("workspaces"), {
key: workspace.id,
factory: () => Workspace.findOrFail(workspace.id),
});
// 2. Create a child cache for the workspace's latest blogs
// The factory for the child receives the parent's data (the workspace object)
const blogsCache = workspaceCache.extend("blogs", (ws) => {
return ws
.related("blogs")
.query()
.orderBy("createdAt", "desc")
.limit(5)
.exec();
});
// This will fetch and cache the workspace first, then its blogs
const latestBlogs = await blogsCache.get();
// --- Invalidation --- //
// When a new blog is created, you only need to invalidate the blogs cache.
// The parent workspaceCache remains valid.
await blogsCache.delete();
// When the workspace itself is updated (e.g., renamed), you can clear its
// entire namespace, which invalidates the workspace AND all its children (blogs).
await workspaceCache.clearNamespace();
API
create
static create<T>(space: CacheProvider, params: ScopedCacheOptions<T>): ScopedCache<T>
Prop
Type
get
async get(options?: { latest?: boolean }): Promise<T>
Retrieves the value from the cache. If latest: true
, it will bypass the cache and re-run the factory.
Prop
Type
peek
async peek(): Promise<T | undefined>
Retrieves the value from the cache but returns undefined
if it's not found, without running the factory.
set
async set(value: T): Promise<void>
Manually sets the value in the cache, bypassing the factory.
Prop
Type
extend
extend<U>(key: string, factory: (parentValue: T) => U | Promise<U>, options?: SetCommonOptions): ScopedCache<U>
Creates a dependent child cache.
Prop
Type
derive
derive<U>(key: string, factory: GetSetFactory<U>, options?: SetCommonOptions): ScopedCache<U>
Creates an independent child cache in a nested namespace.
Prop
Type
delete
async delete(): Promise<boolean>
Deletes the value for the current scope.
clearNamespace
async clearNamespace(): Promise<boolean>
Deletes the value for the current scope and all its children.
Exception Handling
This module provides a robust and structured way to handle HTTP exceptions in your AdonisJS application. It includes a base `HTTPException` class, a list of pre-defined common HTTP exceptions, and a parser to convert various error types into a consistent format.
General Utilities
Documentation for logDeep, and Serialization Utilities.