langgraph-checkpoint-sqlite 0.1.0

SQLite-backed checkpoint persistence for langgraph.
Documentation
use std::collections::BTreeMap;

use langgraph_checkpoint::{Checkpoint, CheckpointError, CheckpointSaver, PendingWrite};
use langgraph_checkpoint_sqlite::SqliteSaver;
use serde_json::json;
use tempfile::tempdir;

#[test]
fn sqlite_saver_put_get_list_roundtrip() {
    let dir = tempdir().unwrap();
    let db_path = dir.path().join("checkpoints.db");
    let saver = SqliteSaver::new(&db_path).unwrap();
    let checkpoint = Checkpoint {
        thread_id: "thread-1".to_string(),
        checkpoint_id: "cp-1".to_string(),
        state: BTreeMap::from([(String::from("text"), json!("ab"))]),
        versions_seen: BTreeMap::from([(String::from("text"), 2)]),
        pending_writes: vec![PendingWrite {
            node: "node_b".to_string(),
            patch: BTreeMap::from([(String::from("text"), json!("b"))]),
        }],
    };

    saver.put(checkpoint.clone()).unwrap();

    let loaded = saver.get("thread-1", "cp-1").unwrap();
    assert_eq!(loaded, Some(checkpoint.clone()));

    let listed = saver.list("thread-1").unwrap();
    assert_eq!(listed, vec![checkpoint]);
}

#[test]
fn sqlite_saver_rejects_duplicate_checkpoint_id_within_thread() {
    let dir = tempdir().unwrap();
    let db_path = dir.path().join("checkpoints.db");
    let saver = SqliteSaver::new(&db_path).unwrap();

    let checkpoint = Checkpoint {
        thread_id: "thread-1".to_string(),
        checkpoint_id: "cp-1".to_string(),
        state: BTreeMap::new(),
        versions_seen: BTreeMap::new(),
        pending_writes: vec![],
    };

    saver.put(checkpoint.clone()).unwrap();
    let err = saver.put(checkpoint).unwrap_err();
    assert!(matches!(err, CheckpointError::Conflict(_)));
}