---
title: Closing stream constraint
editUrl: https://github.com/Cratis/Chronicle/edit/main/Documentation/constraints/declarative/closing-stream.mdx
description: Declare in an IConstraint class that appending an event type closes a stream scope, and which events reopen it.
---

import { Tabs, TabItem } from '@astrojs/starlight/components';


Use `ClosesStreamOn<TEvent>` on the constraint builder to close a stream scope whenever an event of that type is appended. The closure is written in the same step as the event, and later appends inside the scope are rejected with a `StreamClosed` violation. For the concept and the guarantees, see [Closing events](/chronicle/closing-streams/closing-events/). For the attribute style, see the [model-bound closing stream constraint](/chronicle/constraints/model-bound/closing-stream/).

## Defining a closing constraint

Choose the dimensions with the `Per...` methods on the configuration callback:

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;

[EventType]
public record ConstraintsDeclarativeClosingReportDelivered(string Recipient);

public class ConstraintsDeclarativeClosingReportDelivery : IConstraint
{
    public void Define(IConstraintBuilder builder) =>
        builder.ClosesStreamOn<ConstraintsDeclarativeClosingReportDelivered>(
            closes => closes.PerEventSourceId().PerEventStreamType());
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/constraints/declarative/closing-stream/basic.md)

</TabItem>
</Tabs>

| Method | Adds the dimension |
| --- | --- |
| `PerEventSourceId()` | Event source id |
| `PerEventSourceType()` | Event source type |
| `PerEventStreamType()` | Event stream type |
| `PerEventStreamId()` | Event stream id |

If you call none of them, the scope is event source id, stream type and stream id of the append.

## Take the stream id from a property

`EventStreamIdFrom` reads the stream id from an event property and implies the stream id dimension:

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;

[EventType]
public record ConstraintsDeclarativeClosingBooksClosed(string Period, decimal ClosingBalance);

public class ConstraintsDeclarativeClosingBooks : IConstraint
{
    public void Define(IConstraintBuilder builder) =>
        builder.ClosesStreamOn<ConstraintsDeclarativeClosingBooksClosed>(
            closes => closes.PerEventSourceId().EventStreamIdFrom(@event => @event.Period));
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/constraints/declarative/closing-stream/event-stream-id-from.md)

</TabItem>
</Tabs>

The property must have a non-empty value on every append. The kernel refuses an append where it is missing, null or empty. Avoid a nullable property; the analyzer [CHR0012](/chronicle/code-analysis/chr0012/) warns on nullable event properties.

## Reopening events and the constraint name

`ReopenedBy<T>()` names an event type that removes this constraint's closure for the exact scope it resolves to. The optional `name` argument sets the owner of the closure and defaults to the event type name:

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;

[EventType]
public record ConstraintsDeclarativeClosingMonthClosed(string Month);

[EventType]
public record ConstraintsDeclarativeClosingMonthReopened(string Month, string Reason);

public class ConstraintsDeclarativeClosingMonth : IConstraint
{
    public void Define(IConstraintBuilder builder) =>
        builder.ClosesStreamOn<ConstraintsDeclarativeClosingMonthClosed>(
            closes => closes
                .PerEventSourceId()
                .EventStreamIdFrom(@event => @event.Month)
                .ReopenedBy<ConstraintsDeclarativeClosingMonthReopened>(),
            name: "close-month");
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/constraints/declarative/closing-stream/reopened-by.md)

</TabItem>
</Tabs>

The reopening event type must carry the same property as the closing event when `EventStreamIdFrom` is used.

## Several closing events, one constraint

Declarations that share a name merge into one constraint, so a scope can be closed by more than one event type. They must use the same dimensions and stream id property, otherwise the client throws `ConflictingClosesStreamDeclarations`:

<Tabs syncKey="chronicle-client">
<TabItem label="C#">

```csharp
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;

[EventType]
public record ConstraintsDeclarativeClosingReportDeliveredToClient(string Version);

[EventType]
public record ConstraintsDeclarativeClosingReportSuperseded(string Version);

public class ConstraintsDeclarativeClosingVersions : IConstraint
{
    public void Define(IConstraintBuilder builder)
    {
        // Two declarations that share a name merge into one constraint. They must use the same dimensions.
        builder.ClosesStreamOn<ConstraintsDeclarativeClosingReportDeliveredToClient>(
            closes => closes.PerEventSourceId().EventStreamIdFrom(@event => @event.Version),
            name: "close-report-version");
        builder.ClosesStreamOn<ConstraintsDeclarativeClosingReportSuperseded>(
            closes => closes.PerEventSourceId().EventStreamIdFrom(@event => @event.Version),
            name: "close-report-version");
    }
}
```

[View C# snippet source on GitHub](https://github.com/Cratis/Chronicle/blob/main/Documentation/client-snippets/constraints/declarative/closing-stream/several-events.md)

</TabItem>
</Tabs>

Registering or changing the declaration does not close anything that is already in the log.
