batpak 0.8.0

Event sourcing with causal graphs and caller-defined gates. Sync API, no async runtime.
Documentation
mod support;
use batpak::store::{Clock, SystemClock};
use std::sync::Arc;
use support::prelude::*;

#[test]
fn store_config_builder_methods_are_chainable() {
    let config = StoreConfig::new("./batpak-data")
        .with_segment_max_bytes(1024)
        .with_sync_every_n_events(7)
        .with_fd_budget(9)
        .with_writer_channel_capacity(10)
        .with_broadcast_capacity(11)
        .with_single_append_max_bytes(12)
        .with_restart_policy(RestartPolicy::Bounded {
            max_restarts: 2,
            within_ms: 30_000,
        })
        .with_shutdown_drain_limit(13)
        .with_writer_stack_size(Some(14))
        .with_clock_fn(|| 42)
        .with_sync_mode(SyncMode::SyncData)
        .with_platform_profile_path("./platform.profile")
        .with_event_payload_validation(EventPayloadValidation::FailFast);

    assert_eq!(config.data_dir(), std::path::Path::new("./batpak-data"));
    assert_eq!(config.segment_max_bytes(), 1024);
    assert_eq!(config.sync().every_n_events, 7);
    assert_eq!(config.fd_budget(), 9);
    assert_eq!(config.writer().channel_capacity, 10);
    assert_eq!(config.broadcast_capacity(), 11);
    assert_eq!(config.single_append_max_bytes(), 12);
    assert!(matches!(
        config.writer().restart_policy,
        RestartPolicy::Bounded {
            max_restarts: 2,
            within_ms: 30_000
        }
    ));
    let _: batpak::store::WriterConfig = config.writer().clone();
    assert_eq!(config.writer().shutdown_drain_limit, 13);
    assert_eq!(config.writer().stack_size, Some(14));
    assert!(config.has_custom_clock());
    let _: batpak::store::SyncConfig = config.sync().clone();
    assert!(matches!(config.sync().mode, SyncMode::SyncData));
    assert_eq!(config.batch().max_size, BatchConfig::default().max_size);
    let _: batpak::store::IndexConfig = config.index().clone();
    assert_eq!(config.index().topology, IndexTopology::default());
    assert_eq!(
        config.platform_profile_path(),
        Some(std::path::Path::new("./platform.profile"))
    );
    assert!(matches!(
        config.event_payload_validation(),
        EventPayloadValidation::FailFast
    ));

    #[cfg(feature = "dangerous-test-hooks")]
    {
        let _dangerous = StoreConfig::new("./batpak-data").with_fault_injector(None);
    }
}

#[test]
fn store_config_accepts_clock_trait_object() {
    let clock: Arc<dyn Clock> = Arc::new(SystemClock::new());
    let config = StoreConfig::new("./batpak-data").with_clock(Some(clock));

    assert!(
        format!("{config:?}").contains("clock: Some(\"<clock>\")"),
        "public with_clock must install a trait-object clock through the builder path"
    );
}

#[test]
fn open_with_native_cache_is_available_for_common_setup() {
    let data_dir = tempfile::tempdir().expect("data dir");
    let cache_dir = tempfile::tempdir().expect("cache dir");
    let store = Store::open_with_native_cache(
        StoreConfig::new(data_dir.path()),
        cache_dir.path().join("projection_cache"),
    )
    .expect("open store with native cache");
    let coord = Coordinate::new("entity:native", "scope:test").expect("coord");
    let kind = EventKind::custom(0xF, 1);
    let receipt = store
        .append(&coord, kind, &serde_json::json!({"hello": "native"}))
        .expect("append");
    assert!(receipt.event_id != batpak::id::EventId::from(0u128));
    let _closed = store.close().expect("close");
}

#[test]
fn renamed_surface_items_are_reachable() {
    let data_dir = tempfile::tempdir().expect("data dir");
    let store = Store::open(StoreConfig::new(data_dir.path())).expect("open");
    let _typed_open: &Store<Open> = &store;
    let _freshness = Freshness::MaybeStale { max_stale_ms: 5 };
    let _sub = store.subscribe_lossy(&Region::all());
    let _cursor = store.cursor_guaranteed(&Region::all());
    let _closed: Closed = store.close().expect("close");
}