Skip to content

Appending Events

Appending events is documented in the shared Chronicle docs with synchronized examples for C#, Kotlin, Java, Elixir, and TypeScript.

Use the Kotlin get started page for JVM setup before running the shared examples.

Kotlin client: IEventSequence and IEventLog

Section titled “Kotlin client: IEventSequence and IEventLog”

Beyond append/appendMany, the Kotlin client’s store.eventLog (and any sequence from store.getEventSequence) exposes a richer surface for reading, redacting, and observing events:

MemberUse it for
appendMany(events)One atomic batch spanning several event sources.
getTailSequenceNumberCurrent end of the sequence or one source.
getForEventSourceIdAndEventTypesEvents for one source, by type.
getFromSequenceNumberEvents forward from a position (a bookmark).
getNextSequenceNumberSequence number the next append will get.
completeStreamCloses a stream so it can’t be appended to again.
redact/redactForEventSourceErases event content — see below.
appendOperationsHot Flow of appends made through this instance.

See the EventStore API reference for the full IEventSequence interface and ConcurrencyScope (optimistic concurrency via AppendOptions.concurrencyScope).

appendMany(eventSourceId, events) shapes the whole batch around one event source. When a single unit of work touches several — moving money between two accounts, say — pass EventForEventSourceId records instead. Each carries its own event source id and its own shaping, and the batch still commits as one atomic append:

import io.cratis.chronicle.events.EventType
@EventType
data class OrderLineAdded(val sku: String, val quantity: Int)
@EventType
data class StockReserved(val sku: String, val quantity: Int)
import io.cratis.chronicle.eventSequences.EventForEventSourceId
store.eventLog.appendMany(
listOf(
EventForEventSourceId("order-1", OrderLineAdded("sku-9", 2)),
EventForEventSourceId("sku-9", StockReserved("sku-9", 2))
)
)

Pass concurrencyScopes to check specific event sources optimistically — it is keyed by event source id, and any source left out is appended unchecked.

Sometimes the events for one unit of work are not all decided in the same place. forEventSourceId starts a composed operation you can build up and inspect before anything is sent, then commit with a single perform():

import io.cratis.chronicle.eventSequences.EventSequenceNumber
import io.cratis.chronicle.eventSequences.operations.forEventSourceId
val operations = store.eventLog.forEventSourceId("order-1") {
withConcurrencyScope {
withSequenceNumber(EventSequenceNumber(4)).withEventSourceId()
}
append(OrderLineAdded("sku-9", 2))
}
operations.forEventSourceId("sku-9") {
append(StockReserved("sku-9", 2), tags = listOf("inventory"))
}
// Nothing has reached the kernel yet - this is exactly what perform() will send.
println(operations.getEventsToAppend())
val results = operations.perform()

Concurrency lives on the event source rather than on an individual event, because that is where the kernel checks it. A source that never asks for a scope is appended unchecked, and a scope already set is never cleared by a later call that expresses no expectation.

redact and redactForEventSource permanently rewrite event content — a destructive, irreversible operation, not a soft delete or a field mask. Once either call returns, the original content is gone from the event store for good. Use them only for a confirmed compliance/erasure request:

import io.cratis.chronicle.eventSequences.EventSequenceNumber
import io.cratis.chronicle.eventSequences.RedactionReason
val reason = RedactionReason("GDPR erasure request")
store.eventLog.redact(EventSequenceNumber(42), reason)
store.eventLog.redactForEventSource("customer-1", reason)