Skip to content

Cratis Chronicle bounded event reads for history views

Use bounded event reads in Cratis Chronicle when a history view needs one page at a time rather than the whole sequence. In .NET, IEventSequence.GetPage and IEventLog use keyset pagination: the next request starts at a sequence number, not a row offset.

Each request returns at most Count events and a nullable Next sequence number. Events arrive in read order: oldest first for Forward, newest first for Backward. Chronicle reads one extra matching event to decide whether another page exists, without counting the entire sequence.

Pass an existing event sequence and a cancellation token to this function. It follows Next while keeping the same direction, stop bound, and filters:

using Cratis.Chronicle.EventSequences;
public static class ForwardEventPages
{
public static async Task ReadHistory(IEventSequence sequence, CancellationToken cancellationToken)
{
var request = EventSequencePageRequest.Forward(null, 100);
while (true)
{
var page = await sequence.GetPage(request, cancellationToken);
foreach (var appended in page.Events)
{
Console.WriteLine(appended.Context.SequenceNumber);
}
if (!page.HasMore) break;
request = request with { From = page.Next };
}
}
}

View C# snippet source on GitHub

From is inclusive. A null start means the beginning for forward reads and the tail for backward reads. Do not pass EventSequenceNumber.Unavailable to mean the tail.

For an existing IEventSequence sequence, this reads at most 50 matching events, newest first:

using Cratis.Chronicle.EventSequences;
public static class LatestEventPages
{
public static async Task ReadLatest(IEventSequence sequence, CancellationToken cancellationToken)
{
var filter = new EventSequenceFilter(Tags: ["billing", "priority"]);
var latest = await sequence.GetLatest(50, filter, cancellationToken);
if (latest.HasMore)
{
var older = await sequence.GetPage(
EventSequencePageRequest.Backward(latest.Next, 50, filter),
cancellationToken);
foreach (var appended in older.Events)
{
Console.WriteLine(appended.Context.SequenceNumber);
}
}
}
}

View C# snippet source on GitHub

GetLatest is shorthand for GetPage(EventSequencePageRequest.Backward(null, 50, filter), cancellationToken). To read older matching events, call GetPage with EventSequencePageRequest.Backward(latest.Next, 50, filter) only when latest.HasMore is true. Passing null again starts over at the tail.

Until is also inclusive. It is an upper bound for forward reads and a lower bound for backward reads. For example, EventSequencePageRequest.Backward(200, 50) with { Until = 100 } reads newest first within sequence numbers 100 through 200. Reversed bounds, an empty sequence, or a filter with no matches return an empty page with no continuation.

Next is one sequence number past the last returned event in the read direction, and is present only if another matching event exists inside the bounds. It need not identify an existing event: sequence numbers and filtered results can have gaps. Use the returned value unchanged instead of adding or subtracting Count. A full page can still have no continuation.

Each page is an independent read, not a frozen snapshot. To exclude later appends during a forward traversal, capture the tail sequence number before starting and set Until to that value. Check that the sequence is nonempty before using its tail as a bound. The bound does not freeze event content: a revision or redaction between requests can still change what a later page returns.

EventSequenceFilter supports event source identifier, source type, stream type, stream identifier, event types, and tags. Filters are combined; a tag filter matches any of its supplied tags. Filtering happens in storage before the page limit. Null or empty sets do not narrow the results. The usual unspecified/default routing sentinels also mean no restriction.

Count must be between 1 and 1000, inclusive. Invalid counts are rejected, not clamped. Cancellation is supported by both client methods. Stored compliance-protected content is released for returned events, just as for other event-content reads; a page is not a metadata-only read. Authorize access to the store, namespace, and event content before reading.

Existing GetFromSequenceNumber reads remain available; Getting events explains when to use them. If you supply a custom IEventSequence implementation, implement GetPage before using this API. Its default interface member preserves source compatibility but throws NotSupportedException when called. The in-process Testing package uses the same client and kernel query path.