Skip to content

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(), and Subtract() type-check but throw Error('... is not implemented yet.') at runtime (FromBuilder.ts), so those examples below are C#-only.

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));
}

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));
}

These functions always change the value by exactly 1.

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));
}

All projection functions work with these numeric types:

  • int
  • long
  • float
  • double
  • decimal

The functions automatically handle type conversion and maintain the target property’s type.

  1. Initialization: Properties start at 0 (or their default value) when first accessed
  2. Accumulation: Functions apply their operations incrementally as events are processed
  3. Type safety: Values are converted to match the target property type
  4. State preservation: Current values are maintained between events

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));
}

These functions provide powerful aggregation capabilities while keeping projection logic simple and declarative.