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.8.0

Published

NestJS audit logging with automatic PostgreSQL tracking and transactional manual logging for PostgreSQL and SQL Server

Readme

NestJS audit logging with Prisma — @nestarc/audit-log

npm version npm downloads CI License: MIT

Record who changed what, and when, in a NestJS application. Commit manual business events with application writes and query tenant-scoped history on PostgreSQL or SQL Server. PostgreSQL also supports automatic before/after diffs, export, and delivery to external services.

The Quick Start and public-API examples target @nestarc/[email protected] and pin that version. Version 0.8.0 adds managed manual transactions, optional event validation, operation diagnostics, and SQL Server history queries. See CHANGELOG, the 0.8.0 release notes, and the feature guide. Versioned source documentation describes the target package; registry publication is verified separately. Maintainers can also verify a locally packed candidate; verify its source and integrity separately from the registry artifact even when the version matches.

Use SQL Server support with explicit provider: 'sqlserver' for same-transaction events, history reads, and SQL Server storage utilities. The Quick Start and automatic tracking examples below remain PostgreSQL-specific.

Choose your starting point

| Your application | Start here | Verify the first result | |---|---|---| | PostgreSQL: keep existing transactions and add a business event | Manual event adoption | Runnable manual smoke: actor, tenant, masking, and rollback | | PostgreSQL: record supported model changes automatically | Quick Start | Runnable HTTP flow: write, authorized history, and rollback | | SQL Server: record a business event in the same transaction | SQL Server Quick Start | Commit an event, verify rollback, and read authorized history through application-owned SQL |

Use one event or one tracked model first. The adoption guide shows how to expand after actor, tenant, redaction, and transaction behavior are verified.

For the managed transaction helper, event validation, diagnostics, and SQL Server query APIs, see new in 0.8.0 and the feature guide.

Choose atomic-required for automatic tracking: supported tracked writes and their audit rows commit or roll back together inside withAuditTransaction(). Supported tracked writes outside the helper are rejected. Explicit best-effort is non-atomic: rollback can leave orphan success rows and transaction-local diffs can be stale. Coverage is limited to supported operations through the audited client; see tracking boundaries.

Contents

Requirements

  • An existing NestJS application using NestJS 10, 11, or 12.0.1+
  • PostgreSQL with Prisma 7 (primary), or Prisma 5/6 legacy peer compatibility
  • Alternatively, SQL Server 2025 with Prisma CLI/client/MSSQL adapter 7.10.0 for manual logging; follow its separate setup and support boundaries
  • Node.js 22.23.4+ within the 22.x line, or 24.22.1+ within the 24.x line

These minimum versions include the Node.js fix for CVE-2026-111497 in its bundled Brotli decoder. See the October 2026 Node.js security release. Updating npm dependencies or the package's engines field does not replace the running Node binary. Update the deployment runtime, rebuild images or deployment artifacts, and restart processes.

NestJS 12's core packages are ESM-only. This package remains CommonJS-compatible on the supported Node.js versions through Node's require(esm) interoperability. NestJS 12.0.0 is excluded because its published framework peer metadata was corrected in 12.0.1. The example below uses CommonJS Prisma output; match moduleFormat to your application's module system.

Quick Start

For a complete standalone application with schema, migrations, a demo Guard, and smoke assertions, use examples/quick-start. Copy that directory to your own workspace, install its pinned dependencies, and run npm run db:setup, npm run smoke:manual, and npm run smoke. It needs no library checkout build. The manual smoke uses only a base Prisma client and the Nest module.

The integration below assumes NestJS is already installed, DATABASE_URL points to your PostgreSQL database, and your Prisma schema contains a User model with id, name, email, and password, mapped to the users table. The runnable schema provides that model.

1. Install and generate Prisma

In your existing NestJS application, install version 0.8.0 and Prisma 7 runtime dependencies:

npm install --save-exact @nestarc/[email protected]
npm install @prisma/client@7 @prisma/adapter-pg@7 pg dotenv
npm install --save-dev prisma@7

The runnable example pins its complete dependency tuple. Existing Prisma 5/6 applications should keep their generation setup and compatible Prisma dependencies. Contributors testing local changes use the separate build/pack instructions.

# .env — replace with your local database credentials
DATABASE_URL=postgresql://postgres:postgres@localhost:5432/audit_demo
// prisma/schema.prisma — keep your application's model definitions below these blocks
generator client {
  provider     = "prisma-client"
  output       = "../src/generated/prisma"
  moduleFormat = "cjs"
}

datasource db {
  provider = "postgresql"
}
// prisma.config.ts
import 'dotenv/config';
import { defineConfig, env } from 'prisma/config';

export default defineConfig({
  schema: 'prisma/schema.prisma',
  datasource: { url: env('DATABASE_URL') },
});

After adding or updating your application models:

npx prisma migrate dev --name init
npx prisma generate

Prisma 7 reads the CLI datasource URL from prisma.config.ts. Import the generated Prisma namespace from the configured output and pass { Prisma } to this package. Prisma 5/6 applications using prisma-client-js can keep their existing generation/connection setup and import from @prisma/client; the audit APIs are the same.

2. Provide the base and audited client views

The base Prisma client stores and queries audit rows. The extended view intercepts your application's business writes. Both views share the underlying Prisma client; do not create another connection pool solely for the extension. Build module, extension, and table options from one definition:

// audit.config.ts
import { defineAuditConfig } from '@nestarc/audit-log';
import { Prisma } from './generated/prisma/client';

export const auditConfig = defineAuditConfig({
  shared: {
    provider: 'postgresql',
    tableName: 'audit_logs',
    prismaModule: { Prisma },
    actorRequired: true,
    sensitiveFields: ['password', 'ssn'],
  },
  module: {
    actorExtractionStage: 'interceptor',
    actorExtractor: req => ({
      id: req.user?.id ?? null,
      type: req.user ? 'user' : 'system',
      ip: req.ip,
    }),
  },
  extension: {
    consistency: 'atomic-required',
    trackedModels: ['User'],
    databaseMapping: { User: { tableName: 'users' } },
  },
});

actorRequired: true rejects writes without an identifiable actor. Your authentication Guard must populate req.user.id; background jobs use AuditContext.runAs() with a system ID. For tenant-owned data, also set tenantRequired and a trusted tenantResolver in shared; the runnable example demonstrates both.

// prisma.service.ts
import 'dotenv/config';
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { PrismaPg } from '@prisma/adapter-pg';
import { createAuditedClient } from '@nestarc/audit-log';
import { PrismaClient } from './generated/prisma/client';
import { auditConfig } from './audit.config';

@Injectable()
export class PrismaService implements OnModuleInit, OnModuleDestroy {
  readonly base = new PrismaClient({
    adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL! }),
  });

  readonly client = createAuditedClient(this.base, auditConfig.extensionOptions);

  async onModuleInit() { await this.base.$connect(); }
  async onModuleDestroy() { await this.base.$disconnect(); }
}
// prisma.module.ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({ providers: [PrismaService], exports: [PrismaService] })
export class PrismaModule {}

Create the audit table through a migration or setup script using the base client:

import { applyAuditTableSchema } from '@nestarc/audit-log';
import { auditConfig } from './audit.config';

await applyAuditTableSchema(prismaService.base, auditConfig.schemaOptions);

getAuditTableSQL(auditConfig.schemaOptions) provides the SQL for your migration tool. The runnable example includes this setup; production deployments should apply it through their migration process. See storage setup for partitions and database permissions.

3. Register the NestJS module

// app.module.ts
import { Module } from '@nestjs/common';
import { AuditLogModule } from '@nestarc/audit-log';
import { PrismaModule } from './prisma.module';
import { PrismaService } from './prisma.service';
import { auditConfig } from './audit.config';

@Module({
  imports: [
    PrismaModule,
    AuditLogModule.forRootAsync({
      inject: [PrismaService],
      useFactory: (prisma: PrismaService) => ({
        ...auditConfig.moduleOptions,
        prisma: prisma.base,
      }),
    }),
  ],
})
export class AppModule {}

actorExtractionStage: 'interceptor' reads the actor after authentication Guards populate req.user and before the route handler runs. This option is introduced in 0.6.0. Published v0.5.0 extracts in middleware: authenticate before that middleware or provide an extractor that can resolve identity there. The default remains 'middleware' for compatibility. Writes inside Guards precede interceptor extraction; use earlier authentication middleware if those writes need an actor. See actor context for manual interceptor binding and background jobs.

defineAuditConfig() keeps table, actor, tenant, masking, and error settings aligned across consumers. It does not open a connection or apply DDL. Direct module and extension registration remains supported, but those options are independent: setting a policy on the module alone does not configure the automatic extension.

Read and write audit records

Use supported model writes inside withAuditTransaction(). For example, in an injectable service with PrismaService and AuditService in its constructor:

import { Injectable } from '@nestjs/common';
import { AuditService } from '@nestarc/audit-log';
import { PrismaService } from './prisma.service';

@Injectable()
export class UserService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly audit: AuditService,
  ) {}

  async createUser(data: { name: string; email: string; password: string }) {
    const user = await this.prisma.client.withAuditTransaction((tx) =>
      tx.user.create({ data }),
    );
    const page = await this.audit.query({
      targetType: 'User',
      targetId: user.id,
      action: 'User.*',
      source: 'auto',
      includeTotal: false,
    });
    return { userId: user.id, audit: page.entries };
  }
}

Register UserService in its Nest module's providers when using this service fragment. The runnable example demonstrates the same flow in its UserController. For an authenticated creation, the audit entry includes fields such as:

{
  "actorId": "demo-user",
  "actorType": "user",
  "action": "User.created",
  "targetType": "User",
  "source": "auto",
  "changes": {
    "name": { "after": "Alice" },
    "password": { "after": "[REDACTED]" }
  },
  "result": "success"
}

This shows selected fields; IDs, timestamps, and other model fields are also present. Action names preserve Prisma model casing: User.created, User.updated, User.deleted. Wildcard matching is case-sensitive, so user.* does not match User.created.

Manual events use the action name you supply and have source: 'manual':

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

const filters = { action: 'invoice.*', source: 'manual' as const };
const page = await auditService.query({ ...filters, limit: 50, includeTotal: false });
if (page.nextCursor) {
  await auditService.query({
    ...filters,
    cursor: page.nextCursor,
    limit: 50,
    includeTotal: false,
  });
}

query() returns { entries, nextCursor, hasMore } and, by default, total. Setting includeTotal: false skips COUNT(*) and omits total. Keep the same filters on every cursor page; cursors contain a position, not filters. For atomic manual events, pass the same transaction client to AuditService.log(input, tx) as your business write. See the API reference for query options, result types, and manual transaction examples.

Configuration and context

  • trackedModels is an allowlist of Prisma model names; [] audits no models. When omitted, ignoredModels is a denylist. Omitting both tracks all models and emits a one-time warning.
  • @NoAudit() suppresses automatic tracking; @AuditAction() overrides automatic action names. @AuditReason() adds a reason to context metadata. They apply to handlers or controllers.
  • AuditContext.runAs(actor, callback) supplies an actor for worker/cron code. HTTP extraction does not run in background jobs. Context examples cover actor, metadata, reason, and tenant setup.
  • Tenant scope is explicit where needed: query()/getById() accept tenantId or deliberately authorized allTenants: true; scan()/exportCsv() require exactly one of them. A custom tenantResolver replaces the optional @nestarc/tenancy lookup, including when it returns null.

Tracking and storage boundaries

| Capability | Contract | |---|---| | Automatic single-record tracking | create, update, delete, and upsert through the audited client; supported operations use the helper's transaction | | Bulk writes | Atomic deleteMany records individual rows within maxBatchRecords; atomic createMany/updateMany are rejected; see the bulk matrix | | Returning bulk writes | Tracked createManyAndReturn/updateManyAndReturn are rejected in atomic mode; best-effort warns and produces no automatic audit row | | Nested writes | Use explicit related-model writes inside the helper for tracked relations; see nested writes | | Other writes | Base-client writes, raw SQL, unsupported operations, and database-side cascades/triggers are outside automatic record coverage | | Append-only storage | Default row triggers block UPDATE/DELETE; runtime permissions and a separate owner are required to prevent privileged bypasses or TRUNCATE | | Export and delivery | CSV and checkpointed streams scan rows by (created_at, id); this is not a commit-ordered change feed, and late commits can be missed behind a checkpoint |

These boundaries matter when deciding whether the records meet your application's audit requirements. See transactions, database hardening, and export guarantees.

Guides and API reference

| Task | Reference | |---|---| | Adopt one event or model, including a role-change workflow | Adoption guide | | Run a complete integration | Quick Start application | | Configure the module, extension, queries, and manual logging | API reference | | Understand transactions, bulk operations, soft-delete, and the v0.5 migration | Tracking contracts | | Set actors, reasons, correlation metadata, and tenant scope | Context and tenancy | | Create tables, manage partitions, prune, and configure privileges | Storage and retention | | Use SQL Server manual events and table installation | SQL Server guide | | Export CSV or deliver logs to HTTP, object storage, Datadog, or Splunk | Export and streams | | Use the package from an AI-assisted workflow | Agent guide | | Check release changes | CHANGELOG | | Review 0.8.0 changes, validation, and support limits | 0.8.0 release notes | | Use managed manual transactions, validation, diagnostics, and SQL Server history | Feature guide |

AI agents can start with the agent guide, llms.txt, the runnable example, and the public TypeScript exports in @nestarc/audit-log. Historical plans under docs/ preserve earlier designs and are marked as historical; use the guides above for current behavior.

Tested ecosystem versions

The optional PostgreSQL compatibility fixture verifies this last-known-good published tuple:

| Component | Exact verified version | |---|---| | @nestarc/tenancy | 0.15.0 | | @nestarc/audit-log | 0.5.0 | | @nestarc/soft-delete | 0.7.3 | | Runtime lane | Node 24, NestJS 11.1.18, Prisma 7.9.1, PostgreSQL 16 |

This is a coordinated integration test tuple, not the full peer-support matrix or proof of 0.8.0 integration support. Standalone audit-log CI and publication do not depend on this optional soft-delete integration; core validation, packed documentation consumers and MSSQL checks remain required.

The manually dispatched Ecosystem compatibility workflow checks either that published tuple or the packed audit-log candidate with an explicitly selected exact published soft-delete version. Strict peer checks and lifecycle assertions must pass before a tuple is described as supported. Soft-delete 0.7.4 excludes audit-log 0.8.0 from its peer range, so that combination remains unsupported. See compatibility verification for commands, artifact checks and the separate release procedure.

New in 0.8.0

Version 0.8.0 includes the following additions to the 0.7.1 API:

  • Managed manual transactions: AuditService.withManualTransaction() supplies a typed transaction and bound writer. Caught audit failures still prevent commit, started writes settle before completion, and expired writers reject.
  • Event validation: opt into strictManualValidation and maxMetadataBytes to validate inputs and enforce a final redacted JSON budget in UTF-8 bytes before INSERT.
  • Diagnostics and capabilities: onAuditOperation reports outcomes and timing without event contents; diagnostic callbacks are isolated from writes. getCapabilities() describes the configured provider's supported APIs without database access.
  • SQL Server history and storage tools: query() and getById() support tenant-scoped reads, full-precision cursors and exact text filtering. Optional read indexes and read-only storage inspection help prepare the schema for history queries.

See the feature guide for configuration, examples and boundaries. SQL Server automatic tracking, scan/export and retention remain unsupported.

Performance and development

Performance depends on operation, consistency mode, transaction overhead, schema, and workload. Use the reproducible benchmark to compare direct writes, plain transactions, atomic-required, and explicit best-effort. No single latency number represents every mode.

The development candidate recorded in the 2026-10-26 local validation passed seven individual PostgreSQL scenarios against published 0.7.1. The limits stayed at mean latency ≤+10%, P95 ≤+15%, and throughput ≥−10%. Automatic create used a predeclared 32-process comparison with identical package calibration, balanced execution and confidence bounds; the other six scenarios passed both original formal runs.

For automatic create, the one-sided 95% bounds on geometric mean process ratios were +2.83% mean latency, +4.93% P95 latency, and −2.75% throughput. This is approval for the recorded local candidate and scenarios, not a production SLO or SQL Server performance claim. The repository validation record, docs/2026-10-26-performance-resolution.md, identifies the exact artifact, environment, retained sequential results and historical failure.

For repository setup and lint, typecheck, unit, PostgreSQL E2E, and ecosystem tests, see development commands.

License

MIT — see LICENSE.