Audit log
Copy page
The audit log is on by default: nizhal migrate creates _nizhal_audit_log and
createNizhalServer appends one immutable row per applied mutation. To opt out — e.g. a
cost-sensitive or very high-write app that doesn’t want an unbounded log — set audit: false in the
same server configuration used by both:
export default { db: process.env.DATABASE_URL!, schema, syncRules, mutators, auth, // audit: false, // opt out: skips the table and all audit writes (zero overhead)};With the default, migration creates _nizhal_audit_log and every applied mutation appends a row in
the same transaction (rollback drops both). Set audit: false and Nizhal creates no table and
executes no audit write. A storage adapter that doesn’t support audit degrades gracefully — no error,
no write.
What is stored
Section titled “What is stored”Each successfully applied mutation appends one row in the mutation’s database transaction. A
rollback therefore removes both the business changes and the audit append. The row contains the
shared, totally ordered rowVersion, client mutation ID, mutator name, parsed arguments, actor,
client ID, mutation ID, HLC, affected buckets, and creation time. Duplicate mutation replay does not
append another row.
Nizhal exposes no audit update or delete operation. Postgres audit rows use the same
_nizhal_next_row_version() allocator as sync cursors. The exported libsqlAuditStorage primitive
uses _nizhal_row_versions for the equivalent shared allocation inside a libSQL write transaction;
consumer-supplied libSQL StorageAdapter implementations can use that primitive at their commit
chokepoint.
Query from the server
Section titled “Query from the server”const entries = await storage.getAuditLog?.({ actor: { userId: "user-123" }, buckets: ["shop-456"], sinceVersion: "1000", // exclusive untilVersion: "2000", // inclusive limit: 100,});Results are ordered by rowVersion ascending. The default limit is 100 and the maximum is 1,000.
Actor filters match the supplied actor fields; bucket filters match any supplied bucket.
GET /nizhal/audit exposes the same query to server administrators. Authenticate with the bearer
token configured by NIZHAL_ADMIN_PASSWORD, as for /nizhal/stats:
GET /nizhal/audit?bucket=shop-456&sinceVersion=1000&limit=100Authorization: Bearer <NIZHAL_ADMIN_PASSWORD>The endpoint is disabled when audit is off and is not a client sync endpoint.
Deliberate v1 non-goals
Section titled “Deliberate v1 non-goals”Audit argument redaction and retention/pruning are not configurable in v1. Enabling audit stores the full parsed mutation envelope, so do not put secrets in mutation arguments. Full event sourcing, time-travel reconstruction, branching, and client-facing audit queries are also out of scope.