tiny-counter 0.1.0

Track event counts across time windows with fixed memory and fast queries
Documentation
// Multi-device sync and offline-first patterns.
//
// This example shows how to:
// - Export and import event data
// - Merge data from multiple devices
// - Handle offline scenarios with dirty tracking
// - Implement bidirectional sync

use tiny_counter::{EventId, EventStore};

// Sync events for cross-device tracking
#[derive(Debug, Clone, Copy)]
enum SyncEvent {
    AppLaunch,
    PhotoTaken,
    StepCount,
    DocumentEdit,
}

impl AsRef<str> for SyncEvent {
    fn as_ref(&self) -> &str {
        match self {
            SyncEvent::AppLaunch => "sync:app_launch",
            SyncEvent::PhotoTaken => "sync:photo_taken",
            SyncEvent::StepCount => "sync:step_count",
            SyncEvent::DocumentEdit => "sync:document_edit",
        }
    }
}

impl EventId for SyncEvent {}

fn main() {
    println!("=== Multi-Device Sync Examples ===\n");

    // 1. Basic export and import
    println!("1. Export and import:");

    let device1 = EventStore::new();
    device1.record(SyncEvent::AppLaunch);
    device1.record(SyncEvent::PhotoTaken);
    device1.record_count(SyncEvent::StepCount, 1000);

    println!("  Device 1: Recorded 1 launch, 1 photo, 1000 steps");

    // Export from device 1
    let device1_data = device1.export_all().unwrap();
    println!("  Exported {} events from device 1", device1_data.len());

    // Import to device 2
    let device2 = EventStore::new();
    device2.import_all(device1_data).unwrap();

    let launches = device2
        .query(SyncEvent::AppLaunch)
        .last_days(7)
        .sum()
        .unwrap_or(0);
    println!("  Device 2: Imported data, launches={}", launches);

    // 2. Merging data from multiple sources
    println!("\n2. Merging from multiple devices:");

    let phone = EventStore::new();
    phone.record_count(SyncEvent::StepCount, 1000);
    phone.record(SyncEvent::PhotoTaken);
    println!("  Phone: 1000 steps, 1 photo");

    let tablet = EventStore::new();
    tablet.record_count(SyncEvent::StepCount, 500);
    tablet.record(SyncEvent::DocumentEdit);
    println!("  Tablet: 500 steps, 1 document edit");

    // Merge both to server
    let server = EventStore::new();
    server.merge_all(phone.export_all().unwrap()).unwrap();
    server.merge_all(tablet.export_all().unwrap()).unwrap();

    let total_steps = server
        .query(SyncEvent::StepCount)
        .last_days(7)
        .sum()
        .unwrap_or(0);
    println!("  Server: Merged data, total_steps={}", total_steps);

    // 3. Offline scenario with dirty tracking
    println!("\n3. Offline sync with dirty tracking:");

    let offline_device = EventStore::new();
    println!("  Device goes offline...");

    // Record events while offline
    offline_device.record(SyncEvent::AppLaunch);
    offline_device.record(SyncEvent::DocumentEdit);
    offline_device.record(SyncEvent::DocumentEdit);
    println!("  Recorded 3 events offline");
    println!("  Is dirty: {}", offline_device.is_dirty());

    // Export only modified events for efficient sync
    let dirty_data = offline_device.export_dirty().unwrap();
    println!("  Exported {} dirty events", dirty_data.len());

    // Sync to server
    let server = EventStore::new();
    server.merge_all(dirty_data).unwrap();
    println!("  Synced to server");

    let server_count = server
        .query(SyncEvent::DocumentEdit)
        .last_days(7)
        .sum()
        .unwrap_or(0);
    println!("  Server has {} document edits", server_count);

    // 4. Bidirectional sync
    println!("\n4. Bidirectional sync:");

    let local = EventStore::new();
    let remote = EventStore::new();

    // Both devices record different events
    local.record_count(SyncEvent::StepCount, 500);
    remote.record_count(SyncEvent::PhotoTaken, 3);

    println!("  Local: 500 steps");
    println!("  Remote: 3 photos");

    // Sync local → remote
    let local_data = local.export_all().unwrap();
    remote.merge_all(local_data).unwrap();
    println!("  Synced local → remote");

    // Sync remote → local
    let remote_data = remote.export_all().unwrap();
    local.merge_all(remote_data).unwrap();
    println!("  Synced remote → local");

    // Both should now have all data
    let local_steps = local
        .query(SyncEvent::StepCount)
        .last_days(7)
        .sum()
        .unwrap_or(0);
    let local_photos = local
        .query(SyncEvent::PhotoTaken)
        .last_days(7)
        .sum()
        .unwrap_or(0);

    println!(
        "  Local after sync: {} steps, {} photos",
        local_steps, local_photos
    );

    let remote_steps = remote
        .query(SyncEvent::StepCount)
        .last_days(7)
        .sum()
        .unwrap_or(0);
    let remote_photos = remote
        .query(SyncEvent::PhotoTaken)
        .last_days(7)
        .sum()
        .unwrap_or(0);

    println!(
        "  Remote after sync: {} steps, {} photos",
        remote_steps, remote_photos
    );

    // 5. Conflict-free merge (commutative property)
    println!("\n5. Conflict-free merge (order doesn't matter):");

    let device_a = EventStore::new();
    let device_b = EventStore::new();

    device_a.record_count(SyncEvent::StepCount, 1000);
    device_b.record_count(SyncEvent::StepCount, 2000);

    let data_a = device_a.export_all().unwrap();
    let data_b = device_b.export_all().unwrap();

    // Merge A then B
    let result1 = EventStore::new();
    result1.merge_all(data_a.clone()).unwrap();
    result1.merge_all(data_b.clone()).unwrap();

    // Merge B then A
    let result2 = EventStore::new();
    result2.merge_all(data_b).unwrap();
    result2.merge_all(data_a).unwrap();

    let count1 = result1
        .query(SyncEvent::StepCount)
        .last_days(7)
        .sum()
        .unwrap_or(0);
    let count2 = result2
        .query(SyncEvent::StepCount)
        .last_days(7)
        .sum()
        .unwrap_or(0);

    println!("  Merge A+B: {} steps", count1);
    println!("  Merge B+A: {} steps", count2);
    println!("  Results match: {}", count1 == count2);

    println!("\n✓ Multi-device sync complete!");
}