From Every Event
Use an every-event mapping when a read model property should update whenever any event in the projection is processed. This is usually audit metadata such as the last modification time, the latest event sequence number, or the correlation id of the operation that last touched the read model.
Every-event mappings are different from event-specific mappings. A set mapping applies only when its event type is processed. An every-event mapping applies after any event that belongs to the projection.
Map Event Context
Section titled “Map Event Context”Event context is the most common source for every-event mappings because every event has the same context shape. This keeps audit fields independent from the event payloads.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record InventoryProductRegisteredForEvery(string ProductName);
[EventType]public record InventoryItemsAdjustedForEvery(int Quantity);
[FromEvent<InventoryProductRegisteredForEvery>][FromEvent<InventoryItemsAdjustedForEvery>]public record InventoryStatusFromEvery( [Key] Guid Id, string ProductName, [FromEvery(contextProperty: nameof(EventContext.Occurred))] DateTimeOffset LastUpdated);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.FromEveryimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class InventoryProductRegisteredForEvery(val productName: String)
@EventTypedata class InventoryItemsAdjustedForEvery(val quantity: Int)
@ReadModel@FromEvent(InventoryProductRegisteredForEvery::class)@FromEvent(InventoryItemsAdjustedForEvery::class)data class InventoryStatusFromEvery( val productName: String = "",
@FromEvery(contextProperty = "occurred") val lastUpdated: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.FromEvery;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord InventoryProductRegisteredForEvery(String productName) {}
@EventTyperecord InventoryItemsAdjustedForEvery(int quantity) {}
@ReadModel@FromEvent(eventType = InventoryProductRegisteredForEvery.class)@FromEvent(eventType = InventoryItemsAdjustedForEvery.class)class InventoryStatusFromEvery { public String productName = "";
@FromEvery(contextProperty = "occurred") public String lastUpdated = "";}defmodule MyApp.Events.InventoryProductRegisteredForEvery do use Chronicle.Events.EventType, id: "inventory-product-registered-for-every-v1"
defstruct [:product_name]end
defmodule MyApp.Events.InventoryItemsAdjustedForEvery do use Chronicle.Events.EventType, id: "inventory-items-adjusted-for-every-v1"
defstruct [:quantity]end
defmodule MyApp.ReadModels.InventoryStatusFromEvery do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{InventoryItemsAdjustedForEvery, InventoryProductRegisteredForEvery}
defstruct [:id, :product_name, :last_updated]
from InventoryProductRegisteredForEvery, set: [ id: :event_source_id, product_name: :product_name ]
from InventoryItemsAdjustedForEvery
from_every set: [last_updated: :occurred]endimport { eventType, fromEvent, fromEvery, readModel } from '@cratis/chronicle';
@eventType()class InventoryProductRegisteredForEvery { constructor(readonly productName: string) {}}
@eventType()class InventoryItemsAdjustedForEvery { constructor(readonly quantity: number) {}}
@readModel()@fromEvent(InventoryProductRegisteredForEvery)@fromEvent(InventoryItemsAdjustedForEvery)class InventoryStatusFromEvery { productName = '';
@fromEvery(undefined, 'occurred') lastUpdated = new Date();}You can map multiple context fields when the read model needs a small audit trail.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record AuditableInventoryChangedForEvery(string Reason);
[FromEvent<AuditableInventoryChangedForEvery>]public record AuditableInventoryStatusFromEvery( [Key] Guid Id, [FromEvery(contextProperty: nameof(EventContext.Occurred))] DateTimeOffset LastModified, [FromEvery(contextProperty: nameof(EventContext.SequenceNumber))] EventSequenceNumber LastEventSequence, [FromEvery(contextProperty: nameof(EventContext.CorrelationId))] string LastCorrelationId);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.FromEveryimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class AuditableInventoryChangedForEvery(val reason: String)
@ReadModel@FromEvent(AuditableInventoryChangedForEvery::class)data class AuditableInventoryStatusFromEvery( @FromEvery(contextProperty = "occurred") val lastModified: String = "",
@FromEvery(contextProperty = "sequenceNumber") val lastEventSequence: String = "",
@FromEvery(contextProperty = "correlationId") val lastCorrelationId: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.FromEvery;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord AuditableInventoryChangedForEvery(String reason) {}
@ReadModel@FromEvent(eventType = AuditableInventoryChangedForEvery.class)class AuditableInventoryStatusFromEvery { @FromEvery(contextProperty = "occurred") public String lastModified = "";
@FromEvery(contextProperty = "sequenceNumber") public String lastEventSequence = "";
@FromEvery(contextProperty = "correlationId") public String lastCorrelationId = "";}defmodule MyApp.Events.AuditableInventoryChangedForEvery do use Chronicle.Events.EventType, id: "auditable-inventory-changed-for-every-v1"
defstruct [:reason]end
defmodule MyApp.ReadModels.AuditableInventoryStatusFromEvery do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.AuditableInventoryChangedForEvery
defstruct [:id, :last_modified, :last_event_sequence, :last_correlation_id]
from AuditableInventoryChangedForEvery, set: [id: :event_source_id]
from_every set: [ last_modified: :occurred, last_event_sequence: "$context.sequenceNumber", last_correlation_id: "$context.correlationId" ]endimport { eventType, fromEvent, fromEvery, readModel } from '@cratis/chronicle';
@eventType()class AuditableInventoryChangedForEvery { constructor(readonly reason: string) {}}
@readModel()@fromEvent(AuditableInventoryChangedForEvery)class AuditableInventoryStatusFromEvery { @fromEvery(undefined, 'occurred') lastModified = new Date();
@fromEvery(undefined, 'sequenceNumber') lastEventSequence = 0n;
@fromEvery(undefined, 'correlationId') lastCorrelationId = '';}Map Event Payload Properties
Section titled “Map Event Payload Properties”Every-event mappings can also read from event payload properties. Use this only when each event in the projection carries the same property name and compatible value type.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
public enum OrderStateFromEvery{ New, Confirmed, Shipped}
[EventType]public record OrderConfirmedForEvery(OrderStateFromEvery Status);
[EventType]public record OrderShippedForEvery(OrderStateFromEvery Status);
[FromEvent<OrderConfirmedForEvery>][FromEvent<OrderShippedForEvery>]public record OrderStatusFromEvery( [Key] Guid Id, [FromEvery(property: nameof(OrderConfirmedForEvery.Status))] OrderStateFromEvery CurrentStatus);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.FromEveryimport io.cratis.chronicle.readModels.ReadModel
enum class OrderStateFromEvery { New, Confirmed, Shipped}
@EventTypedata class OrderConfirmedForEvery(val status: OrderStateFromEvery)
@EventTypedata class OrderShippedForEvery(val status: OrderStateFromEvery)
@ReadModel@FromEvent(OrderConfirmedForEvery::class)@FromEvent(OrderShippedForEvery::class)data class OrderStatusFromEvery( @FromEvery(property = "status") val currentStatus: OrderStateFromEvery = OrderStateFromEvery.New)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.FromEvery;import io.cratis.chronicle.readModels.ReadModel;
enum OrderStateFromEvery { New, Confirmed, Shipped}
@EventTyperecord OrderConfirmedForEvery(OrderStateFromEvery status) {}
@EventTyperecord OrderShippedForEvery(OrderStateFromEvery status) {}
@ReadModel@FromEvent(eventType = OrderConfirmedForEvery.class)@FromEvent(eventType = OrderShippedForEvery.class)class OrderStatusFromEvery { @FromEvery(property = "status") public OrderStateFromEvery currentStatus = OrderStateFromEvery.New;}defmodule MyApp.Events.OrderConfirmedForEvery do use Chronicle.Events.EventType, id: "order-confirmed-for-every-v1"
defstruct [:status]end
defmodule MyApp.Events.OrderShippedForEvery do use Chronicle.Events.EventType, id: "order-shipped-for-every-v1"
defstruct [:status]end
defmodule MyApp.ReadModels.OrderStatusFromEvery do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{OrderConfirmedForEvery, OrderShippedForEvery}
defstruct [:id, :current_status]
from OrderConfirmedForEvery, set: [id: :event_source_id]
from OrderShippedForEvery
from_every set: [current_status: :status]endimport { eventType, fromEvent, fromEvery, readModel } from '@cratis/chronicle';
enum OrderStateFromEvery { New = 'New', Confirmed = 'Confirmed', Shipped = 'Shipped'}
@eventType()class OrderConfirmedForEvery { constructor(readonly status: OrderStateFromEvery) {}}
@eventType()class OrderShippedForEvery { constructor(readonly status: OrderStateFromEvery) {}}
@readModel()@fromEvent(OrderConfirmedForEvery)@fromEvent(OrderShippedForEvery)class OrderStatusFromEvery { @fromEvery('status') currentStatus = OrderStateFromEvery.New;}If a client supports convention-based property mapping, omitting the source property uses the read model property name.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record ProductRenamedForEveryConvention(string Name, int Version);
[EventType]public record ProductPriceChangedForEveryConvention(decimal Price, int Version);
[FromEvent<ProductRenamedForEveryConvention>][FromEvent<ProductPriceChangedForEveryConvention>]public record ProductVersionFromEveryConvention( [Key] Guid Id, string Name, decimal Price, [FromEvery] int Version);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.FromEveryimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class ProductRenamedForEveryConvention(val name: String, val version: Int)
@EventTypedata class ProductPriceChangedForEveryConvention(val price: Double, val version: Int)
@ReadModel@FromEvent(ProductRenamedForEveryConvention::class)@FromEvent(ProductPriceChangedForEveryConvention::class)data class ProductVersionFromEveryConvention( val name: String = "", val price: Double = 0.0,
@FromEvery val version: Int = 0)import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.FromEvery;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord ProductRenamedForEveryConvention(String name, int version) {}
@EventTyperecord ProductPriceChangedForEveryConvention(double price, int version) {}
@ReadModel@FromEvent(eventType = ProductRenamedForEveryConvention.class)@FromEvent(eventType = ProductPriceChangedForEveryConvention.class)class ProductVersionFromEveryConvention { public String name = ""; public double price = 0.0;
@FromEvery public int version = 0;}Elixir does not support this workflow yet.import { eventType, fromEvent, fromEvery, readModel } from '@cratis/chronicle';
@eventType()class ProductRenamedForEveryConvention { constructor(readonly name: string, readonly version: number) {}}
@eventType()class ProductPriceChangedForEveryConvention { constructor(readonly price: number, readonly version: number) {}}
@readModel()@fromEvent(ProductRenamedForEveryConvention)@fromEvent(ProductPriceChangedForEveryConvention)class ProductVersionFromEveryConvention { name = ''; price = 0;
@fromEvery() version = 0;}Combine With Event-Specific Mappings
Section titled “Combine With Event-Specific Mappings”When an event is processed, Chronicle applies the event-specific mappings for that event and the every-event mappings for the projection. This lets the read model update business fields and audit metadata in the same event pass.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections.ModelBound;
[EventType]public record UserRegisteredForEvery(string Name, string Email);
[EventType]public record UserNameChangedForEvery(string NewName);
[EventType]public record UserEmailChangedForEvery(string NewEmail);
[FromEvent<UserRegisteredForEvery>][FromEvent<UserNameChangedForEvery>][FromEvent<UserEmailChangedForEvery>]public record UserProfileFromEvery( [Key] Guid Id, [SetFrom<UserRegisteredForEvery>(nameof(UserRegisteredForEvery.Name))] [SetFrom<UserNameChangedForEvery>(nameof(UserNameChangedForEvery.NewName))] string Name, [SetFrom<UserRegisteredForEvery>(nameof(UserRegisteredForEvery.Email))] [SetFrom<UserEmailChangedForEvery>(nameof(UserEmailChangedForEvery.NewEmail))] string Email, [FromEvery(contextProperty: nameof(EventContext.Occurred))] DateTimeOffset LastUpdated);import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.FromEveryimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class UserRegisteredForEvery(val name: String, val email: String)
@EventTypedata class UserNameChangedForEvery(val newName: String)
@EventTypedata class UserEmailChangedForEvery(val newEmail: String)
@ReadModel@FromEvent(UserRegisteredForEvery::class)@FromEvent(UserNameChangedForEvery::class)@FromEvent(UserEmailChangedForEvery::class)data class UserProfileFromEvery( @SetFrom("name", UserRegisteredForEvery::class) @SetFrom("newName", UserNameChangedForEvery::class) val name: String = "",
@SetFrom("email", UserRegisteredForEvery::class) @SetFrom("newEmail", UserEmailChangedForEvery::class) val email: String = "",
@FromEvery(contextProperty = "occurred") val lastUpdated: String = "")import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.FromEvent;import io.cratis.chronicle.projections.FromEvery;import io.cratis.chronicle.projections.SetFrom;import io.cratis.chronicle.readModels.ReadModel;
@EventTyperecord UserRegisteredForEvery(String name, String email) {}
@EventTyperecord UserNameChangedForEvery(String newName) {}
@EventTyperecord UserEmailChangedForEvery(String newEmail) {}
@ReadModel@FromEvent(eventType = UserRegisteredForEvery.class)@FromEvent(eventType = UserNameChangedForEvery.class)@FromEvent(eventType = UserEmailChangedForEvery.class)class UserProfileFromEvery { @SetFrom(propertyPath = "name", eventType = UserRegisteredForEvery.class) @SetFrom(propertyPath = "newName", eventType = UserNameChangedForEvery.class) public String name = "";
@SetFrom(propertyPath = "email", eventType = UserRegisteredForEvery.class) @SetFrom(propertyPath = "newEmail", eventType = UserEmailChangedForEvery.class) public String email = "";
@FromEvery(contextProperty = "occurred") public String lastUpdated = "";}defmodule MyApp.Events.UserRegisteredForEvery do use Chronicle.Events.EventType, id: "user-registered-for-every-v1"
defstruct [:name, :email]end
defmodule MyApp.Events.UserNameChangedForEvery do use Chronicle.Events.EventType, id: "user-name-changed-for-every-v1"
defstruct [:new_name]end
defmodule MyApp.Events.UserEmailChangedForEvery do use Chronicle.Events.EventType, id: "user-email-changed-for-every-v1"
defstruct [:new_email]end
defmodule MyApp.ReadModels.UserProfileFromEvery do use Chronicle.ReadModels.ReadModel
alias MyApp.Events.{UserEmailChangedForEvery, UserNameChangedForEvery, UserRegisteredForEvery}
defstruct [:id, :name, :email, :last_updated]
from UserRegisteredForEvery, set: [ id: :event_source_id, name: :name, email: :email ]
from UserNameChangedForEvery, set: [name: :new_name]
from UserEmailChangedForEvery, set: [email: :new_email]
from_every set: [last_updated: :occurred]endimport { eventType, fromEvent, fromEvery, readModel, setFrom } from '@cratis/chronicle';
@eventType()class UserRegisteredForEvery { constructor(readonly name: string, readonly email: string) {}}
@eventType()class UserNameChangedForEvery { constructor(readonly newName: string) {}}
@eventType()class UserEmailChangedForEvery { constructor(readonly newEmail: string) {}}
@readModel()@fromEvent(UserRegisteredForEvery)@fromEvent(UserNameChangedForEvery)@fromEvent(UserEmailChangedForEvery)class UserProfileFromEvery { @setFrom(UserRegisteredForEvery, 'name') @setFrom(UserNameChangedForEvery, 'newName') name = '';
@setFrom(UserRegisteredForEvery, 'email') @setFrom(UserEmailChangedForEvery, 'newEmail') email = '';
@fromEvery(undefined, 'occurred') lastUpdated = new Date();}For example, when the name-change event in the examples above is processed:
- The name mapping updates the read model’s name.
- The every-event mapping updates the last-modified field from the event context.
Declarative Equivalent
Section titled “Declarative Equivalent”Every-event mappings are part of the projection definition Chronicle receives. Clients that expose both model-bound and standalone projection definitions send the same kind of every-event definition to Chronicle.
using Cratis.Chronicle.Events;using Cratis.Chronicle.Keys;using Cratis.Chronicle.Projections;
[EventType]public record InventoryRegisteredDeclarativeForEvery(string ProductName);
[EventType]public record InventoryAdjustedDeclarativeForEvery(int Quantity);
public record InventoryStatusDeclarativeFromEvery( [property: Key] Guid Id, string ProductName, DateTimeOffset LastUpdated);
public class InventoryStatusDeclarativeProjection : IProjectionFor<InventoryStatusDeclarativeFromEvery>{ public void Define(IProjectionBuilderFor<InventoryStatusDeclarativeFromEvery> builder) => builder .From<InventoryRegisteredDeclarativeForEvery>() .From<InventoryAdjustedDeclarativeForEvery>() .FromEvery(_ => _ .Set(m => m.LastUpdated) .ToEventContextProperty(c => c.Occurred));}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionFor
@EventTypedata class InventoryRegisteredDeclarativeForEvery(val productName: String)
@EventTypedata class InventoryAdjustedDeclarativeForEvery(val quantity: Int)
data class InventoryStatusDeclarativeFromEvery( val productName: String = "", val lastUpdated: String = "")
class InventoryStatusDeclarativeProjection : IProjectionFor<InventoryStatusDeclarativeFromEvery> { override fun define(builder: IProjectionBuilderFor<InventoryStatusDeclarativeFromEvery>) { builder .from(InventoryRegisteredDeclarativeForEvery::class) .from(InventoryAdjustedDeclarativeForEvery::class) .fromEvery { it -> it.set(InventoryStatusDeclarativeFromEvery::lastUpdated).toEventContextProperty("occurred") } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
@EventTyperecord InventoryRegisteredDeclarativeForEvery(String productName) {}
@EventTyperecord InventoryAdjustedDeclarativeForEvery(int quantity) {}
class InventoryStatusDeclarativeFromEvery { public String productName = ""; public String lastUpdated = "";}
class InventoryStatusDeclarativeProjection implements IProjectionFor<InventoryStatusDeclarativeFromEvery> { @Override public void define(IProjectionBuilderFor<InventoryStatusDeclarativeFromEvery> builder) { builder .from(InventoryRegisteredDeclarativeForEvery.class) .from(InventoryAdjustedDeclarativeForEvery.class) .fromEvery(every -> { every.set("lastUpdated").toEventContextProperty("occurred"); }); }}defmodule MyApp.Events.InventoryRegisteredDeclarativeForEvery do use Chronicle.Events.EventType, id: "inventory-registered-declarative-for-every-v1"
defstruct [:product_name]end
defmodule MyApp.Events.InventoryAdjustedDeclarativeForEvery do use Chronicle.Events.EventType, id: "inventory-adjusted-declarative-for-every-v1"
defstruct [:quantity]end
defmodule MyApp.ReadModels.InventoryStatusDeclarativeFromEvery do use Chronicle.ReadModels.ReadModel
defstruct [:id, :product_name, :last_updated]end
defmodule MyApp.Projections.InventoryStatusDeclarativeProjection do use Chronicle.Projections.Projection, model: MyApp.ReadModels.InventoryStatusDeclarativeFromEvery
alias MyApp.Events.{InventoryAdjustedDeclarativeForEvery, InventoryRegisteredDeclarativeForEvery}
from InventoryRegisteredDeclarativeForEvery, set: [ id: :event_source_id, product_name: :product_name ]
from InventoryAdjustedDeclarativeForEvery
from_every set: [last_updated: :occurred]endimport { eventType, IProjectionBuilderFor, IProjectionFor, projection, readModel } from '@cratis/chronicle';
@eventType()class InventoryRegisteredDeclarativeForEvery { constructor(readonly productName: string) {}}
@eventType()class InventoryAdjustedDeclarativeForEvery { constructor(readonly quantity: number) {}}
@readModel()class InventoryStatusDeclarativeFromEvery { productName = ''; lastUpdated = new Date();}
@projection('', InventoryStatusDeclarativeFromEvery)class InventoryStatusDeclarativeProjection implements IProjectionFor<InventoryStatusDeclarativeFromEvery> { define(builder: IProjectionBuilderFor<InventoryStatusDeclarativeFromEvery>): void { builder .from(InventoryRegisteredDeclarativeForEvery) .from(InventoryAdjustedDeclarativeForEvery) .fromEvery(_ => _ .set(m => m.lastUpdated) .toEventContextProperty('occurred')); }}Use Cases
Section titled “Use Cases”| Use case | Typical source |
|---|---|
| Last modification time | Event context occurrence time |
| Last event sequence | Event context sequence number |
| Last operation id | Event context correlation id |
| Current status shared by all events | Event payload property |
Prefer context properties for audit metadata. Use payload properties only when the event contracts are intentionally consistent. If a property should update only for selected event types, use an event-specific mapping instead.
Limitations
Section titled “Limitations”- Only one every-event mapping can target the same read model property.
- Payload-property mappings are skipped for events that do not carry the source property.
- Child-projection inclusion is client-specific; check the client-specific projection API if you need every-event mappings to include or exclude child projections.