Current section
Files
Jump to
Current section
Files
factos_sqlight
README.md
README.md
# Factos Sqlight
`factos_sqlight` stores Factos events in SQLite through
[`sqlight`](https://hex.pm/packages/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
```gleam
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
```gleam
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:
```gleam
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
```sh
trellis run test factos_sqlight
```
See [How factos_sqlight works](docs/how-it-works.md) for locking and retry
details.