npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@nestarc/audit-log

v0.1.0

Published

Audit logging module for NestJS with automatic Prisma change tracking and append-only PostgreSQL storage

Readme

@nestarc/audit-log

CI

Audit logging module for NestJS with automatic Prisma change tracking and append-only PostgreSQL storage.

Requirements

  • NestJS 10 or 11
  • Prisma 5 or 6
  • PostgreSQL

Features

  • Automatic CUD tracking via Prisma $extends — create, update, delete, upsert, and batch operations
  • Caller transaction aware — automatic tracking participates in caller's $transaction; audit insert is best-effort
  • Before/after diffs with deep comparison for JSON fields
  • Sensitive field masking — configurable [REDACTED] replacement
  • Manual logging APIAuditService.log() for business events (with optional transaction support)
  • Query APIAuditService.query() with wildcard filters, pagination
  • Decorators@NoAudit() / @AuditAction() on handlers or controllers
  • Custom primary keys — configurable per-model PK field (defaults to id)
  • Multi-tenant — optional @nestarc/tenancy integration with fail-closed mode
  • Append-only — ships PostgreSQL rules to prevent UPDATE/DELETE on audit records

Quick Start

1. Install

npm install @nestarc/audit-log

2. Create the audit_logs table

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)
// 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'],
  // 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();
  }
}
// prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({
  providers: [PrismaService],
  exports: [PrismaService],
})
export class PrismaModule {}
// 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
      }),
    }),
  ],
})
export class AppModule {}
// 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 });
  }
}

API

AuditLogModule.forRoot(options) / forRootAsync(options)

| Option | Type | Default | Description | |--------|------|---------|-------------| | prisma | PrismaClient | required | Base Prisma client for audit storage | | actorExtractor | (req) => AuditActor | required | Extracts actor from HTTP request | | tenantRequired | boolean | false | When true, throws if tenant context is unavailable |

AuditService

// Manual logging
await auditService.log({
  action: 'invoice.approved',
  targetId: 'inv-123',
  targetType: 'Invoice',
  metadata: { amount: 5000, currency: 'USD' },
});

// Manual logging inside a transaction
await prisma.base.$transaction(async (tx) => {
  await tx.invoice.update({ where: { id }, data: { status: 'approved' } });
  await auditService.log({ action: 'invoice.approved', targetId: id }, tx);
  // Both roll back together if anything fails
});

// Querying
const result = await auditService.query({
  actorId: 'user-123',
  action: 'invoice.*',     // wildcard support
  targetType: 'Invoice',
  from: new Date('2026-01-01'),
  to: new Date('2026-04-01'),
  limit: 50,
  offset: 0,
});
// -> { entries: AuditEntry[], total: number }

Decorators

Apply to individual handlers or entire controllers:

@NoAudit()      // Skip audit tracking for this route or controller
@AuditAction('user.role.changed')  // Override auto-generated action name

createAuditExtension(options)

| Option | Type | Default | Description | |--------|------|---------|-------------| | trackedModels | string[] | — | Whitelist of Prisma model names to track | | ignoredModels | string[] | — | Blacklist (used when trackedModels is not set) | | sensitiveFields | string[] | [] | Fields to mask as [REDACTED] in diffs | | primaryKey | Record<string, string> | { *: 'id' } | Map of model name to primary key field name |

Schema Utilities

| Function | Description | |----------|-------------| | getAuditTableSQL() | Returns raw SQL string for creating audit_logs table + rules + indexes | | getAuditTableStatements() | Returns SQL split into individual executable statements | | applyAuditTableSchema(prisma) | Executes the schema SQL statement by statement via Prisma |

Transaction Model

| Path | Caller tx participation | Audit insert | |------|------------------------|--------------| | Automatic tracking (extension) | Yes — query(args) joins caller's $transaction | Best-effort — runs after business write, warns on failure | | Manual logging (log(input, tx)) | Yes — when tx provided | Participates in provided transaction | | Manual logging (log(input)) | No | Independent write via base client |

The automatic extension uses Prisma's query(args) callback, which preserves the caller's transaction context. The audit insert runs separately via the base client and does not block or fail the business operation. If audit insert fails, a warning is logged.

Multi-Tenancy

If @nestarc/tenancy is installed, tenant_id is automatically included in all audit records and query filters.

| Scenario | Behavior | |----------|----------| | Not installed | tenant_id is null, library works normally | | Installed, context available | tenant_id auto-injected | | Installed, context fails | Warning logged, tenant_id falls back to null | | tenantRequired: true + context fails | log() and query() throw an error |

Development

Prerequisites

  • Node.js 18+
  • Docker (for E2E tests)

Setup

npm install
npm run build

Run tests

# Unit tests
npm test

# E2E tests (starts Docker PostgreSQL automatically)
npm run test:e2e:full

# Cleanup
npm run test:e2e:teardown

License

MIT