The History system in Adminizer provides a flexible and extensible way to track changes to model instances, store historical records, and expose them securely to users through an API. It supports multiple adapters, access control, model-specific configurations, and smart data formatting.
The core idea is to capture state snapshots of any model change (create, update, delete), assign metadata (user, timestamp, model name, ID), and allow retrieval filtered by user permissions, time range, or model type.
HistoryHandlerAbstractHistoryAdapterAn abstract base class defining the contract for all history adapters. It includes:
history-${id}, users-history-${id}).getModels()).displayName resolution).excludedModels.DefaultHistoryAdapterThis built-in adapter uses the HistoryActionsAP model to persist history records in the database.
isCurrent: true) for the same (modelName, modelId) is marked as outdated.isCurrent: true, containing:
modelName, modelIduser (ID or login)action (e.g., “update”)data — full snapshot of the model’s fields at that timecreatedAt, etc.)users-history-default permission (to view others’ actions).modelName, forUserName, from, to, pagination.displayName using model config:
displayName: string | ((record: any) => string)
If not defined → falls back to modelId.
UserAP, MediaManagerAP, etc.) are excluded by default.config.history.excludeModels.interface HistoryConfig {
enabled?: boolean;
adapter?: string; // 'default' | custom adapter ID
excludeModels?: string[]; // additional models to exclude from tracking
}
Example:
config: {
history: {
enabled: true,
adapter: "default", // optional, default if not specified
excludeModels: ["post", "category"]
}
}
If enabled: false or not set, no history will be recorded or served.
You can replace or extend the default behavior by implementing your own adapter.
Step 1: Implement AbstractHistoryAdapter
import { AbstractHistoryAdapter } from '../lib/history-actions/AbstractHistoryAdapter';
import { HistoryActionsAP, UserAP } from '../models';
export class MyCustomHistoryAdapter extends AbstractHistoryAdapter {
public id = 'myadapter'; // must be unique
public model = 'custom_history'; // optional, depends on your storage
constructor(adminizer) {
super(adminizer);
// Your initialization
}
async getAllHistory(...) { ... }
async getAllModelHistory(...) { ... }
async setHistory(...) { ... }
async getModelFieldsHistory(...) { ... }
}
You must implement all abstract methods.
Step 2: Register Your Adapter
adminizer.historyHandler = new HistoryHandler();
adminizer.historyHandler.add(new MyCustomHistoryAdapter(adminizer));
Use Cases for Custom Adapters
Logging to external systems (e.g., Kafka, ELK). Immutable storage (e.g., blockchain-like ledger). Lightweight logging without full snapshots. Different DB (e.g., MongoDB, Redis for recent activity).
Each adapter registers its own permissions:
| Token | Purpose |
|---|---|
history-${id} |
General access to view history |
users-history-${id} |
View history of any user (otherwise only own) |
These are auto-registered with the Adminizer access rights system.
| Step | Description |
|---|---|
| 1 | Model Update |
| 2 | Adminizer calls .setHistory(data) |
| 3 | Adapter saves snapshot with: user, model, id, data, isCurrent = true |
| 4 | Older records for same (model, id) → isCurrent = false |
| 5 | On GET history → filter by: • User permissions • Model access • Time range • User scope (own vs all) |
| 6 | Format output: • Add displayName• Resolve media/associations |
| 7 | Return to frontend |
The system is opt-out: all models are tracked unless listed in excludeModels. Future improvements may include diff-only storage and compression.