replay

abstract fun replay(scope: ReplayScope): Flow<ReplayEvent<Op>>

A cold Flow of the frames in scope, in append order, terminated by exactly one verdict on how the stream ended — CleanTail or Truncated.

The verdict arrives on every replay collected to completion. A consumer that cuts the flow short — take(n), first(), an early return from collect — gets no verdict, and that is the honest answer: it stopped reading before the archive said how it ended.

Each collection re-reads the archive, so a flow collected after a later append sees the later frames too. The flow completes when the archive's tail is reached; it does not wait for future appends.

It never throws for damaged bytes, because an archive is best-effort and throwing would discard every intact frame ahead of the damage. But it does not stay silent about them either: that is what the terminal ReplayEvent is for. Use frames to opt out of the verdict, explicitly, when you do not need to know.

Samples

var records = 0
var complete = false

// Collect to COMPLETION. The terminal verdict is what a replay sells — a history that
// stopped at damage, and one that did not, are otherwise indistinguishable. A consumer
// that cuts the flow short (take, first, an early return) gets no verdict, honestly.
bolt.replay(ReplayScope.All).collect { event ->
    when (event) {
        is Archived -> records += event.ops.size
        CleanTail -> complete = true
        is Truncated -> when (event.reason) {
            // Not readable YET — a writer mid-append, a device still locked. Resuming
            // from atOffset later can work.
            TruncationReason.SegmentHeader, TruncationReason.Frame -> retryFrom(event.atOffset)
            // GONE. atOffset is the honest end of the readable history and is NOT a
            // resume cursor: nothing will ever produce the records behind it.
            TruncationReason.MissingRegion -> reportPermanentGap(event.atOffset)
        }
    }
}

if (!complete) reportPartialHistory(records)