Tracevault
OverviewInstallWritingQueryModelo de datos

Iniciar la app

startTracevault es async y devuelve un TracevaultApp. Configurá scopes (nombre lógico → tableName), defaultScope, enmascarado y defaults sync/async. El scope default es el que usan audit.emit y audit.query.

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,
})

Scopes con nombre

Las tablas se eligen al iniciar en scopes — no por evento. Usá audit.getScope("users") para escribir en otra tabla. Cada scope no-default tiene su propia cola async en el 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" },
})

pg.Pool compartido (TLS, RDS)

Pasá pool y readPool opcionales para que bootstrap y queries runtime reutilicen tu config SSL. Tracevault solo cierra pools que creó; cerrá los inyectados vos después de 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

Solo event es obligatorio. Todo lo demás es opcional — incluyendo correlationId / requestId para trazar entre 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",
})

Helpers de correlación

Exportados desde 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" },
})

Outcomes estructurados (opcional)

Claves opcionales dentro de data populan columnas generadas (outcome, error.code → error_code, severity). No las valida la librería — son patrones para 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

Diff superficial a nivel top entre before y after. Sigue siendo azúcar sobre 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 espera el insert. async usa una FIFO in-process — eventos bufferizados se pierden si el proceso termina antes de flush(). Al apagar: await audit.flush() luego await audit.close().

Siguiente

Opciones de schema en Install. lecturas en Query columnas en Modelo de datos.