Record history
Vaultbase records every write to a history-enabled collection in an
append-only vaultbase_record_history table. Each row captures the record’s
post-write state (or pre-delete state on delete) plus the actor that
triggered the change.
Enabling history on a collection
Section titled “Enabling history on a collection”Set history_enabled: true when creating or updating the collection:
PATCH /api/v1/collections/<id>{ "history_enabled": true }…or check the History toggle in the admin schema editor.
When the flag is off, the records API skips the history write entirely — no storage cost, no overhead. Flipping it on starts capturing immediately but does not backfill prior writes.
Storage shape
Section titled “Storage shape”CREATE TABLE vaultbase_record_history ( id TEXT PRIMARY KEY, collection TEXT NOT NULL, record_id TEXT NOT NULL, op TEXT NOT NULL, -- "create" | "update" | "delete" snapshot TEXT NOT NULL, -- JSON-encoded record state actor_id TEXT, -- caller id, or NULL actor_type TEXT, -- "user" | "admin" | NULL at INTEGER NOT NULL -- unix seconds);snapshot is the post-write record on create / update and the pre-delete
record on delete.
Browse
Section titled “Browse”GET /api/<col>/<id>/history?page=1&perPage=50Authorization: Bearer <token>Gated by the parent record’s view_rule — if you can view the live record,
you can read its history. Pages are ordered newest-first.
Restore
Section titled “Restore”POST /api/<col>/<id>/restore?at=<unix-seconds>Authorization: Bearer <admin-token>Admin-only. Restores the record to the most recent snapshot at or before
the given timestamp. The live record’s updated_at advances and the
restore is itself logged as a fresh update row in history.
Restoring a deleted record returns 409 in v1 — createRecord mints its
own id, so re-creating with the original id is unsupported. Workaround:
POST /api/<col> with the snapshot body (you’ll get a new id).
Retention
Section titled “Retention”History rows accumulate forever by default. Drop ancient entries via a cron job:
// cron: 0 3 * * *const days = 90;const cutoff = Math.floor(Date.now() / 1000) - days * 24 * 3600;const removed = await ctx.helpers.db.exec( "DELETE FROM vaultbase_record_history WHERE at < ?", cutoff,);ctx.helpers.log(`pruned ${removed.changes} history rows older than ${days}d`);What history does not track
Section titled “What history does not track”- File contents. Only the filename string in the snapshot. Restoring brings back the field value but the underlying file must still exist on disk / S3.
- Schema changes. This is a record-level log; collection schema diffs live in the migrations subsystem.
- Reads. Only writes are persisted.
Performance
Section titled “Performance”Each enabled write does one extra INSERT (parameterised, transactional)
plus serialising the snapshot to JSON. Indexes cover (collection, record_id, at)
and (at) for prune sweeps, so the read endpoints are O(log n).