Tracevault
OverviewInstallWritingQueryData model

Start the app

startTracevault is async and returns a TracevaultApp. Configure scopes (logical name → tableName), defaultScope, masking, and sync/async defaults. The default scope is what audit.emit and audit.query target.

audit.ts
import { startTracevault } from "tracevault"

const audit = await startTracevault({
  driver: "postgres",
  connectionString: process.env.DATABASE_URL_WRITE!,
  readConnectionString: process.env.DATABASE_URL_READ,
  defaultScope: "default",
  scopes: {
    default: { tableName: "audit_logs" },
    users: { tableName: "audit_user_events" },
  },
  bootstrap: { ensureSchema: true },
  maskFields: ["password", "token", "pin"],
  defaultMode: "sync",
  environment: process.env.NODE_ENV,
})

Named scopes

Tables are chosen at startup in scopes — not per event. Use audit.getScope("users") for writes to another table. Each non-default scope has its own async queue on the write path.

scopes.ts
const userAudit = audit.getScope("users")

await userAudit.emit({
  event: "user.profile.updated",
  actor:  { id: "user_123", type: "user" },
  target: { id: "user_123", type: "user" },
  data:   { field: "phone" },
})

await audit.getScope("default").emit({
  event: "payment.intent.created",
  actor:  { id: "merchant_42", type: "merchant" },
  target: { id: "payment_987", type: "payment" },
  data:   { amount: 1200, currency: "UYU" },
})

Shared pg.Pool (TLS, RDS)

Pass optional pool and readPool so bootstrap and runtime queries reuse your SSL configuration. Tracevault ends only pools it created; close injected pools yourself after await audit.close().

pool.ts
import { startTracevault } from "tracevault"
import { Pool } from "pg"

const pool = new Pool({
  connectionString: process.env.DATABASE_URL!,
  ssl: { rejectUnauthorized: true },
})

const audit = await startTracevault({
  driver: "postgres",
  connectionString: process.env.DATABASE_URL!,
  pool,
  defaultScope: "default",
  scopes: { default: { tableName: "audit_logs" } },
})

// End injected pools yourself after await audit.close()

Emit

Only event is required. Everything else is optional — including correlationId / requestId for tracing across emits.

emit.ts
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",
})

Correlation helpers

Exported from tracevault: randomCorrelationId, readCorrelationIdHeader, resolveCorrelationId.

correlation.ts
import {
  randomCorrelationId,
  readCorrelationIdHeader,
  resolveCorrelationId,
} from "tracevault"

const correlationId = resolveCorrelationId(
  readCorrelationIdHeader((name) => req.headers.get(name)),
  randomCorrelationId,
)

await audit.emit({
  event: "checkout.started",
  correlationId,
  data: { cartId: "cart_1" },
})

Structured outcomes (optional)

Optional keys inside data populate generated columns (outcome, error.code → error_code, severity). Not validated by the library — patterns for dashboards.

failure.ts
// Optional keys inside data — mirrored to generated columns after migrations 002–003:
await audit.emit({
  event: "auth.login.failed",
  actor: { id: "user_123", type: "user" },
  data: {
    outcome: "failure",
    severity: "warning",
    error: { code: "AUTH_INVALID_CREDENTIALS", stage: "credential_check" },
  },
})

emitDiff

Shallow top-level diff between before and after. Still sugar around emit.

diff.ts
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 } }
// }

Sync vs async

sync awaits the insert. async uses an in-process FIFO — buffered events are lost if the process exits before flush(). On shutdown: await audit.flush() then await audit.close().

Next

Schema options on Install. reads on Query columns on Data model.