# ☝️🤓 Factos

Factos is a context-first Event Sourcing library for Gleam. It lets a business
decision read and protect the exact facts that can change its answer, even when
those facts span several entities or would traditionally live in different
event streams.

```text
command + relevant accepted facts -> new facts or a domain error
```

Applications own their domain language, decisions, event codecs, projections,
and effects. Factos provides:

- a small, store-independent model for pure decisions;
- Dynamic Consistency Boundary predicates built from event types and tags;
- optimistic append conditions over those predicates;
- deterministic domain and codec simulation;
- PostgreSQL, SQLite, and Cloudflare D1 storage adapters; and
- transactional subscription hooks where a backend supports them.

Factos is not a DDD framework, command bus, projection framework, or generic
application architecture.

## Why it exists

Business invariants often cross fixed storage boundaries. A course subscription,
for example, may depend on the course's capacity, the student's existing
subscriptions, and whether that student already joined that course.

A traditional stream-per-aggregate design can protect either stream with an
expected revision, but protecting the combined decision requires extra
coordination. Factos instead makes the relevant set of facts explicit for each
command:

```text
read matching facts
      -> fold temporary decision state
      -> apply a pure decision
      -> append only if no matching fact appeared meanwhile
```

This is Dynamic Consistency Boundaries (DCB): consistency follows the business
rule rather than a permanent stream boundary. Unrelated commands can still use
different, narrower contexts.

## Start with the concepts

The documentation is ordered for readers who are new to the ideas:

1. [Start here: why Factos exists](docs/start-here.md)
2. [Domain-Driven Design: a practical primer](docs/domain-driven-design.md)
3. [Event Sourcing and command dispatch](docs/event-sourcing.md)
4. [Dynamic Consistency Boundaries](docs/dynamic-consistency-boundaries.md)
5. [The Factos core model](docs/core-model.md)

## Core model

A `Model` combines pure state transition and decision functions with the
application's event codec:

```gleam
let model =
  factos.model(
    initial: initial_state,
    try: try_command,
    evolve: evolve,
    encode: encode_event,
    decode: decode_event,
  )
```

See [The Factos core model](docs/core-model.md#model) for complete definitions of
all five model values.

Every dispatch also supplies a `DecisionContext`:

- `NoContext` reads no history and appends unconditionally;
- `Matching(items:)` selects facts by event type and tags;
- `AllEvents` reads and protects the complete event log.

Items are OR-combined. Types inside an item are OR-combined; tags are
AND-combined.

## Simulate domain scenarios

The core simulator exercises the real model and codecs without a database:

```gleam
let simulation =
  simulate.new(model, with: [
    TicketSold(buyer: "renata"),
  ])
  |> simulate.dispatch(
    decision_context: sale_context(),
    command: BuyTicket(buyer: "lucy"),
  )
  |> simulate.tap(fn(simulation) {
    assert simulate.events(simulation)
      == Ok([
        TicketSold(buyer: "renata"),
        TicketSold(buyer: "lucy"),
      ])
  })

assert simulate.errors(simulation) == []
```

Simulation proves domain folding, decisions, errors, and codec behavior. It does
not prove an event store's transaction isolation or concurrency guarantee; use
backend dispatch integration tests for those.

PostgreSQL and SQLite projection simulators can run the application's real
subscriptions against a projection database while keeping event history in
memory. Each accepted batch gets its own backend transaction, and a failed
subscription rolls back that batch's projection writes.

## Dispatch through a backend

Configuration and dispatch are backend-specific:

```gleam
let configuration =
  factos_pog.configure(model, connection: connection)

configuration
|> factos_pog.dispatch(
  command,
  decision_context: decision_context(command),
  event_id: new_event_id,
)
```

`factos_pog` runs dispatches in serializable PostgreSQL transactions and retries
serialization or deadlock conflicts. SQLite and Cloudflare D1 expose the same
core model through storage-appropriate transaction APIs.

## Packages

| Package | Role |
| --- | --- |
| `factos` | Store-independent model, event envelope, contexts, and simulator |
| `factos_pog` | PostgreSQL event store and transactional subscriptions |
| `factos_sqlight` | SQLite event store through Sqlight |
| `factos_cf` | Cloudflare D1 event store and immutable transaction plans |

Runnable DCB examples live under
[`examples/`](https://tangled.org/renatillas.dev/factos/tree/main/examples). They cover course
subscriptions, unique usernames, invoice numbers, opt-in tokens, dynamic
product prices, and record deduplication.

## Development

```sh
trellis run check
trellis run test
```

PostgreSQL-backed tests and the benchmark use the root Compose service:

```sh
docker compose up --wait -d
```

See the
[changelog](https://tangled.org/renatillas.dev/factos/blob/main/CHANGELOG.md)
for release history.
