117 lines
5.5 KiB
Markdown
117 lines
5.5 KiB
Markdown
# alkstore
|
|
|
|
One reactive store interface — durable notify/subscribe signals, streams
|
|
with per-consumer offsets, durable queues with the transactional enqueue
|
|
property, named locks, a scheduler, and an outbox helper — over SQLite,
|
|
Postgres, and the in-process mem engine. Each engine uses its own native
|
|
machinery underneath (SQLite: the forked honker watcher design;
|
|
Postgres: `pg_notify`/LISTEN and the pg-boss schema family; mem: guarded
|
|
in-process state, ephemeral by design).
|
|
|
|
alkstore is the substrate, made once: the answer to "how does a change
|
|
in the database become visible to other processes/connections?" that
|
|
downstream stores don't re-derive.
|
|
|
|
## The interface
|
|
|
|
One unified trait surface (`alkstore::Store`), seven mechanisms:
|
|
|
|
- **notify / listen** — durable publish/subscribe signals with
|
|
transactional wake discipline (wake on commit, opaque wake → re-read).
|
|
- **streams** — append-only logs with per-consumer offsets, carried
|
|
metadata, and consumer-invoked `trim_to`.
|
|
- **queues** — visibility-timeout claim discipline, backoff,
|
|
dead-lettering, and an opt-in retention sweep; enqueue is available
|
|
inside the transaction seam (the outbox pattern).
|
|
- **named locks** — TTL-based lease locks with renewal and expiry
|
|
reclaim.
|
|
- **outbox helper** — transactional enqueue-on-commit (`outbox_enqueue_tx`)
|
|
with a `run_once` delivery driver.
|
|
- **scheduler** — `@every`-interval schedules that enqueue onto a queue,
|
|
with bounded catch-up and leadership discipline (multi-process safe).
|
|
- **transaction seam** — `with_tx` and caller-held `TxHandle` methods;
|
|
drop of the handle rolls back.
|
|
|
|
## Crate family
|
|
|
|
Downstream consumers depend on `alkstore` (core) plus exactly one engine
|
|
crate. Engine identity is compile-time: the engine crate your binary
|
|
depends on *is* the deployment statement — there is no runtime
|
|
capability surface ([ADR-016](docs/architecture/decisions/016-deployment-honesty.md)).
|
|
|
|
| Crate | Role | Engine posture |
|
|
|---|---|---|
|
|
| `alkstore` | the contract artifact: unified trait surface, value types, error model (no driver dependencies) | — |
|
|
| `alkstore-sqlite` | SQLite engine (rusqlite over the in-tree forked honker substrate) | **single-host**, file-backed — cross-process on one host is verified territory; NFS two-writers is not a supportable posture |
|
|
| `alkstore-postgres` | Postgres engine (tokio-postgres + deadpool-postgres, hand-rolled LISTEN) | **multi-host native**; v1 TLS is effectively unavailable (`NoTls` on every connection path) — network confidentiality comes from the deployment topology |
|
|
| `alkstore-mem` | in-process engine, full contract v1 | **honest-ephemeral** — no durability, single-process, never fleet-valid; loss on process exit is the documented posture; compiles clean on `wasm32-unknown-unknown` |
|
|
|
|
The unified trait does not pretend SQLite is multi-host — the honest
|
|
boundary lives in the compile-time engine identity plus the documented
|
|
[deployment matrix](docs/architecture/deployment.md) (host semantics,
|
|
connection budgets, durability knobs, growth postures).
|
|
|
|
## Usage
|
|
|
|
The mem engine is the zero-config example carrier — one call and no
|
|
options:
|
|
|
|
```rust
|
|
use alkstore::{EnqueueOpts, JobState, QueueOpts, Store};
|
|
use alkstore_mem::MemStore;
|
|
|
|
let rt = tokio::runtime::Builder::new_current_thread().build()?;
|
|
let store = MemStore::new();
|
|
|
|
let queue = rt.block_on(store.queue("demo", QueueOpts::default()))?;
|
|
let job_id = rt.block_on(queue.enqueue(
|
|
serde_json::json!("hello"),
|
|
EnqueueOpts::default(),
|
|
))?;
|
|
|
|
let claimed = rt.block_on(queue.claim_one("worker-1"))?
|
|
.expect("the enqueued job is claimable");
|
|
assert_eq!(claimed.job().id, job_id);
|
|
assert_eq!(claimed.job().state, JobState::Processing);
|
|
```
|
|
|
|
The surface is the same on every engine; only the constructor differs:
|
|
|
|
```rust
|
|
let store = alkstore_sqlite::open("store.db", SqliteOpts::default())?; // single-host
|
|
let store = alkstore_postgres::open(&dsn, PgOpts::default()).await?; // multi-host
|
|
let store = MemStore::new(); // in-process
|
|
```
|
|
|
|
## Three engines, one pinned contract
|
|
|
|
The contract is pinned once, not per engine: the internal
|
|
`alkstore-contract-suite` crate parameterizes the contract's property
|
|
set over a store factory, and each engine crate runs the *same* suite
|
|
rows against its own factory as part of its test target. Three engines,
|
|
one pinned contract — the [core contract](docs/architecture/core-contract.md)
|
|
is the spec of record (mechanisms, delivery guarantees, error taxonomy,
|
|
naming / reserved namespace).
|
|
|
|
## Documentation
|
|
|
|
- [Core contract](docs/architecture/core-contract.md) — the unified
|
|
trait surface: mechanisms, delivery guarantees, tx seam, naming.
|
|
- [Deployment matrix](docs/architecture/deployment.md) — host
|
|
semantics, connection budgets, durability knobs, growth postures.
|
|
- [Engine specs](docs/architecture/) — SQLite, Postgres, mem.
|
|
- [Decision index](docs/architecture/README.md) — the ADRs.
|
|
- [API docs](https://docs.rs/alkstore) — full crate documentation.
|
|
- [CHANGELOG.md](CHANGELOG.md)
|
|
|
|
## License
|
|
|
|
Dual-licensed under [MIT](LICENSE-MIT) or
|
|
[Apache-2.0](LICENSE-APACHE), matching the workspace
|
|
`license = "MIT OR Apache-2.0"` field.
|
|
|
|
The SQLite engine carries a forked third-party substrate (honker)
|
|
in-tree under its upstream dual license; its bundled license notice
|
|
(`alkstore-sqlite/src/substrate/LICENSE`) is the preservation
|
|
obligation's carrier and is not replaced by the root license files
|
|
([ADR-018](docs/architecture/decisions/018-provenance-register-and-cherry-picks.md)). |