Skip to main content
net10.0net8.0net9.0

SQLite IEventStoreAdapter for ZeroAlloc.EventSourcing. AOT-clean via Microsoft.Data.Sqlite. Supports the * global stream via global_position column.

Published
Sep 30, 2026
18d ago
Total Downloads
623
623

Install

dotnet add package ZeroAlloc.EventSourcing.Sqlite --version 1.4.0
ProductVersions
.NET
net10.0net8.0net9.0

README

ZeroAlloc.EventSourcing.Sqlite

NuGet AOT

A SQLite-backed IEventStoreAdapter for ZeroAlloc.EventSourcing. Designed for embedded scenarios, single-node services, test harnesses, and AOT-published binaries.

What it is

  • A drop-in IEventStoreAdapter implementation that persists events to a single event_store table in a SQLite database.
  • AOT-clean: ships with PublishAot=true compatibility verified by a dedicated AOT smoke project. No reflection, no dynamic code generation.
  • * global stream support: every event is assigned a monotonic global_position, enabling cross-stream reads via StreamId.Global — the same shape as the PostgreSQL and SQL Server adapters.
  • Optimistic concurrency: enforced via expectedVersion checks inside a BEGIN IMMEDIATE transaction; conflicts return StoreError.Conflict(...) rather than throwing.

Install

dotnet add package ZeroAlloc.EventSourcing.Sqlite

Quick start

using Microsoft.Extensions.DependencyInjection;
using ZeroAlloc.EventSourcing;
using ZeroAlloc.EventSourcing.Sqlite;

var services = new ServiceCollection();

services
    .AddEventSourcing()
    .UseSqliteEventStore("Data Source=events.db");

// IEventTypeRegistry must be registered separately — see the core package docs.

UseSqliteEventStore registers SqliteEventStoreAdapter as IEventStoreAdapter and EventStore as IEventStore. By default it also calls EnsureSchemaAsync() once during the singleton factory so the event_store table exists before first use. Pass ensureSchema: false if you prefer to control schema bootstrap yourself (e.g. via a migration runner).

// Opt out of automatic schema bootstrap
services.AddEventSourcing().UseSqliteEventStore(connectionString, ensureSchema: false);

// Then run it manually at a time of your choosing
var adapter = sp.GetRequiredService<IEventStoreAdapter>() as SqliteEventStoreAdapter;
await adapter!.EnsureSchemaAsync();

The * global stream

Reading from StreamId.Global (alias *) returns events from every stream in commit order:

await foreach (var raw in adapter.ReadAsync(StreamId.Global, StreamPosition.Start, ct))
{
    // raw.Position is the global_position (monotonic across all streams)
    // raw.EventType, raw.Payload, raw.Metadata are the usual per-event values
}

Inside a write transaction the adapter reads MAX(global_position) and assigns the next contiguous values. SQLite is single-writer, so global ordering is deterministic and gap-free — no skip-locks or sequence-cache surprises.

Health checks

Add a health check that probes the database with SELECT 1:

services.AddHealthChecks()
    .AddSqliteEventStore("Data Source=events.db");

Default registration name is sqlite-event-store. Override via the name parameter.

AOT attestation

This package is exercised by the ZeroAlloc.EventSourcing.Sqlite.AotSmoke project, which:

  • Publishes with PublishAot=true and InvariantGlobalization=true.
  • Gates IL2026, IL2067, IL2075, IL2091, IL3050, and IL3051 as build errors.
  • At runtime, appends an event and reads it back via both per-stream and * global reads.

A clean publish + exit-0 run is required to ship a new version.

Connection strings

// File-based (typical for embedded production)
"Data Source=events.db"

// File-based with WAL mode (better concurrency on multi-reader workloads)
"Data Source=events.db;Cache=Shared"
// Then run "PRAGMA journal_mode=WAL;" once after EnsureSchemaAsync.

// In-memory, shared cache (typical for tests)
"Data Source=file:test-abc?mode=memory&cache=shared"
// Hold a keep-alive SqliteConnection open for the lifetime of the test so the
// shared-cache backing store is not collected when transient connections close.

// Strictly per-connection in-memory (not useful — each connection sees its own DB)
"Data Source=:memory:"

Concurrency model

SQLite is a single-writer database. The adapter issues BEGIN IMMEDIATE (via IsolationLevel.Serializable on Microsoft.Data.Sqlite) so concurrent appenders queue cleanly at the connection layer instead of racing into "database is locked" errors. For high-throughput workloads, prefer the PostgreSQL or SQL Server adapter; SQLite shines for embedded apps, single-process services, edge deployments, and integration tests.

Roadmap

  • Live subscriptions: the current SubscribeAsync returns a polling subscription. A future release may use SQLite's data_change_notification_callback (the C-level update_hook) to push real-time notifications without polling. Tracked separately.
  • WAL bootstrap helper: an opt-in pragma_journal_mode=WAL switch on UseSqliteEventStore to enable WAL automatically.

Versioning

This package follows the ZeroAlloc.EventSourcing repository's SemVer + Conventional Commits + release-please workflow. The PublicAPI.Shipped.txt file in this project is the authoritative list of the public surface — anything not declared there is unshipped or internal.

Changes from Previous Version

No structural changes between v1.3.1 and v1.4.0.

Adoption Assessment

Assess this package against its supported frameworks, license, maintenance signals and dependency graph before making it a project standard.

Compatibility metadata lists 3 target frameworks; confirm the exact target used by your application.The published metadata declares 45 direct dependency entries across framework groups. Inspect the resolved dependency graph for your target framework.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.