Installation
1. Install
bash
npm install @nestarc/audit-log2. Create the audit_logs table
typescript
import { applyAuditTableSchema } from '@nestarc/audit-log';
// In a migration or setup script:
await applyAuditTableSchema(prisma);Or use getAuditTableSQL() to get the raw SQL string for your migration tool.
3. Complete NestJS Integration
The library requires two Prisma clients with distinct roles:
- Base client — used by
AuditServicefor writing/querying audit logs - Extended client — used by your application code for business writes (CUD tracking fires here)
typescript
// prisma.service.ts
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { createAuditExtension } from '@nestarc/audit-log';
const auditExtensionOptions = {
trackedModels: ['User', 'Invoice', 'Document'],
sensitiveFields: ['password', 'ssn'],
ignoreTimestampOnlyUpdates: true,
// primaryKey: { Order: 'orderNumber' }, // for non-id PKs
};
@Injectable()
export class PrismaService implements OnModuleInit {
/** Base client — for audit storage (log/query) */
readonly base = new PrismaClient();
/** Extended client — use this for all application queries */
readonly client = this.base.$extends(
createAuditExtension(auditExtensionOptions),
);
async onModuleInit() {
await this.base.$connect();
}
}typescript
// prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}typescript
// app.module.ts
import { Module } from '@nestjs/common';
import { AuditLogModule } from '@nestarc/audit-log';
import { PrismaModule } from './prisma.module';
import { PrismaService } from './prisma.service';
@Module({
imports: [
PrismaModule,
AuditLogModule.forRootAsync({
inject: [PrismaService],
useFactory: (prisma: PrismaService) => ({
prisma: prisma.base,
actorExtractor: (req) => ({
id: req.user?.id ?? null,
type: req.user ? 'user' : 'system',
ip: req.ip,
}),
// tenantRequired: true, // fail-closed for multi-tenant deployments
// correlationIdHeader: 'x-request-id',
}),
}),
],
})
export class AppModule {}typescript
// user.service.ts — use prisma.client (extended) for all business writes
@Injectable()
export class UserService {
constructor(private readonly prisma: PrismaService) {}
async createUser(data: CreateUserDto) {
// Automatic audit tracking fires because we use the extended client
return this.prisma.client.user.create({ data });
}
}createAuditExtension Options
| Option | Type | Default | Description |
|---|---|---|---|
trackedModels | string[] | all models when omitted | Allowlist of Prisma model names to track. trackedModels: [] means no models are audited |
ignoredModels | string[] | [] | Denylist used only when trackedModels is not set |
sensitiveFields | string[] | [] | Fields to mask as [REDACTED] in diffs |
sensitiveFieldsByModel | Record<string, string[]> | {} | Per-model fields unioned with sensitiveFields |
primaryKey | Record<string, string> | { *: 'id' } | Map of model name to primary key field name |
tableName | string | audit_logs | Audit table used by automatic inserts |
tenantRequired | boolean | false | Skip automatic audit rows when tenant context is missing and report the skip; the business mutation still returns |
tenantResolver | () => string | null | — | Custom tenant lookup before the optional @nestarc/tenancy fallback |
onAuditError | (error, ctx) => void | — | Structured callback for automatic audit failures |
logFailures | boolean | false | Record best-effort result='failure' rows when business writes throw |
ignoreTimestampOnlyUpdates | boolean | false | Suppress @updatedAt-only update entries |
prismaModule | generated Prisma module | @prisma/client | Namespace for custom Prisma client output paths |
experimentalTxAudit | boolean | false | Experimental transaction-aware audit routing through Prisma internals when available |
When neither trackedModels nor ignoredModels is configured, createAuditExtension() audits all Prisma models and emits a one-time warning. Set trackedModels as an allowlist or ignoredModels as a denylist to narrow scope.
AuditLogModule Options
| Option | Type | Default | Description |
|---|---|---|---|
prisma | PrismaClient | required | Base Prisma client for audit storage |
actorExtractor | (req) => AuditActor | Promise<AuditActor> | required | Extracts actor from HTTP request |
tenantRequired | boolean | false | When true, module-side log() and ambient query()/getById() require tenant context unless tenantId or allTenants is explicit |
excludeRoutes | RouteInfo[] | [] | Routes excluded from AuditActorMiddleware |
registerGlobalInterceptor | boolean | true | Set false to bind AuditInterceptor manually |
correlationIdHeader | string | x-request-id | Header copied into metadata.correlationId |
correlationIdGetter | (req) => string | undefined | — | Custom correlation ID source |
tableName | string | audit_logs | Audit table name used by module-side log/query/prune APIs |
tenantResolver | () => string | null | — | Custom tenant lookup before the optional @nestarc/tenancy fallback |
sensitiveFields | string[] | [] | Metadata redaction keys for manual logs |
sensitiveFieldsByModel | Record<string, string[]> | {} | Model-specific metadata redaction keys |
prismaModule | generated Prisma module | @prisma/client | Namespace for custom Prisma client output paths |
Schema Utilities
| Function | Description |
|---|---|
getAuditTableSQL(options?) | Returns raw SQL string for creating audit tables, trigger enforcement, optional partitions, and indexes |
getAuditTableStatements(options?) | Returns SQL split into individual executable statements |
applyAuditTableSchema(prisma, options?) | Executes the schema SQL statement by statement via Prisma |
ensurePartitions(prisma, options?) | Creates missing monthly partitions for partitioned audit tables |