Skip to content

Getting events

Event sequences support multiple ways of reading events depending on your needs. The IEventSequence APIs (and the specialized IEventLog) cover common patterns such as:

  • Reading events from a sequence in order
  • Reading events for a specific event source
  • Reading a range of events based on sequence numbers
  • Reading a fixed number of events from the tail

Use the client APIs to select the pattern that best matches your scenario, such as replaying state, building read models, or auditing changes.

Use GetMetadataAt when you already know an event’s sequence number and need its context, not its payload. The single-locator overload returns null for a missing event. The batch overload returns an immutable dictionary keyed by sequence number and omits missing events.

using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
public static class EventMetadataReads
{
// Call only after your application authorizes access to these events.
public static async Task Read(IEventSequence eventSequence)
{
EventMetadata? metadata = await eventSequence.GetMetadataAt((EventSequenceNumber)42UL);
if (metadata is not null)
{
Console.WriteLine($"{metadata.EventStreamType}: {metadata.CausedBy.Resolution}");
}
var batch = await eventSequence.GetMetadataAt([42UL, 43UL]);
foreach (var entry in batch.Values)
{
Console.WriteLine($"{entry.SequenceNumber}: {entry.CausedBy.Name ?? "Name unavailable"}");
}
}
}

View C# snippet source on GitHub

A request accepts at most 500 locators, including duplicates. Chronicle rejects larger requests before querying storage and removes duplicates from accepted requests. These are point lookups, not reads of everything after a checkpoint.

Metadata includes routing, occurred time, correlation, causation, tags, subject, source name, and the caused-by identity chain. It contains no payload, generation, content hash or revision content, and does not release compliance-protected data. Identity names resolve from storage on each read, so a rename appears on the next metadata read. See identity resolution for the resolution statuses and privacy boundaries.

Your application must authorize access to the event store, namespace and requested events before calling this API. Metadata can still contain personal information, including identity names, tags and causation properties.

using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
using System.Collections.Immutable;
[EventType]
public record GettingEventsOrderPlaced(string OrderId, decimal Total);
[EventType]
public record GettingEventsOrderCancelled(string OrderId, string Reason);
public class GettingEventsOrderHistoryReader(IEventLog eventLog)
{
public async Task<IImmutableList<AppendedEvent>> GetOrderEvents(EventSourceId orderId)
{
// Filters the timeline to only the order events you care about.
var eventTypes = new[]
{
typeof(GettingEventsOrderPlaced).GetEventType(),
typeof(GettingEventsOrderCancelled).GetEventType()
};
return await eventLog.GetForEventSourceIdAndEventTypes(orderId, eventTypes);
}
}

View C# snippet source on GitHub

Kotlin, Java, and TypeScript don’t currently expose a way to read raw events back for an event source — their public event sequence APIs only support appending and checking for existence.

using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
using System.Collections.Immutable;
public class GettingEventsReplayEvents(IEventLog eventLog)
{
public async Task<IImmutableList<AppendedEvent>> ReadFrom(EventSequenceNumber sequenceNumber)
{
// Replays from a known checkpoint to rebuild projections or read models.
return await eventLog.GetFromSequenceNumber(sequenceNumber);
}
}

View C# snippet source on GitHub

This is currently a C#-only capability — no other client exposes a way to resume reading from an arbitrary sequence number.

using Cratis.Chronicle.Events;
using Cratis.Chronicle.EventSequences;
using System.Collections.Immutable;
using System.Linq;
public class GettingEventsTailReader(IEventLog eventLog)
{
public async Task<IImmutableList<AppendedEvent>> ReadLast(int count)
{
// Reads from the computed start and trims in memory to the requested count.
var tail = await eventLog.GetTailSequenceNumber();
var start = tail.IsActualValue && tail.Value >= (ulong)count
? tail - (count - 1)
: EventSequenceNumber.First;
var events = await eventLog.GetFromSequenceNumber(start);
return events.TakeLast(count).ToImmutableList();
}
}

View C# snippet source on GitHub

Reading the tail this way also relies on resuming from a sequence number, so it’s currently C#-only too. Kotlin, Java, and TypeScript can still get the tail sequence number (see Getting state), just not the events at it.