Counter
For a short summary of Counter, see Reventless Components Overview.
This component follows the Reventless Component Structure Pattern, using separate files for interface definitions (Counter.res), builder logic (Counter_Builder.res), runtime operations (Counter_Operations.res), and runtime callbacks (Counter_Callback.res).
Overview
The Counter component enables coordination across multiple events by counting references and triggering actions when a target count is reached. It's primarily used in EventMappers to implement patterns like "wait for N events before proceeding" or collecting data from multiple sources before generating a command.
Purpose and Responsibilities
- Responsibility: Track event references by counter ID; countdown to zero when references are collected; emit Triggered events when count reaches zero; collect target data from multiple events; enable multi-event coordination patterns
- In: Count operations and target data from EventMapper
- Out: Triggered events (Counter.CountFinished) when count reaches zero
Counter Operations
The Counter provides two main operations:
Count Operation
Tracks references and increments/decrements counters:
type countItem = {
counterId: string, // Unique counter identifier
reference: string, // Reference (typically correlationId)
inc: int, // Increment value (positive or negative)
}
type count = array<countItem> => promise<unit>
Usage:
// Increment counter by 1
await count([{
counterId: "order-123",
reference: "correlation-abc",
inc: 1
}])
// Set expected count (negative value)
await count([{
counterId: "order-123",
reference: "expected-count",
inc: -5 // Expect 5 items
}])
AddToCounterTarget Operation
Associates target data with a counter for later retrieval:
type counterTargetRef = {
counterId: string, // Counter identifier
target: 'a, // Target data to collect
targetRef: string, // Reference for this target
}
type addToCounterTarget = counterTargetRef => promise<unit>
Usage:
// Collect order items
await addToCounterTarget({
counterId: "order-123",
target: {itemId: "item-1", quantity: 2, price: 19.99},
targetRef: "correlation-abc"
})
Usage Pattern
Counter in EventMappings
The Counter is used within an Aggregate's <Entity>_Mappings.res file
(@@reventless.mappings). The PPX injects module Target, module type Mapping,
let counter = None, and open Reventless.EventMapping (so Count,
CountMulti, AddToCounterTarget, Publish are in scope unqualified). Override
let counter to enable the Counter:
@@reventless.mappings
// Map Order events to Invoice commands
module OrderMapping = {
module Source = Order
let map = (orderId, event, _queryEngine) =>
switch event {
| Order.Created({expectedItems}) => [
// Set the expected count (negative number)
CountMulti(orderId->Order.Id.toString, -expectedItems), // Expect this many items
]
| Order.ItemAdded({itemId, quantity, price}) => [
// Add item data to counter targets
AddToCounterTarget({counterId: orderId->Order.Id.toString, target: {itemId, quantity, price}}),
// Increment counter by 1
Count(orderId->Order.Id.toString),
]
| Order.ItemRemoved(_) => [
// Decrement counter when item removed
CountMulti(orderId->Order.Id.toString, -1),
]
| _ => []
}
}
// Handle Counter.CountFinished events
module CounterMapping = {
module Source = Counter.Source
let map = (counterId, event, _queryEngine) =>
switch event {
| Counter.Source.CountFinished =>
// Counter reached zero — all items collected; targets hold the data
[Publish(counterId->Invoice.Id.fromString, Invoice.Generate)]
}
}
let mappings: array<module(Mapping)> = [module(OrderMapping), module(CounterMapping)]
// Enable counter for this EventMapper (overrides the default `let counter = None`)
let counter = Some(module(Invoice_Counter: Counter.T))