factstr 0.5.2

Rust event store contract for append-only facts, streams, and command context consistency.
Documentation
# factstr

`factstr` is the shared Rust contract crate for FACTSTR.

FACTSTR is an event store built around append-only facts, global sequencing, streams, durable streams, and command context consistency.

This crate defines the public contract and the core types. It does not store events by itself. Runtime stores live in separate crates.

## What this crate provides

The `factstr` crate defines the common API used by all FACTSTR store implementations.

It includes:

* event input and event record types
* event queries and filters
* append results
* query results
* conditional append conflicts
* live stream contracts
* durable stream contracts
* the `EventStore` trait

The purpose of this crate is to keep the observable behavior of different stores aligned. Memory, SQLite, and PostgreSQL can use different internal mechanisms, but they implement the same public contract.

## Store crates

Use this crate together with one of the runtime store implementations:

* `factstr-memory` for tests, examples, and local development
* `factstr-sqlite` for embedded persistent usage without a separate database server
* `factstr-postgres` for PostgreSQL-backed persistence

The core crate stays independent of those implementations.

## Add to `Cargo.toml`

```toml
[dependencies]
factstr = "0.5.2"
```

Use the GitHub repository when you want to work on FACTSTR itself or test unreleased changes before a crates.io release.

## Core idea

FACTSTR treats events as facts in one append-only log.

Each committed event receives a global sequence number. A committed batch receives one consecutive sequence range. Reads return events in ascending sequence order.

Consistency is checked against the facts relevant to a command. A command reads the current version of its context, then appends new facts only if that context has not changed.

That means consistency is not tied to aggregate roots or stream-per-entity ownership. The relevant context is described by an `EventQuery`.

## Main operations

The shared contract supports:

* `append`
* `query`
* `append_if`
* `stream_all`
* `stream_to`
* `stream_all_durable`
* `stream_to_durable`

## Important semantics

FACTSTR keeps several meanings explicit because they matter for correctness.

### Append

Appending a batch produces an `AppendResult` with:

* `first_sequence_number`
* `last_sequence_number`
* `committed_count`

A successful batch is committed as one consecutive range.

### Query

A query returns matching event records and two separate sequence meanings:

* `last_returned_sequence_number`
* `current_context_version`

`last_returned_sequence_number` describes the events returned by the read.

`current_context_version` describes the full matching context used for consistency checks.

Those values are intentionally separate.

### `min_sequence_number`

`min_sequence_number` is a read cursor only.

It limits which rows are returned by a query, but it does not shrink the consistency context used by `append_if`.

### Conditional append

`append_if` appends only when the expected context version still matches the current version of the full matching context.

If the context changed, the store returns a conditional append conflict instead of appending a partial batch.

### Streams

Live streams observe future committed batches.

Handlers are async-capable through `HandleStream::new(move |batch| async move { ... })`.

Live delivery happens only after persistence succeeds, and handler failure does not roll back append success.

Durable streams replay committed batches after a stored cursor, await handler success for each delivered batch, and then continue with future delivery. Durable cursors must not advance past undelivered committed facts.

## Contract types

The central public types are:

* `NewEvent`
* `EventRecord`
* `EventFilter`
* `EventQuery`
* `QueryResult`
* `AppendResult`
* `DurableStream`
* `EventStream`
* `EventStore`
* `EventStoreError`

## Example: query and conditional append

This example uses the contract shape. A concrete store such as `factstr-memory` or `factstr-sqlite` provides the actual implementation.

```rust
use factstr::{EventQuery, EventFilter, NewEvent};

fn reservation_context(item_id: &str, window_key: &str) -> EventQuery {
    EventQuery {
        filters: Some(vec![
            EventFilter {
                event_types: Some(vec!["item-reserved".to_string()]),
                payload_predicates: Some(vec![
                    serde_json::json!({
                        "item_id": item_id,
                        "window_key": window_key
                    })
                ]),
            }
        ]),
        min_sequence_number: None,
    }
}

let context = reservation_context("camera-1", "2026-05-10-morning");

let event = NewEvent {
    event_type: "item-reserved".to_string(),
    payload: serde_json::json!({
        "reservation_id": "res-1",
        "item_id": "camera-1",
        "window_key": "2026-05-10-morning"
    }),
};
```

The command first queries the context, reads `current_context_version`, and then calls `append_if` on a concrete store implementation.

## Choosing a store

Use `factstr-memory` when the process lifetime is enough and persistence is not needed.

Use `factstr-sqlite` when the application should keep facts locally without operating a separate database server.

Use `factstr-postgres` when the application should use PostgreSQL as the persistent store.

## Related packages

Rust store crates:

* `factstr-memory`
* `factstr-sqlite`
* `factstr-postgres`

Node and TypeScript package:

* `@factstr/factstr-node`

## License

Licensed under either of:

* MIT
* Apache-2.0