- AI
- Analytics
- Auth
- CMS
- Collaboration
- Cron Jobs
- Database
- Data Layer
- CRUD System
- Design System
- Transactional Emails
- Feature Flags
- Formatting
- Internationalization
- Observability
- Next.js Config
- Notifications
- Payments
- Security
- SEO
- Storage
- Testing
- Toolbar
- View System
- View Builder
- Trigger
- Trigger Rules
- Webhooks
Audit System
Tracking changes to data
Audit System
The audit package in @repo/crud
provides a comprehensive system for tracking changes to data. It automatically logs all create, update, and delete operations, recording who made what changes and when.
Features
- Automatic Logging: Automatically log all data changes
- User Tracking: Record which user made each change
- Change Tracking: Record the exact changes made to data
- Flexible Storage: Store audit logs in any database or service
- Customizable: Configure what gets logged and how
- TypeScript Support: Full TypeScript support with generics
Basic Usage
Setting Up Audit Logging
import { audit } from '@repo/crud';
import { engine } from '@repo/crud';
import { providers } from '@repo/data';
// Create an audit provider
const auditProvider = audit.createAuditProvider({
logAudit: async (auditLog) => {
// Store audit log in database
await db.auditLogs.create(auditLog);
}
});
// Create a data provider
const dataProvider = providers.supabase.createProvider({
url: process.env.SUPABASE_URL,
key: process.env.SUPABASE_KEY
});
// Create a CRUD engine with audit logging
const crudEngine = engine.createCrudEngine({
dataProvider,
enableAudit: true,
auditProvider
});
Setting the Current User
Before performing CRUD operations, you need to set the current user:
import { audit } from '@repo/crud';
// Set the current user
audit.setUser({
id: '123',
name: 'John Doe',
email: 'john@example.com'
});
// Now CRUD operations will include this user in audit logs
const result = await crudEngine.create({
resource: 'posts',
data: { title: 'New Post', content: 'Hello World' }
});
Integration with Authentication
You can integrate the audit system with your authentication system:
import { audit } from '@repo/crud';
import { useAuth } from '@repo/auth';
function AuditProvider({ children }) {
const { user } = useAuth();
// Set the current user whenever it changes
useEffect(() => {
if (user) {
audit.setUser(user);
} else {
audit.clearUser();
}
}, [user]);
return children;
}
function App() {
return (
<AuthProvider>
<AuditProvider>
<YourApp />
</AuditProvider>
</AuthProvider>
);
}
Audit Log Structure
Audit Log Interface
interface AuditLog {
/** Timestamp of the audit log */
timestamp: Date;
/** User who performed the action */
user: {
id: string | number;
name?: string;
email?: string;
[key: string]: any;
};
/** Action performed */
action: 'create' | 'update' | 'delete';
/** Resource type */
resource: string;
/** Resource ID */
resourceId: string | number;
/** New data (for create and update) */
data?: Record<string, any>;
/** Previous data (for update and delete) */
previousData?: Record<string, any>;
/** Changes made (for update) */
changes?: {
[field: string]: {
from: any;
to: any;
};
};
/** Additional metadata */
meta?: Record<string, any>;
}
Example Audit Logs
Create Operation
// Audit log for a create operation
{
timestamp: new Date('2025-05-21T13:45:30.000Z'),
user: {
id: '123',
name: 'John Doe',
email: 'john@example.com'
},
action: 'create',
resource: 'posts',
resourceId: '456',
data: {
id: '456',
title: 'New Post',
content: 'Hello World',
createdAt: '2025-05-21T13:45:30.000Z'
}
}
Update Operation
// Audit log for an update operation
{
timestamp: new Date('2025-05-21T14:30:45.000Z'),
user: {
id: '123',
name: 'John Doe',
email: 'john@example.com'
},
action: 'update',
resource: 'posts',
resourceId: '456',
data: {
id: '456',
title: 'Updated Post',
content: 'Hello World Updated',
createdAt: '2025-05-21T13:45:30.000Z',
updatedAt: '2025-05-21T14:30:45.000Z'
},
previousData: {
id: '456',
title: 'New Post',
content: 'Hello World',
createdAt: '2025-05-21T13:45:30.000Z'
},
changes: {
title: {
from: 'New Post',
to: 'Updated Post'
},
content: {
from: 'Hello World',
to: 'Hello World Updated'
},
updatedAt: {
from: undefined,
to: '2025-05-21T14:30:45.000Z'
}
}
}
Delete Operation
// Audit log for a delete operation
{
timestamp: new Date('2025-05-21T15:20:10.000Z'),
user: {
id: '123',
name: 'John Doe',
email: 'john@example.com'
},
action: 'delete',
resource: 'posts',
resourceId: '456',
previousData: {
id: '456',
title: 'Updated Post',
content: 'Hello World Updated',
createdAt: '2025-05-21T13:45:30.000Z',
updatedAt: '2025-05-21T14:30:45.000Z'
}
}
Audit Providers
Creating an Audit Provider
You can create an audit provider to store audit logs in any database or service:
import { audit } from '@repo/crud';
// Create an audit provider for a SQL database
const sqlAuditProvider = audit.createAuditProvider({
logAudit: async (auditLog) => {
// Store audit log in SQL database
await db.execute(`
INSERT INTO audit_logs (
timestamp, user_id, user_name, user_email, action, resource, resource_id,
data, previous_data, changes, meta
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
auditLog.timestamp,
auditLog.user.id,
auditLog.user.name,
auditLog.user.email,
auditLog.action,
auditLog.resource,
auditLog.resourceId,
JSON.stringify(auditLog.data),
JSON.stringify(auditLog.previousData),
JSON.stringify(auditLog.changes),
JSON.stringify(auditLog.meta)
]);
}
});
// Create an audit provider for MongoDB
const mongoAuditProvider = audit.createAuditProvider({
logAudit: async (auditLog) => {
// Store audit log in MongoDB
await db.collection('auditLogs').insertOne(auditLog);
}
});
// Create an audit provider for a logging service
const loggingServiceAuditProvider = audit.createAuditProvider({
logAudit: async (auditLog) => {
// Send audit log to logging service
await fetch('https://logging-service.example.com/api/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.LOGGING_SERVICE_API_KEY}`
},
body: JSON.stringify({
type: 'audit',
timestamp: auditLog.timestamp.toISOString(),
user: auditLog.user.id,
action: auditLog.action,
resource: auditLog.resource,
resourceId: auditLog.resourceId,
changes: auditLog.changes
})
});
}
});
Multiple Audit Providers
You can use multiple audit providers to store audit logs in different places:
import { audit } from '@repo/crud';
// Create a composite audit provider
const compositeAuditProvider = audit.createCompositeAuditProvider([
// Store in database
audit.createAuditProvider({
logAudit: async (auditLog) => {
await db.auditLogs.create(auditLog);
}
}),
// Also log to console
audit.createAuditProvider({
logAudit: async (auditLog) => {
console.log('Audit Log:', auditLog);
}
}),
// Also send to logging service
audit.createAuditProvider({
logAudit: async (auditLog) => {
await fetch('https://logging-service.example.com/api/logs', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.LOGGING_SERVICE_API_KEY}`
},
body: JSON.stringify({
type: 'audit',
timestamp: auditLog.timestamp.toISOString(),
user: auditLog.user.id,
action: auditLog.action,
resource: auditLog.resource,
resourceId: auditLog.resourceId,
changes: auditLog.changes
})
});
}
})
]);
// Use the composite audit provider
const crudEngine = engine.createCrudEngine({
dataProvider,
enableAudit: true,
auditProvider: compositeAuditProvider
});
Customizing Audit Logs
Filtering Audit Logs
You can filter which operations are logged:
import { audit } from '@repo/crud';
// Create an audit provider with filtering
const filteredAuditProvider = audit.createAuditProvider({
logAudit: async (auditLog) => {
await db.auditLogs.create(auditLog);
},
filter: (auditLog) => {
// Only log create and delete operations
if (auditLog.action === 'update') {
return false;
}
// Only log certain resources
if (!['users', 'posts', 'comments'].includes(auditLog.resource)) {
return false;
}
// Only log changes made by non-admin users
if (auditLog.user.role === 'admin') {
return false;
}
return true;
}
});
// Use the filtered audit provider
const crudEngine = engine.createCrudEngine({
dataProvider,
enableAudit: true,
auditProvider: filteredAuditProvider
});
Transforming Audit Logs
You can transform audit logs before they are stored:
import { audit } from '@repo/crud';
// Create an audit provider with transformation
const transformedAuditProvider = audit.createAuditProvider({
logAudit: async (auditLog) => {
await db.auditLogs.create(auditLog);
},
transform: (auditLog) => {
// Add additional metadata
auditLog.meta = {
...auditLog.meta,
environment: process.env.NODE_ENV,
appVersion: process.env.APP_VERSION
};
// Remove sensitive data
if (auditLog.data && auditLog.data.password) {
auditLog.data.password = '[REDACTED]';
}
if (auditLog.previousData && auditLog.previousData.password) {
auditLog.previousData.password = '[REDACTED]';
}
if (auditLog.changes && auditLog.changes.password) {
auditLog.changes.password = {
from: '[REDACTED]',
to: '[REDACTED]'
};
}
return auditLog;
}
});
// Use the transformed audit provider
const crudEngine = engine.createCrudEngine({
dataProvider,
enableAudit: true,
auditProvider: transformedAuditProvider
});
Resource-Specific Audit Configuration
You can configure audit logging differently for each resource:
import { audit } from '@repo/crud';
// Create an audit provider with resource-specific configuration
const resourceSpecificAuditProvider = audit.createAuditProvider({
logAudit: async (auditLog) => {
await db.auditLogs.create(auditLog);
},
resources: {
users: {
// Only log create and delete operations for users
actions: ['create', 'delete'],
// Exclude sensitive fields
excludeFields: ['password', 'ssn', 'creditCard']
},
posts: {
// Log all operations for posts
actions: ['create', 'update', 'delete'],
// Only include these fields in the audit log
includeFields: ['id', 'title', 'content', 'userId', 'status']
},
comments: {
// Log all operations for comments
actions: ['create', 'update', 'delete'],
// Custom transform function for comments
transform: (auditLog) => {
// Add the post ID to the metadata
if (auditLog.data && auditLog.data.postId) {
auditLog.meta = {
...auditLog.meta,
postId: auditLog.data.postId
};
}
return auditLog;
}
}
}
});
// Use the resource-specific audit provider
const crudEngine = engine.createCrudEngine({
dataProvider,
enableAudit: true,
auditProvider: resourceSpecificAuditProvider
});
Querying Audit Logs
Basic Queries
Once you have stored audit logs, you can query them:
// Get all audit logs
const allAuditLogs = await db.auditLogs.findMany();
// Get audit logs for a specific resource
const userAuditLogs = await db.auditLogs.findMany({
where: { resource: 'users' }
});
// Get audit logs for a specific resource ID
const userAuditLogs = await db.auditLogs.findMany({
where: {
resource: 'users',
resourceId: '123'
}
});
// Get audit logs for a specific action
const createAuditLogs = await db.auditLogs.findMany({
where: { action: 'create' }
});
// Get audit logs for a specific user
const userAuditLogs = await db.auditLogs.findMany({
where: { 'user.id': '123' }
});
// Get audit logs for a specific time period
const recentAuditLogs = await db.auditLogs.findMany({
where: {
timestamp: {
gte: new Date('2025-05-01T00:00:00.000Z'),
lte: new Date('2025-05-31T23:59:59.999Z')
}
}
});
Advanced Queries
You can also perform more advanced queries:
// Get audit logs for changes to a specific field
const titleChangeAuditLogs = await db.auditLogs.findMany({
where: {
action: 'update',
'changes.title': { exists: true }
}
});
// Get audit logs for a specific change
const statusChangeAuditLogs = await db.auditLogs.findMany({
where: {
action: 'update',
'changes.status.from': 'draft',
'changes.status.to': 'published'
}
});
// Get audit logs with specific metadata
const productionAuditLogs = await db.auditLogs.findMany({
where: {
'meta.environment': 'production'
}
});
UI Integration
Displaying Audit Logs
You can create a UI to display audit logs:
import { useState, useEffect } from 'react';
function AuditLogList() {
const [auditLogs, setAuditLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function fetchAuditLogs() {
try {
const response = await fetch('/api/audit-logs');
const data = await response.json();
setAuditLogs(data);
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
}
fetchAuditLogs();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h2>Audit Logs</h2>
<table>
<thead>
<tr>
<th>Timestamp</th>
<th>User</th>
<th>Action</th>
<th>Resource</th>
<th>Resource ID</th>
<th>Changes</th>
</tr>
</thead>
<tbody>
{auditLogs.map(log => (
<tr key={log.id}>
<td>{new Date(log.timestamp).toLocaleString()}</td>
<td>{log.user.name || log.user.id}</td>
<td>{log.action}</td>
<td>{log.resource}</td>
<td>{log.resourceId}</td>
<td>
{log.changes && (
<ul>
{Object.entries(log.changes).map(([field, change]) => (
<li key={field}>
{field}: {JSON.stringify(change.from)} → {JSON.stringify(change.to)}
</li>
))}
</ul>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
Filtering Audit Logs in the UI
You can add filtering to the audit log UI:
import { useState, useEffect } from 'react';
function AuditLogList() {
const [auditLogs, setAuditLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [filters, setFilters] = useState({
resource: '',
action: '',
userId: '',
startDate: '',
endDate: ''
});
useEffect(() => {
async function fetchAuditLogs() {
try {
// Build query string from filters
const queryParams = new URLSearchParams();
if (filters.resource) queryParams.set('resource', filters.resource);
if (filters.action) queryParams.set('action', filters.action);
if (filters.userId) queryParams.set('userId', filters.userId);
if (filters.startDate) queryParams.set('startDate', filters.startDate);
if (filters.endDate) queryParams.set('endDate', filters.endDate);
const response = await fetch(`/api/audit-logs?${queryParams.toString()}`);
const data = await response.json();
setAuditLogs(data);
setLoading(false);
} catch (err) {
setError(err.message);
setLoading(false);
}
}
fetchAuditLogs();
}, [filters]);
const handleFilterChange = (e) => {
const { name, value } = e.target;
setFilters(prev => ({ ...prev, [name]: value }));
};
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;
return (
<div>
<h2>Audit Logs</h2>
<div className="filters">
<div>
<label htmlFor="resource">Resource:</label>
<select
id="resource"
name="resource"
value={filters.resource}
onChange={handleFilterChange}
>
<option value="">All</option>
<option value="users">Users</option>
<option value="posts">Posts</option>
<option value="comments">Comments</option>
</select>
</div>
<div>
<label htmlFor="action">Action:</label>
<select
id="action"
name="action"
value={filters.action}
onChange={handleFilterChange}
>
<option value="">All</option>
<option value="create">Create</option>
<option value="update">Update</option>
<option value="delete">Delete</option>
</select>
</div>
<div>
<label htmlFor="userId">User ID:</label>
<input
type="text"
id="userId"
name="userId"
value={filters.userId}
onChange={handleFilterChange}
/>
</div>
<div>
<label htmlFor="startDate">Start Date:</label>
<input
type="date"
id="startDate"
name="startDate"
value={filters.startDate}
onChange={handleFilterChange}
/>
</div>
<div>
<label htmlFor="endDate">End Date:</label>
<input
type="date"
id="endDate"
name="endDate"
value={filters.endDate}
onChange={handleFilterChange}
/>
</div>
</div>
<table>
{/* Table content as before */}
</table>
</div>
);
}
Advanced Usage
Custom Audit Context
You can provide additional context to audit logs:
import { audit } from '@repo/crud';
// Set the current user
audit.setUser({
id: '123',
name: 'John Doe',
email: 'john@example.com'
});
// Set additional context
audit.setContext({
requestId: '456',
clientIp: '192.168.1.1',
userAgent: 'Mozilla/5.0 ...',
sessionId: '789'
});
// Now CRUD operations will include this context in audit logs
const result = await crudEngine.create({
resource: 'posts',
data: { title: 'New Post', content: 'Hello World' }
});
Audit Events
You can listen for audit events:
import { audit } from '@repo/crud';
// Listen for audit events
audit.on('log', (auditLog) => {
console.log('Audit Log:', auditLog);
});
// Listen for specific audit events
audit.on('create', (auditLog) => {
console.log('Create Audit Log:', auditLog);
});
audit.on('update', (auditLog) => {
console.log('Update Audit Log:', auditLog);
});
audit.on('delete', (auditLog) => {
console.log('Delete Audit Log:', auditLog);
});
// Remove event listeners
audit.off('log');
Audit Hooks
You can use hooks to modify audit logs before they are stored:
import { audit } from '@repo/crud';
// Add a hook to modify audit logs
audit.addHook('beforeLog', (auditLog) => {
// Add additional metadata
auditLog.meta = {
...auditLog.meta,
environment: process.env.NODE_ENV,
appVersion: process.env.APP_VERSION
};
return auditLog;
});
// Add a hook to filter audit logs
audit.addHook('beforeLog', (auditLog) => {
// Don't log changes to the 'updatedAt' field
if (auditLog.changes && auditLog.changes.updatedAt) {
delete auditLog.changes.updatedAt;
}
return auditLog;
});
// Remove a hook
audit.removeHook('beforeLog');
Manual Audit Logging
You can manually log audit events:
import { audit } from '@repo/crud';
// Manually log an audit event
audit.log({
action: 'custom',
resource: 'system',
resourceId: 'backup',
data: {
status: 'success',
files: 42,
size: '1.2GB'
},
meta: {
duration: 1250,
backupId: '123'
}
});
API Reference
For a complete API reference, please refer to the TypeScript definitions in the package.
- Audit System
- Features
- Basic Usage
- Setting Up Audit Logging
- Setting the Current User
- Integration with Authentication
- Audit Log Structure
- Audit Log Interface
- Example Audit Logs
- Create Operation
- Update Operation
- Delete Operation
- Audit Providers
- Creating an Audit Provider
- Multiple Audit Providers
- Customizing Audit Logs
- Filtering Audit Logs
- Transforming Audit Logs
- Resource-Specific Audit Configuration
- Querying Audit Logs
- Basic Queries
- Advanced Queries
- UI Integration
- Displaying Audit Logs
- Filtering Audit Logs in the UI
- Advanced Usage
- Custom Audit Context
- Audit Events
- Audit Hooks
- Manual Audit Logging
- API Reference