Skip to content

Installation

1. Install

bash
npm install @nestarc/audit-log

2. 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 AuditService for 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

OptionTypeDefaultDescription
trackedModelsstring[]all models when omittedAllowlist of Prisma model names to track. trackedModels: [] means no models are audited
ignoredModelsstring[][]Denylist used only when trackedModels is not set
sensitiveFieldsstring[][]Fields to mask as [REDACTED] in diffs
sensitiveFieldsByModelRecord<string, string[]>{}Per-model fields unioned with sensitiveFields
primaryKeyRecord<string, string>{ *: 'id' }Map of model name to primary key field name
tableNamestringaudit_logsAudit table used by automatic inserts
tenantRequiredbooleanfalseSkip automatic audit rows when tenant context is missing and report the skip; the business mutation still returns
tenantResolver() => string | nullCustom tenant lookup before the optional @nestarc/tenancy fallback
onAuditError(error, ctx) => voidStructured callback for automatic audit failures
logFailuresbooleanfalseRecord best-effort result='failure' rows when business writes throw
ignoreTimestampOnlyUpdatesbooleanfalseSuppress @updatedAt-only update entries
prismaModulegenerated Prisma module@prisma/clientNamespace for custom Prisma client output paths
experimentalTxAuditbooleanfalseExperimental 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

OptionTypeDefaultDescription
prismaPrismaClientrequiredBase Prisma client for audit storage
actorExtractor(req) => AuditActor | Promise<AuditActor>requiredExtracts actor from HTTP request
tenantRequiredbooleanfalseWhen true, module-side log() and ambient query()/getById() require tenant context unless tenantId or allTenants is explicit
excludeRoutesRouteInfo[][]Routes excluded from AuditActorMiddleware
registerGlobalInterceptorbooleantrueSet false to bind AuditInterceptor manually
correlationIdHeaderstringx-request-idHeader copied into metadata.correlationId
correlationIdGetter(req) => string | undefinedCustom correlation ID source
tableNamestringaudit_logsAudit table name used by module-side log/query/prune APIs
tenantResolver() => string | nullCustom tenant lookup before the optional @nestarc/tenancy fallback
sensitiveFieldsstring[][]Metadata redaction keys for manual logs
sensitiveFieldsByModelRecord<string, string[]>{}Model-specific metadata redaction keys
prismaModulegenerated Prisma module@prisma/clientNamespace for custom Prisma client output paths

Schema Utilities

FunctionDescription
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

Released under the MIT License.