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 pgimport { 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" },
})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.
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.
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.
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.
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.
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.
Helper diff opcional
emitDiff calcula un diff superficial de campos y guarda { before, after, diff } como evento normal. Sigue siendo azúcar sobre emit.
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.
- 01
Iniciá la app
await startTracevault({ scopes, defaultScope, … }). readConnectionString o readPool opcionales para roles de lectura en producción.
- 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.
- 03
Emití eventos custom
Llamá emit() o emitDiff(). Los helpers de correlación alinean trazas de request entre emits.
- 04
Schema a tu manera
Dejá que ensureSchema cree tablas, o aplicá sql/001–003 / generateInitSql cuando las migraciones manejan el DDL.
- 05
Leé como quieras
Usá audit.query o getScope(name).query — o pg, Drizzle, Prisma, Knex, SQL directo para todo lo demás.
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.
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.
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.
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.
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.
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 appSolo SQL manualbootstrap.ensureSchema: true por defecto (o mantené tus migraciones)