Custom audit events, consistently persisted.
A lightweight audit event library for Node.js and TypeScript. You define the events. Tracevault validates, masks, and stores them reliably in PostgreSQL.
npm install tracevault pgimport { createTracevault } from "tracevault"
const audit = createTracevault({
driver: "postgres",
connectionString: process.env.DATABASE_URL!,
maskFields: ["password", "token"],
})
await audit.emit({
event: "product.price.updated",
actor: { id: "user_123", type: "user" },
target: { id: "product_456", type: "product" },
data: { oldPrice: 120, newPrice: 150, currency: "UYU" },
meta: { source: "admin-panel" },
})Enough structure to query. No more.
Tracevault handles the boring-but-crucial parts of audit logging so you can focus on what each event actually means in your domain.
Custom events first
You name the events. You own the shape of data and meta. No prescribed catalog, no compliance taxonomy you didn't ask for.
Structured by design
A small, stable persisted shape: event, actor, target, data, meta, occurredAt. Enough structure to query. No more.
PostgreSQL persistence
A JSONB-first schema with sensible indexes. Sync for durability, async for throughput. Zero runtime deps beyond pg.
Optional diff helper
emitDiff computes a shallow field diff between before and after, and stores { before, after, diff } as a normal event.
Four moving parts. That's the whole thing.
Tracevault is intentionally small. Create a client, emit events, let it persist them, query later with the tool you already use.
- 01
Create the client
Wire the PostgreSQL driver once at boot. Configure masking and default mode.
- 02
Emit custom events
Call emit() anywhere a business fact happens. You own the event name and payload.
- 03
Persist them reliably
Strict validation, recursive masking, sync or async writes against a JSONB table.
- 04
Inspect with your own tools
No lock-in. Query audit_logs with pg, drizzle, knex, prisma or raw SQL.
The whole public API, in three snippets.
No decorators. No magic. No ORM coupling. Just functions you call when something worth remembering happens.
Create the client
Wire the PostgreSQL driver once, at boot. maskFields is applied recursively to data, meta, before and after.
import { createTracevault } from "tracevault"
const audit = createTracevault({
driver: "postgres",
connectionString: process.env.DATABASE_URL!,
tableName: "audit_logs",
maskFields: ["password", "token", "pin"],
defaultMode: "sync",
environment: process.env.NODE_ENV,
})Emit a custom event
Only event is required. actor, target, data, meta, correlationId and requestId are optional — use what makes sense.
await audit.emit({
event: "auth.login.succeeded",
actor: { id: "user_123", type: "user" },
meta: { ip: "127.0.0.1", userAgent: "curl/8" },
correlationId: "req_abc",
})Emit an object change
emitDiff is sugar around emit. It computes a shallow field diff and persists { before, after, diff } as the event payload.
await audit.emitDiff({
event: "product.updated",
actor: { id: "user_123", type: "user" },
target: { id: "product_456", type: "product" },
before: { name: "Café", price: 120 },
after: { name: "Café", price: 150 },
})
// Persisted data:
// {
// "before": { "name": "Café", "price": 120 },
// "after": { "name": "Café", "price": 150 },
// "diff": { "price": { "before": 120, "after": 150 } }
// }A library, not a framework.
Most audit tools push a worldview. Tracevault stays out of your way and gives you a reliable place to land events you already know how to name.
- 01
Does not impose an event catalog.
- 02
Does not require an ORM.
- 03
Does not try to be a framework.
- 04
Stays minimal, typed, and predictable.
Tracevault does not define your event catalog. It gives you a consistent, reliable way to store your custom audit events.