Build on the supported package contract
Use @absolutejs/audit-postgres through its supported public entry points.
@absolutejs/audit-postgresv0.1.4betaObservabilityPostgres-backed AuditSink for @absolutejs/audit with first-class Drizzle and tagged-template adapters, native JSONB metadata, and indexed history.
bun add @absolutejs/audit-postgresPostgres-backed AuditSink for @absolutejs/audit with first-class Drizzle and tagged-template adapters.
Durable, queryable, and uses the same metadata.__integrity field for tamper-evidence as the in-memory sink — jsonb preserves the chain through the round-trip.
Drizzle
Re-export the package-owned table from your application schema so your normal Drizzle migration workflow owns its lifecycle:
Then pass any Drizzle Postgres database:
When the Drizzle database uses Bun SQL, opt into the Bun-native JSONB mapping and sink so object and array parameters are not pre-stringified:
auditBunSqlDrizzleSchema provides the same mapping as a schema object. The default auditEvents, auditDrizzleSchema, and createDrizzleAuditSink exports retain their existing postgres.js-compatible codec. Select the mapping from the configured database driver rather than the JavaScript runtime: postgres.js can itself run under Bun.
The Drizzle adapter deliberately never runs DDL at application runtime. It exports auditEvents and auditDrizzleSchema, uses native typed JSONB, and implements the same recent-window, actor, kind, time-range, and prune behavior as the tagged-template adapter.
If your application intentionally keeps package-owned tables out of its Drizzle schema, apply this adapter's migration through your existing PostgreSQL client instead:
The runner uses an atomic, digest-checked 0001_init migration for each validated table name and records it in audit_postgres_migrations. Concurrent runners serialize with a transaction-scoped advisory lock. Existing tables created through the lazy adapter are safely adopted by the first runner. Changing an applied migration's SQL fails closed. The runner never creates or closes the injected client. getAuditPostgresSchemaSql({ table? }) exposes the same deterministic, validated SQL for migration systems that apply SQL themselves, while auditPostgresMigrationPlan({ table? }) exposes the numbered ID, SQL, and SHA-256 digest.
postgres.js
Neon serverless (Lambda / Workers)
Same adapter; the only difference is the SQL tag template.
The adapter creates this lazily on first append / list / prune:
metadata is jsonb — the __integrity chain field rides here untouched
by the round-trip.
All three indexes are partial-or-full to cover the common filter paths
(recent-first lists; per-kind filters; per-actor lookups).
The table name is customizable via the table option (strictly validated
against /^[a-zA-Z_][a-zA-Z0-9_]$/ and limited to 53 characters to defend against injection and PostgreSQL index-name truncation — the identifier has to be interpolated into the DDL, not parameterized).
Pass ensureSchema: false if you manage migrations yourself.
Returns a standard AuditSink implementing append, list (with kind / actor / since / until / limit filters), and prune(before).
Lazy schema. First call to any method runs the DDL once; subsequent
calls skip.
Portable row counts. prune uses RETURNING id and counts the returned
array, so it works the same on postgres-js (which exposes .count) and Neon serverless (which doesn't expose row count the same way).
bigint at column. Wall-clock Date.now() won't exceed
Number.MAX_SAFE_INTEGER for centuries; the row is normalized back to a JS number on read regardless of driver configuration.
metadata jsonb-as-string fallback. Some driver setups return jsonb as
a string; the sink parses on read so callers never see a string.
The Drizzle adapter's PGlite suite is self-contained:
The tagged-template compatibility suite uses a real Postgres service:
Override the DSN via AUDIT_PG_TEST_URL to point at your own Postgres.
Outcomes
Use @absolutejs/audit-postgres through its supported public entry points.
Hardening checklist
Follow in order
The Drizzle adapter's PGlite suite is self-contained:
bun test tests/drizzleAuditSink.test.tsRe-export the package-owned table from your application schema so your normal Drizzle migration workflow owns its lifecycle:
export { auditEvents } from "@absolutejs/audit-postgres";Re-export the package-owned table from your application schema so your normal Drizzle migration workflow owns its lifecycle:
import { createAudit, withIntegrity } from "@absolutejs/audit";
import { createDrizzleAuditSink } from "@absolutejs/audit-postgres";
const audit = createAudit({
sinks: [
withIntegrity(createDrizzleAuditSink({ db }), {
secret: process.env.AUDIT_SECRET,
writerId: "shard-A",
}),
],
});Supported entry points declared by this package manifest.
Package entry point declared in package.json.
Scripts declared by this package manifest.
Search the declarations exported by the current package type files. Expand a symbol to inspect its source-backed signature.