obzenflow_core 0.2.4

Core domain layer for ObzenFlow - pure abstractions with minimal dependencies
Documentation
// SPDX-License-Identifier: MIT OR Apache-2.0
// SPDX-FileCopyrightText: 2025-2026 ObzenFlow Contributors
// https://obzenflow.dev

//! Journal reader trait for efficient sequential reading
//!
//! This trait provides cursor-based reading that maintains position
//! and keeps file handles open for optimal performance.

use super::journal_error::JournalError;
use crate::event::journal_record::JournalRecord;
use crate::event::JournalEvent;
use async_trait::async_trait;

/// A reader that maintains position for efficient sequential journal reading
///
/// This trait is designed to solve the O(n²) performance problem with large journals
/// by keeping file handles open and tracking position, similar to database cursors.
#[async_trait]
pub trait JournalReader<T>: Send + Sync
where
    T: JournalEvent,
{
    /// Read the next event from the current position (append order).
    ///
    /// This is an append-order cursor, not a causal-order iterator. Callers that need
    /// deterministic causal ordering should use `Journal::read_causally_ordered()` /
    /// `Journal::read_causally_after(...)` instead of `JournalReader::next()`.
    ///
    /// Returns None if no more events are available (EOF).
    /// This method should be efficient - O(1) regardless of journal size.
    async fn next(&mut self) -> Result<Option<JournalRecord<T::Payload>>, JournalError>;

    /// Get the current position in the journal
    ///
    /// The position is journal-specific (e.g., line number for disk, index for memory).
    /// This can be used for checkpointing and resuming.
    fn position(&self) -> u64;

    /// Whether all logical records committed when this reader opened have
    /// been returned. The boundary is fixed, including every atomic-group
    /// member, and completion stays true while this same reader tails appends.
    /// This is independent of physical EOF and terminal drain completion.
    fn initial_prefix_complete(&self) -> Result<bool, JournalError> {
        Err(JournalError::InitialPrefixUnsupported)
    }

    /// Check if we've reached the end of the journal
    ///
    /// This is a hint - `next()` may still return None even if this returns false
    /// (e.g., if new events are being written concurrently).
    /// Metrics completion requires a completed `next() == Ok(None)` followed
    /// by a positive end indication after the current pipeline terminal fact.
    /// Providers and reader decorators used by metrics must implement/delegate
    /// this indication. The default false leaves completion unproven; it is
    /// never a licence to convert an unknown or partial-frame tail into success.
    fn is_at_end(&self) -> bool {
        // Default implementation - can be overridden for efficiency
        false
    }
}

pub mod observations;
pub use observations::{
    JournalObservationReader, LocatedObservation, ObservationKey, ObservationLookup,
};