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);Kotlin does not support this workflow yet.Java does not support this workflow yet.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);Kotlin does not support this workflow yet.Java does not support this workflow yet.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);Kotlin does not support this workflow yet.Java does not support this workflow yet.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);Kotlin does not support this workflow yet.Java does not support this workflow yet.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);Kotlin does not support this workflow yet.Java does not support this workflow yet.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));}Kotlin does not support this workflow yet.Java does not support this workflow yet.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.