Base Classes
Documentation for BaseCacher, BaseHelper, and BaseTransformer.
Base Classes
This document covers the base classes provided in the Node Library: BaseCacher
, BaseHelper
, and BaseTransformer
.
Features
- Model-Specific Caching: Each cacher is tied to a specific Lucid model, promoting organized and maintainable cache logic.
- Namespace Management: It simplifies cache namespacing, allowing you to create isolated cache spaces for different models and even for individual model instances.
- Extensible: As an abstract class, it's meant to be extended, allowing you to add model-specific caching methods.
Usage
The common pattern is to create a cacher class for a model and then instantiate it via a static getter on the model itself. This makes the cacher easily accessible wherever you have the model class.
Example
Let's create a caching service for a Workspace
model.
1. Create the Cacher Class:
This class can contain methods that define what and how to cache data related to the Workspace
model.
// app/cachers/workspace.ts
import Workspace from "#models/workspace";
import { BaseCacher, ScopedCache } from "@localspace/node-lib";
import { promiseMap } from "@localspace/lib";
export class WorkspaceCacher extends BaseCacher<typeof Workspace, string> {
getActiveMembers(params: { workspace: Workspace }) {
return ScopedCache.create(this.space({ id: params.workspace.id }), {
key: "active-members",
factory: async () => {
const workspaceMembers =
await params.workspace.helper.activeMemberQuery;
return await promiseMap(
workspaceMembers,
async (m) => await m.transformer.serialize(),
);
},
});
}
}
2. Add the Cacher to Your Model:
Use a static getter on the model to provide a single, consistent instance of the cacher.
// app/models/workspace.ts
import { BaseModel } from "@adonisjs/lucid/orm";
import { WorkspaceCacher } from "#cacher/workspace";
import cache from "@adonisjs/cache/services/main";
export default class Workspace extends BaseModel {
// ... your model properties
static get cacher() {
// `this.table` provides the model's table name, e.g., 'workspaces'
return new WorkspaceCacher(Workspace, cache.namespace(this.table));
}
}
3. Use the Cacher in Your Application:
Now you can easily use the cacher in your controllers or services.
// In a controller or service
const workspace = await Workspace.findOrFail(id);
// Get active members. If not in cache, the factory runs and caches the result.
const activeMembers = await Workspace.cacher
.getActiveMembers({ workspace })
.get();
// When a member is added or removed, you can invalidate the cache.
await Workspace.cacher.getActiveMembers({ workspace }).delete();
API
constructor
constructor(model: T, space: CacheProvider)
Prop
Type
space
space(params?: { namespace?: Spaces; id?: string }): CacheProvider
Prop
Type
Returns: A CacheProvider
instance that you can use to perform cache operations like get
, set
, getOrSet
, etc.
BaseHelper
The BaseHelper
is a simple abstract class designed to provide helper functionalities to your Lucid models. It's a great way to encapsulate logic that is specific to a model instance, keeping your model files clean and focused on their primary responsibilities (database schema, relationships, etc.).
Features
- Instance-Specific Logic: Helpers are instantiated with a model instance, so you can perform actions or calculations based on the instance's data.
- Clean Code: It helps you separate business logic from the model definition, leading to more organized and maintainable code.
- Extensible: You can add any methods you need to the helper class, tailored to the model's requirements.
Usage
To use BaseHelper
, you create a new class that extends it, providing your Lucid model as the type parameter. Then, you can add a getter to your model to easily access the helper.
Example
Let's add a helper to a User
model to calculate how many workspaces they own.
1. Create the Helper Class:
// app/helpers/user.ts
import { dbRef } from "#database/reference";
import User from "#models/user";
import { workspaceMemberRoleE } from "#types/literals";
import { BaseHelper } from "@localspace/node-lib";
export class UserHelper extends BaseHelper<User> {
/**
* Calculates the number of workspaces owned by the user.
*/
async getOwnedWorkspaceCount(): Promise<number> {
const result = await this.resource
.related("workspaceMember")
.query()
.where(dbRef.workspaceMember.role, workspaceMemberRoleE("owner"))
.count("*", "total");
// The result of count is an array with a single object like [{ total: 5 }]
return result[0].$extras.total as number;
}
}
2. Add the Helper to Your Model:
Now, add a getter to your User
model to instantiate and return the UserHelper
.
// app/models/user.ts
import { BaseModel } from "@adonisjs/lucid/orm";
import { UserHelper } from "#helper/user";
export default class User extends BaseModel {
// ... your columns and relationships
get helper() {
return new UserHelper(this);
}
}
3. Use the Helper:
Now you can easily access the helper methods from any User
instance, for example, in a controller before creating a new workspace for a user.
// In a controller or service
const user = await User.findOrFail(1);
const ownedWorkspaces = await user.helper.getOwnedWorkspaceCount();
if (ownedWorkspaces >= 5) {
throw new Error("User has reached the maximum number of workspaces.");
}
API
constructor
constructor(protected readonly resource: TR)
Prop
Type
BaseTransformer
The BaseTransformer
is an abstract class that provides a standardized way to transform your Lucid models into plain JavaScript objects. This is especially useful when you need to control the JSON output of your models for API responses, ensuring that you only expose the data you want in the format you need.
Features
- Standardized Serialization: Provides a consistent
serialize
method for all your transformers. - Data Hiding: Easily control which model attributes are included or excluded from the final output.
- Utility Methods: Includes
pick
andomit
helpers to make it easy to select or remove properties from your model. - Date Formatting: Comes with a built-in
datetime
method to serializeDateTime
objects to ISO strings.
Usage
To use BaseTransformer
, you create a new class that extends it and implements the serialize
method.
Example
Let's say you have a User
model and you want to control how it's serialized for an API response.
1. Create the Transformer Class:
// app/transformers/user.ts
import { BaseTransformer } from "@localspace/node-lib";
import User from "#models/user";
export class UserTransformer extends BaseTransformer<User> {
async serialize() {
return {
id: this.resource.id,
name: this.resource.name,
email: this.resource.email,
createdAt: this.datetime(this.resource.createdAt),
updatedAt: this.datetime(this.resource.updatedAt),
};
}
// You can add other transformation methods for different contexts
async forPublicProfile() {
return {
id: this.resource.id,
name: this.resource.name,
};
}
}
2. Add the Transformer to Your Model:
Add a getter to your User
model to easily access the transformer.
// app/models/user.ts
import { BaseModel } from "@adonisjs/lucid/orm";
import { UserTransformer } from "#transformers/user";
export default class User extends BaseModel {
// ... your columns and relationships
get transformer() {
return new UserTransformer(this);
}
}
3. Use the Transformer:
Now you can call the serialize
method from your user instance.
const user = await User.find(1);
// In a controller for an authenticated user's own profile
return user.transformer.serialize();
// In a public-facing page, you might want to expose less information
return user.transformer.forPublicProfile();
API
serialize
abstract serialize(): Promise<Record<string, unknown>>
This is the main method you need to implement. It should return a promise that resolves to a plain JavaScript object representing your transformed model.
Returns: Promise<Record<string, unknown>>
pick
protected pick<T extends object, K extends keyof T>(source: T, keys: K[]): Pick<T, K>
A utility method to create a new object with only the specified keys from the source object.
Prop
Type
Returns: Pick<T, K>
omit
protected omit<T extends object, K extends keyof T>(source: T, keys: K[]): Omit<T, K>
A utility method to create a new object without the specified keys from the source object.
Prop
Type
Returns: Omit<T, K>
datetime
protected datetime = serializeDateTime
A pre-bound method that serializes a DateTime
object into an ISO 8601 string. It handles null values gracefully.
Returns: string | null