Annotations
@EventType
Section titled “@EventType”Marks a data class as a Chronicle event type.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Stable identifier. Defaults to class name. |
generation | Int | 1 | Schema version. Increment when shape changes. |
tombstone | Boolean | false | Signals event source retirement. |
import io.cratis.chronicle.events.EventType
@EventTypedata class OrderPlaced(val orderId: String, val totalAmount: Double)Omitting id is the common case — Chronicle uses OrderPlaced as the
identifier automatically.
@Reactor
Section titled “@Reactor”Marks a class as a Chronicle reactor. Each public method becomes a handler for the event type of its first parameter.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Stable identifier. Defaults to class name. |
eventSequence | String | event log | The event sequence to observe. Overridden by @EventSequence. |
A handler takes the event, and optionally an EventContext carrying the event’s
metadata:
import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.observation.Reactor
@Reactorclass OrderNotifications { fun orderPlaced(event: OrderPlaced) { println("Order ${event.orderId} placed") }
fun orderShipped(event: OrderShipped, context: EventContext) { println("Order ${event.orderId} shipped at ${context.occurred}") }}Supply an explicit id only when you need the identifier to survive class renames.
@OnceOnly
Section titled “@OnceOnly”Excludes a reactor, or a single handler, from replay. Put it on the class and the whole reactor is registered as non-replayable, so redaction, revision, and observer rewind never replay it. Put it on one method and only that handler is skipped when an event arrives as part of a replay — the reactor’s other handlers still replay.
Use it for side effects where running again is worse than never running again.
import io.cratis.chronicle.observation.OnceOnlyimport io.cratis.chronicle.observation.Reactor
@Reactorclass PaymentNotifications { @OnceOnly fun orderPlaced(event: OrderPlaced) { println("Charging for ${event.orderId} - never repeated on replay") }}@Replay
Section titled “@Replay”Marks a reactor handler as the one to run while events are being replayed. When an event type has a handler marked with this, it takes over for the duration of the replay and the everyday handler does not also run. Without one, the everyday handler keeps running during replay.
Use @OnceOnly instead when the side effect should simply not happen again on replay.
import io.cratis.chronicle.observation.Reactorimport io.cratis.chronicle.observation.Replay
@Reactorclass ShippingNotifications { fun orderPlaced(event: OrderPlaced) { println("Emailing the customer about ${event.orderId}") }
@Replay fun orderPlacedDuringReplay(event: OrderPlaced) { println("Rebuilding ${event.orderId} without emailing anyone") }}@Reducer
Section titled “@Reducer”Marks a class as a reducer. Each public method folds one event type into the read model.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Stable identifier. Defaults to class name. |
eventSequence | String | event log | The event sequence to observe. Overridden by @EventSequence. |
isActive | Boolean | true | Whether the kernel runs the reducer. |
A handler takes the event, the state so far, and optionally an EventContext.
The state is null until the first event for an event source has been folded in.
import io.cratis.chronicle.events.EventContextimport io.cratis.chronicle.observation.Reducer
@Reducerclass OrderSummaryReducer { fun orderPlaced(event: OrderPlaced, state: OrderSummary?): OrderSummary = (state ?: OrderSummary()).copy(orderId = event.orderId)
fun orderShipped( event: OrderShipped, state: OrderSummary?, context: EventContext ): OrderSummary = (state ?: OrderSummary()).copy(status = "shipped at ${context.occurred}")}@ReadModel
Section titled “@ReadModel”Marks a data class as a Chronicle read model.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Stable identifier. Defaults to class name. |
displayName | String | "" | Human-readable label. Defaults to name. |
import io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class OrderSummary(val orderId: String = "", val status: String = "pending")@Passive
Section titled “@Passive”Marks a model-bound read model’s projection as passive — registered with the kernel but not actively run. A passive projection’s read model is computed on demand rather than kept up to date as events arrive. Placed on the read model class.
No parameters.
import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.readModels.Passiveimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class SnapshotCreated(val data: String)
@Passive@ReadModel@FromEvent(SnapshotCreated::class)data class HistoricalSnapshot(val data: String = "")@Projection
Section titled “@Projection”Marks a class as a Chronicle projection, or overrides the projection identifier on a model-bound read model. It is optional — when omitted, the class simple name is used as the identifier.
For a declarative projection the read model type is inferred from the
IProjectionFor<T> type parameter. For a model-bound projection the
annotated class is itself the read model.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Stable identifier. Defaults to class name. |
eventSequence | String | event log | The event sequence to observe. Overridden by @EventSequence. |
import io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.Projectionimport io.cratis.chronicle.readModels.ReadModel
@ReadModel@Projection(eventSequence = "outbox")@FromEvent(OrderPlaced::class)data class OutboxOrderTracking(val orderId: String = "")@EventSequence
Section titled “@EventSequence”Points an observer — a reactor, a reducer or a projection — at the event
sequence it observes. This is the standalone alternative to the
eventSequence parameter on @Reactor, @Reducer and
@Projection; reach for it when the sequence is the only thing
being configured, so the observer keeps its conventional identifier.
| Parameter | Type | Default | Description |
|---|---|---|---|
value | String | (required) | The event sequence to observe. |
import io.cratis.chronicle.observation.EventSequenceimport io.cratis.chronicle.observation.Reactor
@Reactor@EventSequence("outbox")class OutboxOrderNotifications { fun orderPlaced(event: OrderPlaced) { println("Order ${event.orderId} placed, observed from the outbox") }}When both this annotation and the eventSequence parameter are present, this
annotation wins.
There is no @EventLog counterpart. In the .NET client it exists to override
the inbox routing that [EventStore] on an event type turns on — and the
Kotlin client has no @EventStore, so an observer without an explicit
sequence already reads from the event log.
@Constraint
Section titled “@Constraint”Marks a class as a Chronicle constraint definition. The class must implement
IConstraint.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Stable identifier. Defaults to class name. |
@Unique
Section titled “@Unique”Marks a property or an event type as needing to be unique - the model-bound
alternative to a hand-written IConstraint. On a property, no two events of
that type may carry the same value; applying it with the same id to
properties on more than one event type groups them under one constraint,
checked across all of them combined. On an event type, at most one instance
of that type may exist per event source.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Constraint name, defaulting to the property/class. |
message | String | "" | Message for a constraint violation. |
import io.cratis.chronicle.constraints.Uniqueimport io.cratis.chronicle.events.EventType
@EventTypedata class ProjectCreated(@Unique val name: String, val description: String)
@EventType@Uniquedata class WorkspaceClaimed(val slug: String)Pair it with @RemoveConstraint on a removal event to release the value for reuse.
@RemoveConstraint
Section titled “@RemoveConstraint”Marks an event type as releasing a named @Unique constraint when it is appended - typically a deletion or lifecycle-ending event. Repeatable, so one event can release more than one constraint.
Only one event type may release a given constraint name with this client - if more than one declares the same name, registration keeps the first one it finds and reports the rest, rather than silently overwriting on every reconnect.
| Parameter | Type | Default | Description |
|---|---|---|---|
value | String | (required) | Name of the constraint to release. |
import io.cratis.chronicle.constraints.RemoveConstraintimport io.cratis.chronicle.events.EventType
@EventType@RemoveConstraint("UniqueWorkspaceSlug")data class WorkspaceArchived(val workspaceId: String)@Seeder
Section titled “@Seeder”Marks a class as a Chronicle event seeder. The class must implement ICanSeedEvents.
Marks a property, constructor parameter, field, or type as personally identifiable information. Chronicle encrypts annotated values at rest using a per-subject key. See PII Attribute for the full compliance model this participates in.
Applying it directly to a property works, but the declare-once pattern is to
put it on a ConceptAs<T> type instead: every event or read model property
that reuses that concept is PII automatically, with nothing to repeat at each
call site. It can also mark a composite value object type, in which case
every value the type holds is treated as PII wherever that type appears.
@Pii cannot be applied to an EventSourceId concept — Chronicle uses the
event source id to look up the encryption key for every other PII value
belonging to that source, so encrypting the id itself would make its own key
unfindable.
| Parameter | Type | Default | Description |
|---|---|---|---|
description | String | "" | Note about what the field holds. |
import io.cratis.chronicle.compliance.Piiimport io.cratis.chronicle.events.EventType
// Declared once on the concept, every event or read model property that// reuses EmailAddress is PII automatically.@Pii(description = "Customer email address")data class EmailAddress(override val value: String) : io.cratis.chronicle.concepts.ConceptAs<String>
@EventTypedata class CustomerRegistered( val customerId: String, val email: EmailAddress)@JsonSchemaType
Section titled “@JsonSchemaType”Overrides the type a class is represented as in the generated JSON schema. Apply it to a type that brings its own serializer and writes something other than its own shape on the wire — a value object collapsed into a single string, for instance. Without it the generated schema would describe the Kotlin shape, and the value would not round-trip through the kernel.
| Parameter | Type | Default | Description |
|---|---|---|---|
type | KClass<*> | (required) | Type the class is represented as. |
import io.cratis.chronicle.schemas.JsonSchemaType
// Money serializes as a single string ("42.50 USD") through its own// serializer, so the schema needs to describe a string, not an object// with amount/currency fields.@JsonSchemaType(String::class)data class Money(val amount: Double, val currency: String)Pointing the annotation at the annotated type itself throws
SelfReferencingJsonSchemaType — generating that schema would recurse
forever.
@Subject
Section titled “@Subject”Marks a property as the compliance subject - the identity a release
decrypts @Pii values against. IReadModelsService.release uses it
to pick which property carries the subject; without it, release falls back
to a property named id (case-insensitive), the convention every read
model followed before this annotation existed.
Add it whenever a read model’s subject is not its id - for example a
support ticket keyed by ticket id but holding a customer’s PII, where the
customer, not the ticket, is who the encryption key belongs to.
No parameters.
import io.cratis.chronicle.Subjectimport io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class SupportTicketSummary( val id: String = "", @Subject val customerId: String = "", val topic: String = "")@FromEvent
Section titled “@FromEvent”Applied to a read model class to declare that its fields are mapped from an event type. Part of the annotation-based projection style. It is repeatable — apply it once per event type the read model projects from.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | (required) | The source event class. |
key | String | "EventSourceId" | Correlates events to instances. |
import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class OrderShipped(val orderId: String, val carrier: String)
@ReadModel@FromEvent(OrderPlaced::class)@FromEvent(OrderShipped::class)data class OrderTracking( val orderId: String = "", val status: String = "")Marks a property on an event as the key a projection correlates that event
to a read model instance by. @FromEvent’s key parameter
takes this today as a bare property-name string; @Key is the
strongly-typed alternative for consumers that resolve the key by
reflection instead of by name.
No parameters.
import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.keys.Key
@EventTypedata class PickTicketOpened(@Key val orderId: String, val warehouse: String)@ContextKey
Section titled “@ContextKey”Marks a function as deriving its key from the event context — for example the event source id, or a correlation id — rather than from a property on the event payload.
| Parameter | Type | Default | Description |
|---|---|---|---|
property | String | (required) | EventContext property to use. |
import io.cratis.chronicle.keys.ContextKey
class PickTicketHandlers { @ContextKey(property = "EventSourceId") fun pickTicketOpened(event: PickTicketOpened) = Unit}IKeyBuilder/KeyBuilder build the same resolution fluently instead of
declaratively — see the io.cratis.chronicle.keys package.
@SetFrom
Section titled “@SetFrom”Applied to a read model property to override auto-mapping by name and declare which event field populates it. It is repeatable, so one property can be mapped differently per event type.
| Parameter | Type | Default | Description |
|---|---|---|---|
propertyPath | String | "" | Path to the source property. |
eventType | KClass<*> | Nothing::class | Event it applies to. |
propertyPath is a dot-separated path on the event, and defaults to the
annotated property’s own name. The default eventType applies the mapping
to every event in the read model’s @FromEvent list that has a matching
source property.
@SetValue
Section titled “@SetValue”Sets a read model property to a constant value when a specific event occurs, or clears it back to no value. It is repeatable, so the same property can hold a different constant per event type.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class to trigger on. |
value | String | "" | The constant’s literal text. Ignored if clear. |
clear | Boolean | false | Clears instead of setting value. |
Kotlin annotation parameters cannot be nullable, so value is always a plain
string — a numeric or boolean constant is written out as its literal text
("42", "true") rather than as a typed argument. Set clear = true to
clear the property instead, which is Kotlin’s equivalent of passing null
for value in the .NET client.
import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetValueimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class SubscriptionActivated(val placeholder: Boolean = true)
@EventTypedata class SubscriptionCanceled(val placeholder: Boolean = true)
@ReadModel@FromEvent(SubscriptionActivated::class)@FromEvent(SubscriptionCanceled::class)data class Subscription( @SetValue(SubscriptionActivated::class, value = "active") @SetValue(SubscriptionCanceled::class, value = "canceled") val status: String = "")@SetFromContext
Section titled “@SetFromContext”Maps a read model property from a named EventContext property, for one
specific event. Unlike @FromAll / @FromEvery, which
map a context property across every event the projection observes, this ties
the mapping to a single event type.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class this mapping applies to. |
contextProperty | String | "" | The context property to read from. |
contextProperty defaults to the annotated property’s own name.
import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.FromEventimport io.cratis.chronicle.projections.SetFromimport io.cratis.chronicle.projections.SetFromContextimport io.cratis.chronicle.readModels.ReadModel
@EventTypedata class OrderPlacedForAudit(val customerName: String)
@ReadModel@FromEvent(OrderPlacedForAudit::class)data class AuditedOrder( @SetFrom("customerName", OrderPlacedForAudit::class) val customerName: String = "",
@SetFromContext(OrderPlacedForAudit::class, contextProperty = "occurred") val orderedAt: String = "")Populates a read model property by joining against another event type on its event source id. Use it when the triggering event doesn’t carry the read model’s own key but instead references another entity by id.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class to join against. |
on | String | "" | Read model property to join on. |
eventPropertyName | String | "" | Property on eventType to read. |
on and eventPropertyName both default to the annotated property’s own name.
import io.cratis.chronicle.projections.Joinimport io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class OrderWithCustomerEmail( val orderId: String = "", val customerId: String = "", @Join( eventType = CustomerRegistered::class, on = "customerId", eventPropertyName = "email" ) val customerEmail: String = "")@ChildrenFrom
Section titled “@ChildrenFrom”Declares that a collection property is populated with child read model instances created or updated by a specific event type.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class that creates children. |
key | String | "EventSourceId" | Event property identifying the child. |
identifiedBy | String | "" | Child’s own identity property. |
parentKey | String | "EventSourceId" | Event property for the parent. |
identifiedBy defaults to the child type’s id/key property, falling back
to EventSourceId.
@Nested
Section titled “@Nested”Marks a single nullable property as a nested sub-object built from its own
type’s @FromEvent/@SetFrom annotations.
No parameters.
@ClearWith
Section titled “@ClearWith”Declares which event clears (nulls out) a @Nested property. Placed on the
nested type itself, alongside its @FromEvent annotation.
| Parameter | Type | Description |
|---|---|---|
eventType | KClass<*> | The event class that clears the nested object. |
@Count
Section titled “@Count”Turns a property into an occurrence counter for a specific event type —
every time eventType fires for the read model instance, the property is
bumped by one.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class to count occurrences of. |
constantKey | String | "" | See “Constant keys” below. |
@Increment / @Decrement
Section titled “@Increment / @Decrement”Bumps a numeric property up (@Increment) or down (@Decrement) by one
every time eventType fires for the read model instance.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class that bumps the property. |
constantKey | String | "" | See “Constant keys” below. |
import io.cratis.chronicle.projections.Incrementimport io.cratis.chronicle.readModels.ReadModel
@ReadModeldata class OrderShipmentStats( val orderId: String = "", @Increment(OrderShipped::class) val shipmentCount: Int = 0)Constant keys: when constantKey is set on @Count, @Increment, or
@Decrement, every occurrence of eventType updates the same read model
instance, identified by that constant value, instead of the projection’s
normal per-instance key resolution.
@AddFrom / @SubtractFrom
Section titled “@AddFrom / @SubtractFrom”Adds (@AddFrom) or subtracts (@SubtractFrom) the value of an event
property into/from a numeric property every time eventType fires.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class carrying the value. |
eventPropertyName | String | "" | Property on eventType to read. |
eventPropertyName defaults to the annotated property’s own name.
@FromAll / @FromEvery
Section titled “@FromAll / @FromEvery”Projects a property from every event type the projection observes, rather
than a single one. @FromAll and @FromEvery are equivalent aliases.
| Parameter | Type | Default | Description |
|---|---|---|---|
property | String | "" | Triggering event property to read from. |
contextProperty | String | "" | Event context property to read from. |
Both default to the annotated property’s own name. contextProperty (e.g.
the causing identity) takes precedence over property when both are set.
@NotRewindable
Section titled “@NotRewindable”Marks a projection as forward-only — it cannot be rewound and replayed from scratch. Placed on the read model class.
No parameters.
@RemovedWith
Section titled “@RemovedWith”Declares which event removes a read model instance, or — when placed on a
@ChildrenFrom property — which event removes a single child from that
collection.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class that triggers removal. |
key | String | "EventSourceId" | Event property for what to remove. |
parentKey | String | "EventSourceId" | Event property for the parent. |
parentKey only applies when removing a single child from a
@ChildrenFrom collection.
@RemovedWithJoin
Section titled “@RemovedWithJoin”Like @RemovedWith, but the removal event doesn’t directly carry the id —
it’s resolved via a join instead.
| Parameter | Type | Default | Description |
|---|---|---|---|
eventType | KClass<*> | — | The event class that triggers the removal. |
key | String | "EventSourceId" | Property used in the join lookup. |
@NoAutoMap
Section titled “@NoAutoMap”Disables AutoMap. Placed on a read model, @ChildrenFrom element, or
@Nested type, it disables AutoMap entirely for that type. Placed on a
single property, it excludes just that property from AutoMap while
siblings keep auto-mapping.
No parameters.
@Webhook
Section titled “@Webhook”Marks a class as a discoverable Chronicle webhook definition. The class
must implement IWebhookDefiner.
| Parameter | Type | Default | Description |
|---|---|---|---|
id | String | "" | Stable identifier. Defaults to class name. |
targetUrl | String | — | The URL to send events to. |
import io.cratis.chronicle.webhooks.IWebhookDefinerimport io.cratis.chronicle.webhooks.IWebhookDefinitionBuilderimport io.cratis.chronicle.webhooks.Webhook
@Webhook(targetUrl = "https://hooks.example.com/orders")class OrderPlacedWebhook : IWebhookDefiner { override fun define(builder: IWebhookDefinitionBuilder) { builder.withEventType(OrderPlaced::class) }}