# 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.

## Install the exact tested packages

[Section titled “Install the exact tested packages”](#install-the-exact-tested-packages)

Terminal window

```sh
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.

## Persist, reopen, and verify

[Section titled “Persist, reopen, and verify”](#persist-reopen-and-verify)

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

```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

```sh
bun src/examples/storage/file-store.ts
```

The build executes the fixture and requires this exact output:

verified output

```json
{
  "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`.

## Files and ownership

[Section titled “Files and ownership”](#files-and-ownership)

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.

## Concurrency and crash boundary

[Section titled “Concurrency and crash boundary”](#concurrency-and-crash-boundary)

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.

One directory, one writer boundary

A lock file is not a distributed consensus system. If more than one service instance can append for a tenant, use a transaction-capable authoritative store such as Postgres.

## Production verification checklist

[Section titled “Production verification checklist”](#production-verification-checklist)

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](/docs/storage/postgres/). For a complete host boundary, read [Self-hosting](/docs/storage/self-hosting/).

[Edit page](https://github.com/getveritio/veritio-website/edit/main/src/content/docs/docs/storage/file-store.mdx)

Last updated: Aug 23, 2026

[Previous  
MongoDB](/docs/storage/mongodb/)[Next  
Transactional outbox](/docs/storage/outbox/)

Veritio provides evidence support, not legal advice or automatic compliance.

This site uses cookieless, anonymous analytics (Umami) by default. With your consent, we also enable Google Analytics, which sets cookies and sends usage data to Google. [Privacy Policy](/legal/privacy/)
