Factos Sqlight

factos_sqlight stores Factos events in SQLite through sqlight. It is intended for embedded, single-process applications that want the same model and decision-context API as other Factos backends.

Setup

Apply priv/migrations.sql, or copy it into your application migration history. The schema contains one append-only factos_events table.

Configure and dispatch

let model = user.model()

let configuration = factos_sqlight.configure(model, connection: connection)

configuration
|> factos_sqlight.dispatch(
  command,
  decision_context: user.decision_context(command),
  event_id: new_event_id,
)

Dispatch uses BEGIN IMMEDIATE, so the context read, event append, and subscriptions share one SQLite transaction.

Transactional subscriptions

let projection =
  factos.subscription(fn(connection, recorded) {
    use _ <- result.try(projection.apply(connection, recorded))
    Ok(connection)
  })

let configuration =
  factos_sqlight.Configuration(
    ..factos_sqlight.configure(model, connection: connection),
    subscriptions: [projection],
  )

A subscription failure rolls back the event append and all subscription writes.

Simulate projections

Use factos/factos_sqlight/simulate to run the same subscriptions from an in-memory domain scenario against a real SQLite projection database:

import factos/factos_sqlight/simulate as simulate_sqlight
import factos/simulate

let simulation =
  simulate_sqlight.new(
    model,
    with: existing_events,
    connection:,
    subscriptions: [projection],
  )
  |> simulate.dispatch(decision_context:, command:)

assert simulate.errors(simulation) == []

assert Ok(rows) = projection.load(connection)

The initial event batch and each accepted dispatch batch run in their own BEGIN IMMEDIATE transaction. Subscription or transaction failure rolls back projection writes and leaves the batch out of simulated history. The simulator does not write the SQLite event log; event-store atomicity and locking remain dispatch integration concerns.

Durable work

There is no built-in M25 equivalent for SQLite. Persist application-owned jobs through a transactional subscription, then process them after commit with a supervised worker. External IO must not run inside a retryable dispatch.

Recovery reads

read_after returns a bounded page in global position order for rebuilds and application-owned recovery.

Development

trellis run test factos_sqlight

See How factos_sqlight works for locking and retry details.

Search Document