Projection functions
Projections support several built-in functions for mathematical operations and counting. These functions allow you to perform calculations directly within projections without needing custom logic.
TypeScript note: of these, only
Increment()/Decrement()are implemented in TypeScript’s fluent builder today.Count(),Add(), andSubtract()type-check but throwError('... is not implemented yet.')at runtime (FromBuilder.ts), so those examples below are C#-only.
Counting events
Section titled “Counting events”Use Count() to increment a counter each time an event is processed:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record DecFunctionsUserLoggedIn(string Username);
[EventType]public record DecFunctionsUserPerformedAction(string Username, string ActionType);
public record DecFunctionsUserActivity( string Username, int LoginCount, int ActionCount);
public class DecFunctionsUserActivityProjection : IProjectionFor<DecFunctionsUserActivity>{ public void Define(IProjectionBuilderFor<DecFunctionsUserActivity> builder) => builder .AutoMap() .From<DecFunctionsUserLoggedIn>(_ => _ .Count(m => m.LoginCount)) .From<DecFunctionsUserPerformedAction>(_ => _ .Count(m => m.ActionCount));}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionFor
@EventTypedata class DecFunctionsUserLoggedIn(val username: String)
@EventTypedata class DecFunctionsUserPerformedAction(val username: String, val actionType: String)
data class DecFunctionsUserActivity( val username: String = "", val loginCount: Int = 0, val actionCount: Int = 0)
class DecFunctionsUserActivityProjection : IProjectionFor<DecFunctionsUserActivity> { override fun define(builder: IProjectionBuilderFor<DecFunctionsUserActivity>) { builder .autoMap() .from(DecFunctionsUserLoggedIn::class) { it.count(DecFunctionsUserActivity::loginCount) } .from(DecFunctionsUserPerformedAction::class) { it.count(DecFunctionsUserActivity::actionCount) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
@EventTyperecord DecFunctionsUserLoggedIn(String username) {}
@EventTyperecord DecFunctionsUserPerformedAction(String username, String actionType) {}
class DecFunctionsUserActivity { public String username = ""; public int loginCount = 0; public int actionCount = 0;}
class DecFunctionsUserActivityProjection implements IProjectionFor<DecFunctionsUserActivity> { @Override public void define(IProjectionBuilderFor<DecFunctionsUserActivity> builder) { builder .autoMap() .from(DecFunctionsUserLoggedIn.class, fb -> { fb.count("loginCount"); }) .from(DecFunctionsUserPerformedAction.class, fb -> { fb.count("actionCount"); }); }}defmodule MyApp.Events.DecFunctionsUserLoggedIn do use Chronicle.Events.EventType, id: "dec-functions-user-logged-in"
defstruct [:username]end
defmodule MyApp.Events.DecFunctionsUserPerformedAction do use Chronicle.Events.EventType, id: "dec-functions-user-performed-action"
defstruct [:username, :action_type]end
defmodule MyApp.ReadModels.DecFunctionsUserActivity do use Chronicle.ReadModels.ReadModel
defstruct [:username, login_count: 0, action_count: 0]end
defmodule MyApp.Projections.DecFunctionsUserActivityProjection do use Chronicle.Projections.Projection, model: MyApp.ReadModels.DecFunctionsUserActivity
alias MyApp.Events.{DecFunctionsUserLoggedIn, DecFunctionsUserPerformedAction}
from DecFunctionsUserLoggedIn, count: :login_count
from DecFunctionsUserPerformedAction, count: :action_countendimport { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecFunctionsUserLoggedIn { constructor(readonly username: string) {}}
@eventType()class DecFunctionsUserPerformedAction { constructor(readonly username: string, readonly actionType: string) {}}
class DecFunctionsUserActivity { username = ''; loginCount = 0; actionCount = 0;}
@projection()class DecFunctionsUserActivityProjection implements IProjectionFor<DecFunctionsUserActivity> { define(builder: IProjectionBuilderFor<DecFunctionsUserActivity>): void { builder .autoMap() .from(DecFunctionsUserLoggedIn, _ => _ .count(m => m.loginCount)) .from(DecFunctionsUserPerformedAction, _ => _ .count(m => m.actionCount)); }}Increment and decrement
Section titled “Increment and decrement”Use Increment() and Decrement() to add or subtract 1 from a property:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record DecFunctionsItemAdded(string Name);
[EventType]public record DecFunctionsItemRemoved(string Name);
public record DecFunctionsInventory(int Quantity);
public class DecFunctionsInventoryProjection : IProjectionFor<DecFunctionsInventory>{ public void Define(IProjectionBuilderFor<DecFunctionsInventory> builder) => builder .AutoMap() .From<DecFunctionsItemAdded>(_ => _ .Increment(m => m.Quantity)) .From<DecFunctionsItemRemoved>(_ => _ .Decrement(m => m.Quantity));}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionFor
@EventTypedata class DecFunctionsItemAdded(val name: String)
@EventTypedata class DecFunctionsItemRemoved(val name: String)
data class DecFunctionsInventory(val quantity: Int = 0)
class DecFunctionsInventoryProjection : IProjectionFor<DecFunctionsInventory> { override fun define(builder: IProjectionBuilderFor<DecFunctionsInventory>) { builder .autoMap() .from(DecFunctionsItemAdded::class) { it.increment(DecFunctionsInventory::quantity) } .from(DecFunctionsItemRemoved::class) { it.decrement(DecFunctionsInventory::quantity) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
@EventTyperecord DecFunctionsItemAdded(String name) {}
@EventTyperecord DecFunctionsItemRemoved(String name) {}
class DecFunctionsInventory { public int quantity = 0;}
class DecFunctionsInventoryProjection implements IProjectionFor<DecFunctionsInventory> { @Override public void define(IProjectionBuilderFor<DecFunctionsInventory> builder) { builder .autoMap() .from(DecFunctionsItemAdded.class, fb -> { fb.increment("quantity"); }) .from(DecFunctionsItemRemoved.class, fb -> { fb.decrement("quantity"); }); }}defmodule MyApp.Events.DecFunctionsItemAdded do use Chronicle.Events.EventType, id: "dec-functions-item-added"
defstruct [:name]end
defmodule MyApp.Events.DecFunctionsItemRemoved do use Chronicle.Events.EventType, id: "dec-functions-item-removed"
defstruct [:name]end
defmodule MyApp.ReadModels.DecFunctionsInventory do use Chronicle.ReadModels.ReadModel
defstruct quantity: 0end
defmodule MyApp.Projections.DecFunctionsInventoryProjection do use Chronicle.Projections.Projection, model: MyApp.ReadModels.DecFunctionsInventory
alias MyApp.Events.{DecFunctionsItemAdded, DecFunctionsItemRemoved}
from DecFunctionsItemAdded, add: [quantity: 1]
from DecFunctionsItemRemoved, subtract: [quantity: 1]endimport { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecFunctionsItemAdded { name = '';}
@eventType()class DecFunctionsItemRemoved { name = '';}
class DecFunctionsInventory { quantity = 0;}
@projection()class DecFunctionsInventoryProjection implements IProjectionFor<DecFunctionsInventory> { define(builder: IProjectionBuilderFor<DecFunctionsInventory>): void { builder .autoMap() .from(DecFunctionsItemAdded, _ => _ .increment(m => m.quantity)) .from(DecFunctionsItemRemoved, _ => _ .decrement(m => m.quantity)); }}These functions always change the value by exactly 1.
Add and subtract with values
Section titled “Add and subtract with values”Use Add() and Subtract() to add or subtract specific values from event properties:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record DecFunctionsAccountOpened(string Number);
[EventType]public record DecFunctionsMoneyDeposited(decimal Amount);
[EventType]public record DecFunctionsMoneyWithdrawn(decimal Amount);
public record DecFunctionsAccount(string Number, decimal Balance);
public class DecFunctionsAccountProjection : IProjectionFor<DecFunctionsAccount>{ public void Define(IProjectionBuilderFor<DecFunctionsAccount> builder) => builder .AutoMap() .From<DecFunctionsAccountOpened>(_ => _ .Set(m => m.Balance).ToValue(0m)) .From<DecFunctionsMoneyDeposited>(_ => _ .Add(m => m.Balance).With(e => e.Amount)) .From<DecFunctionsMoneyWithdrawn>(_ => _ .Subtract(m => m.Balance).With(e => e.Amount));}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionForimport java.math.BigDecimal
@EventTypedata class DecFunctionsAccountOpened(val number: String)
@EventTypedata class DecFunctionsMoneyDeposited(val amount: BigDecimal)
@EventTypedata class DecFunctionsMoneyWithdrawn(val amount: BigDecimal)
data class DecFunctionsAccount(val number: String = "", val balance: BigDecimal = BigDecimal.ZERO)
class DecFunctionsAccountProjection : IProjectionFor<DecFunctionsAccount> { override fun define(builder: IProjectionBuilderFor<DecFunctionsAccount>) { builder .autoMap() .from(DecFunctionsAccountOpened::class) .from(DecFunctionsMoneyDeposited::class) { it.add(DecFunctionsAccount::balance).with("amount") } .from(DecFunctionsMoneyWithdrawn::class) { it.subtract(DecFunctionsAccount::balance).with("amount") } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
import java.math.BigDecimal;
@EventTyperecord DecFunctionsAccountOpened(String number) {}
@EventTyperecord DecFunctionsMoneyDeposited(BigDecimal amount) {}
@EventTyperecord DecFunctionsMoneyWithdrawn(BigDecimal amount) {}
class DecFunctionsAccount { public String number = ""; public BigDecimal balance = BigDecimal.ZERO;}
class DecFunctionsAccountProjection implements IProjectionFor<DecFunctionsAccount> { @Override public void define(IProjectionBuilderFor<DecFunctionsAccount> builder) { builder .autoMap() .from(DecFunctionsAccountOpened.class) .from(DecFunctionsMoneyDeposited.class, fb -> { fb.add("balance").with("amount"); }) .from(DecFunctionsMoneyWithdrawn.class, fb -> { fb.subtract("balance").with("amount"); }); }}defmodule MyApp.Events.DecFunctionsAccountOpened do use Chronicle.Events.EventType, id: "dec-functions-account-opened"
defstruct [:number]end
defmodule MyApp.Events.DecFunctionsMoneyDeposited do use Chronicle.Events.EventType, id: "dec-functions-money-deposited"
defstruct [:amount]end
defmodule MyApp.Events.DecFunctionsMoneyWithdrawn do use Chronicle.Events.EventType, id: "dec-functions-money-withdrawn"
defstruct [:amount]end
defmodule MyApp.ReadModels.DecFunctionsAccount do use Chronicle.ReadModels.ReadModel
defstruct number: "", balance: 0end
defmodule MyApp.Projections.DecFunctionsAccountProjection do use Chronicle.Projections.Projection, model: MyApp.ReadModels.DecFunctionsAccount
alias MyApp.Events.{DecFunctionsAccountOpened, DecFunctionsMoneyDeposited, DecFunctionsMoneyWithdrawn}
from DecFunctionsAccountOpened, set: [balance: 0]
from DecFunctionsMoneyDeposited, add: [balance: :amount]
from DecFunctionsMoneyWithdrawn, subtract: [balance: :amount]endimport { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecFunctionsAccountOpened { constructor(readonly number: string) {}}
@eventType()class DecFunctionsMoneyDeposited { constructor(readonly amount: number) {}}
@eventType()class DecFunctionsMoneyWithdrawn { constructor(readonly amount: number) {}}
class DecFunctionsAccount { number = ''; balance = 0;}
@projection()class DecFunctionsAccountProjection implements IProjectionFor<DecFunctionsAccount> { define(builder: IProjectionBuilderFor<DecFunctionsAccount>): void { builder .autoMap() .from(DecFunctionsAccountOpened, _ => _ .set(m => m.balance).toValue(0)) .from(DecFunctionsMoneyDeposited, _ => _ .add(m => m.balance).with(e => e.amount)) .from(DecFunctionsMoneyWithdrawn, _ => _ .subtract(m => m.balance).with(e => e.amount)); }}Supported types
Section titled “Supported types”All projection functions work with these numeric types:
intlongfloatdoubledecimal
The functions automatically handle type conversion and maintain the target property’s type.
How functions work
Section titled “How functions work”- Initialization: Properties start at 0 (or their default value) when first accessed
- Accumulation: Functions apply their operations incrementally as events are processed
- Type safety: Values are converted to match the target property type
- State preservation: Current values are maintained between events
Combining functions
Section titled “Combining functions”You can use multiple functions in a single projection:
[EventType]public record DecFunctionsTransaction(decimal Amount);
public record DecFunctionsTransactionSummary( int TransactionCount, decimal TotalAmount, int ProcessedEvents);
public class DecFunctionsTransactionSummaryProjection : IProjectionFor<DecFunctionsTransactionSummary>{ public void Define(IProjectionBuilderFor<DecFunctionsTransactionSummary> builder) => builder .From<DecFunctionsTransaction>(_ => _ .Count(m => m.TransactionCount) .Add(m => m.TotalAmount).With(e => e.Amount) .Increment(m => m.ProcessedEvents));}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.projections.IProjectionForimport java.math.BigDecimal
@EventTypedata class DecFunctionsTransaction(val amount: BigDecimal)
data class DecFunctionsTransactionSummary( val transactionCount: Int = 0, val totalAmount: BigDecimal = BigDecimal.ZERO, val processedEvents: Int = 0)
class DecFunctionsTransactionSummaryProjection : IProjectionFor<DecFunctionsTransactionSummary> { override fun define(builder: IProjectionBuilderFor<DecFunctionsTransactionSummary>) { builder .from(DecFunctionsTransaction::class) { it.count(DecFunctionsTransactionSummary::transactionCount) it.add(DecFunctionsTransactionSummary::totalAmount).with("amount") it.increment(DecFunctionsTransactionSummary::processedEvents) } }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;
import java.math.BigDecimal;
@EventTyperecord DecFunctionsTransaction(BigDecimal amount) {}
class DecFunctionsTransactionSummary { public int transactionCount = 0; public BigDecimal totalAmount = BigDecimal.ZERO; public int processedEvents = 0;}
class DecFunctionsTransactionSummaryProjection implements IProjectionFor<DecFunctionsTransactionSummary> { @Override public void define(IProjectionBuilderFor<DecFunctionsTransactionSummary> builder) { builder .from(DecFunctionsTransaction.class, fb -> { fb.count("transactionCount"); fb.add("totalAmount").with("amount"); fb.increment("processedEvents"); }); }}defmodule MyApp.Events.DecFunctionsTransaction do use Chronicle.Events.EventType, id: "dec-functions-transaction"
defstruct [:amount]end
defmodule MyApp.ReadModels.DecFunctionsTransactionSummary do use Chronicle.ReadModels.ReadModel
defstruct transaction_count: 0, total_amount: 0, processed_events: 0end
defmodule MyApp.Projections.DecFunctionsTransactionSummaryProjection do use Chronicle.Projections.Projection, model: MyApp.ReadModels.DecFunctionsTransactionSummary
alias MyApp.Events.DecFunctionsTransaction
from DecFunctionsTransaction, count: :transaction_count, add: [total_amount: :amount, processed_events: 1]endimport { eventType, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class DecFunctionsTransaction { constructor(readonly amount: number) {}}
class DecFunctionsTransactionSummary { transactionCount = 0; totalAmount = 0; processedEvents = 0;}
@projection()class DecFunctionsTransactionSummaryProjection implements IProjectionFor<DecFunctionsTransactionSummary> { define(builder: IProjectionBuilderFor<DecFunctionsTransactionSummary>): void { builder .from(DecFunctionsTransaction, _ => _ .count(m => m.transactionCount) .add(m => m.totalAmount).with(e => e.amount) .increment(m => m.processedEvents)); }}These functions provide powerful aggregation capabilities while keeping projection logic simple and declarative.