Skip to content
VeritioDocs

File store

Kind
tutorial
For
developer · operator
Verified against
@veritio/storage@0.4.7 · file-store tests at veritio@c4100ee

The file store is the smallest durable OSS boundary. It writes event, edge, and commit chains into one operator-owned directory and can reopen them in a later process. It is useful for local tools, single-host services, and integration proofs; it is not a multi-writer database substitute.

Terminal window
bun add @veritio/core@0.4.7 @veritio/storage@0.4.7

The storage package receives its directory from your host. It does not discover paths or credentials from environment variables.

This checked fixture creates a fresh directory, appends two events, constructs a second store instance to model a process restart, reads the records, and verifies the event, edge, and commit chains.

src/examples/storage/file-store.ts
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { createFileEvidenceStore } from '@veritio/storage'
/**
* Proves that the published file store persists two tenant-scoped events across
* independent store instances and re-verifies all three local evidence chains.
*/
export async function persistReopenAndVerify() {
const directory = await mkdtemp(join(tmpdir(), 'veritio-file-store-'))
try {
const writer = createFileEvidenceStore(directory)
await writer.recordEvent({
id: 'evt_invoice_created_01',
occurredAt: '2026-08-09T12:00:00.000Z',
actor: { type: 'service', id: 'billing_api' },
action: 'invoice.created',
target: { type: 'invoice', id: 'inv_123' },
scope: { tenantId: 'org_acme', environment: 'test' },
metadata: { currency: 'USD' },
})
await writer.recordEvent({
id: 'evt_invoice_sent_01',
occurredAt: '2026-08-09T12:01:00.000Z',
actor: { type: 'service', id: 'billing_api' },
action: 'invoice.sent',
target: { type: 'invoice', id: 'inv_123' },
scope: { tenantId: 'org_acme', environment: 'test' },
metadata: { channel: 'email' },
})
const readerAfterRestart = createFileEvidenceStore(directory)
const events = await readerAfterRestart.listEvents()
const verification = await readerAfterRestart.verify()
return {
eventSequences: events.map((record) => record.sequence),
reopenedEventCount: events.length,
verification,
}
} finally {
await rm(directory, { recursive: true })
}
}
if (import.meta.main) {
console.log(JSON.stringify(await persistReopenAndVerify(), null, 2))
}

Run it from the website checkout:

Terminal window
bun src/examples/storage/file-store.ts

The build executes the fixture and requires this exact output:

verified output
{
"eventSequences": [
1,
2
],
"reopenedEventCount": 2,
"verification": {
"ok": true,
"audit": {
"ok": true
},
"edges": {
"ok": true
},
"commits": {
"ok": true
}
}
}

reopenedEventCount proves that the second store instance read the persisted records. The event sequences prove tenant-local ordering. Empty edge and commit chains are valid, so all three verifier results are ok: true.

One directory represents one tenant and contains three independent JSONL chains:

File Contents Sequence owner
events.jsonl Canonical audit records File store
edges.jsonl Canonical evidence-edge records File store
commits.jsonl Commit envelopes binding event and edge members File store

The caller owns the directory, permissions, encryption at the host or disk layer, backups, restore procedure, and retention policy. The store requires scope.tenantId on every append and rejects a replay when the same derived idempotency identity carries different canonical content.

The implementation serializes access with a lock file and writes each JSONL append through a temporary file plus rename. Use a local filesystem whose exclusive-create and rename semantics you have verified. Do not put one tenant directory behind multiple independent writers or an unproven network filesystem.

  1. Restart between append and read, as the fixture does.
  2. Back up the whole tenant directory, not individual JSONL files at unrelated points in time.
  3. Restore into an isolated directory and run verify() before accepting the restore.
  4. Corrupt a copied line in a non-production restore and confirm verification fails closed.
  5. Alert on stale lock recovery and every non-ok verification result.

For concurrent service writers, continue with Postgres and Neon. For a complete host boundary, read Self-hosting.