Cratis Chronicle event copying to another event source
Copying in Cratis Chronicle seeds one event source with history from another event source in the same event sequence. Use it to create a variant of an entity under a different identifier without changing the source history.
Each copy is a new event with a new sequence number. Observers process these appends, so review their effects before starting: copying history can trigger application actions as well as build state. The source and target identifiers must differ.
Copying every event
Section titled “Copying every event”Pass the source and target event source identifiers to copy all event types for the source:
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
public class CopyAccountEventsService(IEventLog eventLog){ public Task<Guid> CopyAccountEvents(EventSourceId sourceId, EventSourceId targetId) => eventLog.Copy(sourceId, targetId);}import io.cratis.chronicle.IEventStore
/** * Copies every event of one event source onto another. The copy runs as a durable job on the * kernel; the returned job id identifies it. */suspend fun copyAccountHistory(store: IEventStore, sourceId: String, targetId: String): String = store.eventLog.copyForEventSource(sourceId, targetId)import io.cratis.chronicle.EventStore;
import java.util.List;
import io.cratis.chronicle.java.EventLogJavaBridge;
class EventsCopyingByEventSource { // Copies every event of one event source onto another. The copy runs as a durable job on the // kernel; the returned job id identifies it. String copyAccountHistory(EventStore store, String sourceId, String targetId) { return EventLogJavaBridge.copyForEventSource(store.getEventLog(), sourceId, targetId, List.of()); }}defmodule MyApp.CopyByEventSourceService do alias Chronicle.EventSequences.EventLog
def copy_account(source_id, target_id) do {:ok, job_id} = EventLog.copy(source_id, target_id) job_id endendimport { Guid, IEventLog } from '@cratis/chronicle';
class CopyByEventSourceService { constructor(private readonly eventLog: IEventLog) {}
copyAccount(sourceId: string, targetId: string): Promise<Guid> { return this.eventLog.copy(sourceId, targetId); }}Copying specific event types
Section titled “Copying specific event types”Provide an event type filter when the target needs only part of the history. Events of other types remain in the source and are not copied:
using Cratis.Chronicle.Events;using Cratis.Chronicle.EventSequences;
[EventType]public record AccountOpened(string Name);
[EventType]public record AccountRenamed(string Name);
public class CopyAccountOpeningsService(IEventLog eventLog){ public Task<Guid> CopyAccountOpenings(EventSourceId sourceId, EventSourceId targetId) => eventLog.Copy(sourceId, targetId, typeof(AccountOpened), typeof(AccountRenamed));}import io.cratis.chronicle.IEventStoreimport io.cratis.chronicle.events.EventType
@EventTypedata class CopyingAccountOpened(val owner: String = "")
@EventTypedata class CopyingAddressChanged(val street: String = "", val city: String = "")
/** * Copies only the given event types for an event source, leaving every other event type behind. */suspend fun copyAccountDetails(store: IEventStore, sourceId: String, targetId: String): String = store.eventLog.copyForEventSource( sourceId, targetId, listOf(CopyingAccountOpened::class, CopyingAddressChanged::class) )import io.cratis.chronicle.EventStore;import io.cratis.chronicle.events.EventType;
import java.util.List;
import io.cratis.chronicle.java.EventLogJavaBridge;
@EventTyperecord CopyingAccountOpened(String owner) {}
@EventTyperecord CopyingAddressChanged(String street, String city) {}
class EventsCopyingByEventSourceAndTypes { // Copies only the given event types for an event source, leaving every other event type behind. String copyAccountDetails(EventStore store, String sourceId, String targetId) { return EventLogJavaBridge.copyForEventSource( store.getEventLog(), sourceId, targetId, List.of(CopyingAccountOpened.class, CopyingAddressChanged.class)); }}defmodule MyApp.Events.CopyPersonalDetailsRecorded do use Chronicle.Events.EventType, id: "copy-personal-details-recorded"
defstruct [:name, :social_security_number]end
defmodule MyApp.Events.CopyAddressChanged do use Chronicle.Events.EventType, id: "copy-address-changed"
defstruct [:street, :city]end
defmodule MyApp.CopyByEventSourceAndTypesService do alias Chronicle.EventSequences.EventLog alias MyApp.Events.{CopyAddressChanged, CopyPersonalDetailsRecorded}
def copy_personal_data(source_id, target_id) do {:ok, job_id} = EventLog.copy(source_id, target_id, [CopyPersonalDetailsRecorded, CopyAddressChanged])
job_id endendimport { eventType, Guid, IEventLog } from '@cratis/chronicle';import { field } from '@cratis/fundamentals';
@eventType()class CopyAccountOpened { @field(String) readonly name: string;
constructor(name: string = '') { this.name = name; }}
@eventType()class CopyAddressChanged { @field(String) readonly street: string; @field(String) readonly city: string;
constructor(street: string = '', city: string = '') { this.street = street; this.city = city; }}
class CopyByEventSourceAndTypesService { constructor(private readonly eventLog: IEventLog) {}
copyAccountDetails(sourceId: string, targetId: string): Promise<Guid> { return this.eventLog.copy(sourceId, targetId, CopyAccountOpened, CopyAddressChanged); }}How it works
Section titled “How it works”The call starts a durable job and returns its identifier, not a completed copy. The job reads matching source events in sequence order and appends them to the target in batches. Follow progress on the Workbench’s Jobs page or through the jobs service.
Each copy uses an idempotency key derived from the job identifier and the source event’s sequence number. Resuming that job reuses those keys to avoid duplicate appends. Starting a new copy job creates different keys; it does not deduplicate against an earlier job.
A copy keeps the event type and generation, tags, stream, and original occurred time. Its new audit context identifies the copy operation. Personal data is released from the source and protected again for the target. When the subject was the source event source, the target becomes the subject; an explicitly assigned subject stays unchanged.
Copying is not atomic across the whole history. If a target append fails, for example because of a unique constraint, the job reports the failure and earlier batches remain in the target. Inspect the job and target history before deciding how to recover. Use Getting events to read the resulting history.