Tracevault
Open source · MIT · Node.js + TypeScript

Eventos de auditoría personalizados, persistidos con consistencia.

Una librería ligera de auditoría para Node.js y TypeScript. Definí tus propios eventos, mapeá scopes lógicos a tablas en PostgreSQL, persistí con validación y enmascarado estrictos — y leé con audit.query o SQL directo.

npm install tracevault pg
emit.ts
import { startTracevault } from "tracevault"

const audit = await startTracevault({
  driver: "postgres",
  connectionString: process.env.DATABASE_URL_WRITE!,
  defaultScope: "default",
  scopes: { default: { tableName: "audit_logs" } },
  bootstrap: { ensureSchema: true },
  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" },
})
Qué obtienes

Estructura suficiente para consultar. Nada más.

Tracevault se encarga de lo aburrido pero crucial del audit logging para que te concentres en lo que cada evento significa en tu dominio.

01

Eventos personalizados primero

Vos nombrás los eventos. Vos definís data y meta. Sin catálogo impuesto — Tracevault valida, enmascara y normaliza lo que enviás.

02

Estructurado por diseño

Una forma persistida pequeña y estable: event, actor, target, data, meta, timestamps, correlation. Suficiente para indexar y razonar. Nada más.

03

Scopes con nombre

Mapeá claves lógicas a tablas físicas al iniciar con scopes. Usá getScope("users") para escribir y getScope("users").query para leer — una app, pools compartidos.

04

Bootstrap de schema

startTracevault puede ejecutar DDL idempotente para cada tabla en scopes por defecto. Preferí bootstrap: { ensureSchema: false } cuando tus migraciones manejan el schema.

05

Read API integrada

audit.query en el mismo objeto de la app — filtros de igualdad, ventanas de tiempo, paginación determinística, errorsOnly. Sin import path separado; consultas exóticas quedan en SQL.

06

Helper diff opcional

emitDiff calcula un diff superficial de campos y guarda { before, after, diff } como evento normal. Sigue siendo azúcar sobre emit.

Cómo funciona

Cinco pasos de cero a eventos guardados.

La superficie se mantiene chica: una app, scopes explícitos, escrituras predecibles, schema flexible y el camino de lectura que elijas.

  1. 01

    Iniciá la app

    await startTracevault({ scopes, defaultScope, … }). readConnectionString o readPool opcionales para roles de lectura en producción.

  2. 02

    Nombrá tus scopes

    Listá cada tabla física en scopes una sola vez. Usá audit.emit en el scope default o audit.getScope("users") para otros dominios.

  3. 03

    Emití eventos custom

    Llamá emit() o emitDiff(). Los helpers de correlación alinean trazas de request entre emits.

  4. 04

    Schema a tu manera

    Dejá que ensureSchema cree tablas, o aplicá sql/001–003 / generateInitSql cuando las migraciones manejan el DDL.

  5. 05

    Leé como quieras

    Usá audit.query o getScope(name).query — o pg, Drizzle, Prisma, Knex, SQL directo para todo lo demás.

Ejemplos

API de escritura central, en tres snippets.

Scopes con nombre, helpers de correlación, bootstrap de schema y audit.query están en la documentación — acá va el camino feliz más corto.

Iniciar la app

await startTracevault con scopes y readConnectionString opcional. maskFields se aplica recursivamente en data, meta, before y after.

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

Emitir un evento custom

Solo event es obligatorio. actor, target, data, meta, correlationId y requestId son opcionales — usá lo que tenga sentido.

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

Emitir un cambio de objeto

emitDiff es azúcar sobre emit. Calcula un diff superficial y persiste { before, after, diff } como payload del evento.

update.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 } }
// }

Para scopes con nombre, opciones de schema y audit.query, mirá la Documentación.

Filosofía

Una librería, no un framework.

La mayoría de herramientas de auditoría imponen una visión del mundo. Tracevault se aparta y te da un lugar confiable para aterrizar eventos que ya sabés nombrar.

  • 01

    No impone un catálogo de eventos.

  • 02

    No requiere un ORM.

  • 03

    No intenta ser un framework.

  • 04

    Se mantiene mínima, tipada y predecible.

  • 05

    Mantiene audit.query acotada a propósito — sin DSL de queries.

Tracevault no define tu catálogo de eventos. Te da una forma consistente y confiable de guardar tus eventos de auditoría custom. Cuando necesitás lecturas indexadas, audit.query se mantiene deliberadamente acotada — el resto pertenece al SQL en el que ya confiás.
Actualización

Migrando desde 0.x

1.x unifica escrituras y lecturas en un solo objeto app. El schema y la forma del evento no cambian — en su mayoría son renombres de imports y config.

  • createTracevault(config)await startTracevault({ scopes, defaultScope, … })
  • root.scope({ tableName })scopes al iniciar + audit.getScope("name")
  • import from tracevault/queryaudit.query y getScope(name).query en la misma app
  • Solo SQL manualbootstrap.ensureSchema: true por defecto (o mantené tus migraciones)

Notas de release y breaking changes: CHANGELOG. Siguiente: Install y Writing.