Skip to content

Closing Streams

Event streams can be permanently closed to prevent further appends. Once a stream is closed, any attempt to append events to it will result in a constraint violation of type StreamClosed.

Closing a stream is useful when a logical unit of work is complete and no further events should be recorded for that stream. Examples include:

  • Finalizing an invoice — once issued, no further line items should be added.
  • Archiving a case — the case is resolved and the event history is sealed.
  • Completing an order — the order lifecycle has ended and further mutations are disallowed.

Call CompleteStream on the event log with the stream type and stream identifier you want to close:

using Cratis.Chronicle.EventSequences;
public class ClosingStreamsInvoiceCloser(IEventLog eventLog)
{
public async Task CloseInvoiceStream(EventStreamId invoiceStreamId)
{
var result = await eventLog.CompleteStream(new EventStreamType("invoices"), invoiceStreamId);
result.Switch(
sequenceNumber => Console.WriteLine($"Stream closed at sequence number {sequenceNumber}"),
error => Console.WriteLine($"Failed to close stream: {error}"));
}
}

The method returns Result<EventSequenceNumber, CompleteStreamError>.

ErrorMeaning
AlreadyCompletedThe stream has already been closed.
DefaultStreamCannotBeCompletedThe default stream (EventStreamType.All / EventStreamId.Default) cannot be closed.

After a stream is closed, any append targeting that stream is rejected with a StreamClosed constraint violation:

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
using Cratis.Chronicle.EventSequences;
using System.Linq;
[EventType]
public record ClosingStreamsInvoiceLineAdded(string Description, decimal Amount);
public class ClosingStreamsInvoiceLineAppender(IEventLog eventLog)
{
public async Task<bool> TryAppendLine(EventSourceId invoiceId)
{
var appendResult = await eventLog.Append(
invoiceId,
new ClosingStreamsInvoiceLineAdded("Consulting", 500m),
new EventStreamType("invoices"),
new EventStreamId("invoice-42"));
if (!appendResult.IsSuccess)
{
var violation = appendResult.ConstraintViolations
.FirstOrDefault(v => v.ConstraintType == ConstraintType.StreamClosed);
return violation is null;
}
return true;
}
}

The rejection is enforced by the ClosedStreamConstraintValidator which is automatically active for every event sequence — no additional configuration is required.