Skip to main content

sayiir_postgres/
lib.rs

1//! PostgreSQL persistence backend for the Sayiir workflow engine.
2//!
3//! This crate provides [`PostgresBackend`], a production-grade implementation of
4//! [`SnapshotStore`](sayiir_persistence::SnapshotStore),
5//! [`SignalStore`](sayiir_persistence::SignalStore), and
6//! [`TaskClaimStore`](sayiir_persistence::TaskClaimStore) backed by PostgreSQL via
7//! [`sqlx`].
8//!
9//! # Features
10//!
11//! - **Codec-generic**: Serialise snapshots with any codec (JSON for debuggability,
12//!   rkyv/bincode for speed). The data column is always `BYTEA`.
13//! - **ACID transactions**: Composite signal operations (`check_and_cancel`,
14//!   `check_and_pause`, `unpause`) use single Postgres transactions with
15//!   `SELECT … FOR UPDATE` for true atomicity.
16//! - **Snapshot history**: Every checkpoint is appended to an immutable history
17//!   table for debugging, auditing, and future replay.
18//! - **Observability-ready**: Indexed metadata columns (`status`, `current_task_id`,
19//!   `completed_task_count`, `error`, timestamps) plus a denormalised
20//!   `sayiir_workflow_tasks` table enable monitoring without deserialising blobs.
21//! - **Distributed task claiming**: `TaskClaimStore` with TTL-based claims,
22//!   expired-claim replacement, and soft worker bias.
23//!
24//! # PostgreSQL version support
25//!
26//! Minimum supported version: **PostgreSQL 13**. The schema uses
27//! `INSERT … ON CONFLICT DO UPDATE` (9.5+) and `ALTER TABLE … ADD COLUMN IF NOT
28//! EXISTS` (9.6+); 13 is the floor because it is the oldest major release still
29//! receiving security patches. Integration tests run against both 13 and 17.
30//!
31//! # Quick start
32//!
33//! ```rust,no_run
34//! use sayiir_postgres::PostgresBackend;
35//! use sayiir_runtime::serialization::JsonCodec;
36//! use sayiir_persistence::SnapshotStore;
37//! use sayiir_core::snapshot::WorkflowSnapshot;
38//!
39//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
40//! let backend = PostgresBackend::<JsonCodec>::connect("postgresql://localhost/sayiir").await?;
41//!
42//! let snapshot = WorkflowSnapshot::new("order-123".to_string(), "hash-abc".to_string());
43//! backend.save_snapshot(&snapshot).await?;
44//!
45//! let loaded = backend.load_snapshot("order-123").await?;
46//! # Ok(())
47//! # }
48//! ```
49
50#![deny(clippy::pedantic)]
51#![deny(
52    clippy::unwrap_used,
53    clippy::expect_used,
54    clippy::panic,
55    clippy::indexing_slicing,
56    clippy::todo,
57    clippy::unimplemented,
58    clippy::dbg_macro,
59    clippy::print_stdout,
60    clippy::print_stderr
61)]
62// PostgreSQL is a proper noun, not inline code.
63#![allow(clippy::doc_markdown)]
64
65mod backend;
66mod error;
67mod helpers;
68mod signal_store;
69mod snapshot_store;
70mod task_claim_store;
71
72pub use backend::PostgresBackend;