Seeding with C Sharp
This page shows how to seed events using the Chronicle .NET client. Seeding is sent to the Chronicle Server when the event store connects, and the server applies it once per namespace.
Define events
Section titled “Define events”Use record types for event definitions in examples:
using Cratis.Chronicle.Events;
[EventType]public record EvtSeedingUserRegistered(string Email, string DisplayName);
[EventType]public record EvtSeedingEmailVerified(string Email);
[EventType]public record EvtSeedingProfileUpdated(string DisplayName);
[EventType]public record EvtSeedingOrderPlaced(string UserId, decimal Amount);import io.cratis.chronicle.events.EventType
@EventTypedata class EvtSeedingUserRegistered(val email: String, val displayName: String)
@EventTypedata class EvtSeedingEmailVerified(val email: String)
@EventTypedata class EvtSeedingProfileUpdated(val displayName: String)
@EventTypedata class EvtSeedingOrderPlaced(val userId: String, val amount: Double)import io.cratis.chronicle.events.EventType;
@EventTyperecord EvtSeedingUserRegistered(String email, String displayName) {}
@EventTyperecord EvtSeedingEmailVerified(String email) {}
@EventTyperecord EvtSeedingProfileUpdated(String displayName) {}
@EventTyperecord EvtSeedingOrderPlaced(String userId, double amount) {}defmodule MyApp.Events.EvtSeedingUserRegistered do use Chronicle.Events.EventType, id: "evt-seeding-user-registered"
defstruct [:email, :display_name]end
defmodule MyApp.Events.EvtSeedingEmailVerified do use Chronicle.Events.EventType, id: "evt-seeding-email-verified"
defstruct [:email]end
defmodule MyApp.Events.EvtSeedingProfileUpdated do use Chronicle.Events.EventType, id: "evt-seeding-profile-updated"
defstruct [:display_name]end
defmodule MyApp.Events.EvtSeedingOrderPlaced do use Chronicle.Events.EventType, id: "evt-seeding-order-placed"
defstruct [:user_id, :amount]endimport { eventType } from '@cratis/chronicle';
@eventType()class EvtSeedingUserRegistered { constructor(readonly email: string, readonly displayName: string) {}}
@eventType()class EvtSeedingEmailVerified { constructor(readonly email: string) {}}
@eventType()class EvtSeedingProfileUpdated { constructor(readonly displayName: string) {}}
@eventType()class EvtSeedingOrderPlaced { constructor(readonly userId: string, readonly amount: number) {}}Implement a seeder
Section titled “Implement a seeder”Implement ICanSeedEvents and use IEventSeedingBuilder to define events to append:
using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingUserSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder .For<EvtSeedingUserRegistered>("user-123", [ new("john@example.com", "John") ]) .ForEventSource("user-456", [ new EvtSeedingUserRegistered("jane@example.com", "Jane"), new EvtSeedingEmailVerified("jane@example.com") ]); }}import io.cratis.chronicle.seeding.ICanSeedEventsimport io.cratis.chronicle.seeding.IEventSeedingBuilderimport io.cratis.chronicle.seeding.Seeder
@Seederclass EvtSeedingUserSeeding : ICanSeedEvents { override fun seed(builder: IEventSeedingBuilder) { builder .forEventType( EvtSeedingUserRegistered::class, "user-123", listOf(EvtSeedingUserRegistered("john@example.com", "John")) ) .forEventSource( "user-456", listOf( EvtSeedingUserRegistered("jane@example.com", "Jane"), EvtSeedingEmailVerified("jane@example.com") ) ) }}import io.cratis.chronicle.seeding.ICanSeedEvents;import io.cratis.chronicle.seeding.IEventSeedingBuilder;import io.cratis.chronicle.seeding.Seeder;
import io.cratis.chronicle.java.EventSeedingBuilderJavaBridge;
import java.util.List;
@Seederclass EvtSeedingUserSeeding implements ICanSeedEvents { @Override public void seed(IEventSeedingBuilder builder) { EventSeedingBuilderJavaBridge.forEventType( builder, EvtSeedingUserRegistered.class, "user-123", List.of(new EvtSeedingUserRegistered("john@example.com", "John")));
builder.forEventSource( "user-456", List.of( new EvtSeedingUserRegistered("jane@example.com", "Jane"), new EvtSeedingEmailVerified("jane@example.com"))); }}defmodule MyApp.Events.EvtSeedingSeederUserRegistered do use Chronicle.Events.EventType, id: "evt-seeding-seeder-user-registered"
defstruct [:email, :display_name]end
defmodule MyApp.Events.EvtSeedingSeederEmailVerified do use Chronicle.Events.EventType, id: "evt-seeding-seeder-email-verified"
defstruct [:email]end
defmodule MyApp.Seeders.EvtSeedingUserSeeding do use Chronicle.Seeding.Seeder
alias MyApp.Events.{EvtSeedingSeederEmailVerified, EvtSeedingSeederUserRegistered}
@impl true def seed(builder) do builder |> Chronicle.Seeding.for(EvtSeedingSeederUserRegistered, "user-123", [ %EvtSeedingSeederUserRegistered{email: "john@example.com", display_name: "John"} ]) |> Chronicle.Seeding.for_event_source("user-456", [ %EvtSeedingSeederUserRegistered{email: "jane@example.com", display_name: "Jane"}, %EvtSeedingSeederEmailVerified{email: "jane@example.com"} ]) endendimport { ICanSeedEvents, IEventSeedingBuilder, seeder } from '@cratis/chronicle';
@seeder()class EvtSeedingUserSeeding implements ICanSeedEvents { seed(builder: IEventSeedingBuilder): void { builder .for('user-123', [new EvtSeedingUserRegistered('john@example.com', 'John')]) .forEventSource('user-456', [ new EvtSeedingUserRegistered('jane@example.com', 'Jane'), new EvtSeedingEmailVerified('jane@example.com') ]); }}Seed multiple events of the same type
Section titled “Seed multiple events of the same type”using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingMultipleSameTypeSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingUserRegistered>("user-123", [ new("john@example.com", "John"), new("jane@example.com", "Jane") ]); }}import io.cratis.chronicle.seeding.ICanSeedEventsimport io.cratis.chronicle.seeding.IEventSeedingBuilderimport io.cratis.chronicle.seeding.Seeder
@Seederclass EvtSeedingMultipleSameTypeSeeding : ICanSeedEvents { override fun seed(builder: IEventSeedingBuilder) { builder.forEventType( EvtSeedingUserRegistered::class, "user-123", listOf( EvtSeedingUserRegistered("john@example.com", "John"), EvtSeedingUserRegistered("jane@example.com", "Jane") ) ) }}import io.cratis.chronicle.seeding.ICanSeedEvents;import io.cratis.chronicle.seeding.IEventSeedingBuilder;import io.cratis.chronicle.seeding.Seeder;
import io.cratis.chronicle.java.EventSeedingBuilderJavaBridge;
import java.util.List;
@Seederclass EvtSeedingMultipleSameTypeSeeding implements ICanSeedEvents { @Override public void seed(IEventSeedingBuilder builder) { EventSeedingBuilderJavaBridge.forEventType( builder, EvtSeedingUserRegistered.class, "user-123", List.of( new EvtSeedingUserRegistered("john@example.com", "John"), new EvtSeedingUserRegistered("jane@example.com", "Jane"))); }}defmodule MyApp.Events.EvtSeedingMultiSameTypeUserRegistered do use Chronicle.Events.EventType, id: "evt-seeding-multi-same-type-user-registered"
defstruct [:email, :display_name]end
defmodule MyApp.Seeders.EvtSeedingMultipleSameTypeSeeding do use Chronicle.Seeding.Seeder
alias MyApp.Events.EvtSeedingMultiSameTypeUserRegistered
@impl true def seed(builder) do Chronicle.Seeding.for(builder, EvtSeedingMultiSameTypeUserRegistered, "user-123", [ %EvtSeedingMultiSameTypeUserRegistered{email: "john@example.com", display_name: "John"}, %EvtSeedingMultiSameTypeUserRegistered{email: "jane@example.com", display_name: "Jane"} ]) endendimport { ICanSeedEvents, IEventSeedingBuilder, seeder } from '@cratis/chronicle';
@seeder()class EvtSeedingMultipleSameTypeSeeding implements ICanSeedEvents { seed(builder: IEventSeedingBuilder): void { builder.for('user-123', [ new EvtSeedingUserRegistered('john@example.com', 'John'), new EvtSeedingUserRegistered('jane@example.com', 'Jane') ]); }}Seed mixed event types
Section titled “Seed mixed event types”using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingMixedTypesSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.ForEventSource("user-123", [ new EvtSeedingUserRegistered("john@example.com", "John"), new EvtSeedingEmailVerified("john@example.com"), new EvtSeedingProfileUpdated("John Doe") ]); }}import io.cratis.chronicle.seeding.ICanSeedEventsimport io.cratis.chronicle.seeding.IEventSeedingBuilderimport io.cratis.chronicle.seeding.Seeder
@Seederclass EvtSeedingMixedTypesSeeding : ICanSeedEvents { override fun seed(builder: IEventSeedingBuilder) { builder.forEventSource( "user-123", listOf( EvtSeedingUserRegistered("john@example.com", "John"), EvtSeedingEmailVerified("john@example.com"), EvtSeedingProfileUpdated("John Doe") ) ) }}import io.cratis.chronicle.seeding.ICanSeedEvents;import io.cratis.chronicle.seeding.IEventSeedingBuilder;import io.cratis.chronicle.seeding.Seeder;
import java.util.List;
@Seederclass EvtSeedingMixedTypesSeeding implements ICanSeedEvents { @Override public void seed(IEventSeedingBuilder builder) { builder.forEventSource( "user-123", List.of( new EvtSeedingUserRegistered("john@example.com", "John"), new EvtSeedingEmailVerified("john@example.com"), new EvtSeedingProfileUpdated("John Doe"))); }}defmodule MyApp.Events.EvtSeedingMixedTypesUserRegistered do use Chronicle.Events.EventType, id: "evt-seeding-mixed-types-user-registered"
defstruct [:email, :display_name]end
defmodule MyApp.Events.EvtSeedingMixedTypesEmailVerified do use Chronicle.Events.EventType, id: "evt-seeding-mixed-types-email-verified"
defstruct [:email]end
defmodule MyApp.Events.EvtSeedingMixedTypesProfileUpdated do use Chronicle.Events.EventType, id: "evt-seeding-mixed-types-profile-updated"
defstruct [:display_name]end
defmodule MyApp.Seeders.EvtSeedingMixedTypesSeeding do use Chronicle.Seeding.Seeder
alias MyApp.Events.{ EvtSeedingMixedTypesEmailVerified, EvtSeedingMixedTypesProfileUpdated, EvtSeedingMixedTypesUserRegistered }
@impl true def seed(builder) do Chronicle.Seeding.for_event_source(builder, "user-123", [ %EvtSeedingMixedTypesUserRegistered{email: "john@example.com", display_name: "John"}, %EvtSeedingMixedTypesEmailVerified{email: "john@example.com"}, %EvtSeedingMixedTypesProfileUpdated{display_name: "John Doe"} ]) endendimport { ICanSeedEvents, IEventSeedingBuilder, seeder } from '@cratis/chronicle';
@seeder()class EvtSeedingMixedTypesSeeding implements ICanSeedEvents { seed(builder: IEventSeedingBuilder): void { builder.forEventSource('user-123', [ new EvtSeedingUserRegistered('john@example.com', 'John'), new EvtSeedingEmailVerified('john@example.com'), new EvtSeedingProfileUpdated('John Doe') ]); }}Development-only seeding
Section titled “Development-only seeding”If seed data should only run in development, use conditional compilation or runtime configuration:
using Cratis.Chronicle.Seeding;
#if DEBUGpublic sealed class EvtSeedingDevelopmentSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingUserRegistered>("dev-user-1", [ new("dev@example.com", "Dev User") ]); }}#endifimport io.cratis.chronicle.seeding.ICanSeedEventsimport io.cratis.chronicle.seeding.IEventSeedingBuilderimport io.cratis.chronicle.seeding.Seeder
// Kotlin has no equivalent of C#'s #if DEBUG - gate seed data with a runtime check instead,// such as an environment variable or an application profile flag.@Seederclass EvtSeedingDevelopmentSeeding : ICanSeedEvents { override fun seed(builder: IEventSeedingBuilder) { if (System.getenv("APP_ENVIRONMENT") != "Development") return
builder.forEventType( EvtSeedingUserRegistered::class, "dev-user-1", listOf(EvtSeedingUserRegistered("dev@example.com", "Dev User")) ) }}import io.cratis.chronicle.seeding.ICanSeedEvents;import io.cratis.chronicle.seeding.IEventSeedingBuilder;import io.cratis.chronicle.seeding.Seeder;
import io.cratis.chronicle.java.EventSeedingBuilderJavaBridge;
import java.util.List;
// Java has no equivalent of C#'s #if DEBUG - gate seed data with a runtime check instead,// such as an environment variable or an application profile flag.@Seederclass EvtSeedingDevelopmentSeeding implements ICanSeedEvents { @Override public void seed(IEventSeedingBuilder builder) { if (!"Development".equals(System.getenv("APP_ENVIRONMENT"))) { return; }
EventSeedingBuilderJavaBridge.forEventType( builder, EvtSeedingUserRegistered.class, "dev-user-1", List.of(new EvtSeedingUserRegistered("dev@example.com", "Dev User"))); }}defmodule MyApp.Events.EvtSeedingDevUserRegistered do use Chronicle.Events.EventType, id: "evt-seeding-dev-user-registered"
defstruct [:email, :display_name]end
defmodule MyApp.Seeders.EvtSeedingDevelopmentSeeding do use Chronicle.Seeding.Seeder
alias MyApp.Events.EvtSeedingDevUserRegistered
@impl true def seed(builder) do Chronicle.Seeding.for(builder, EvtSeedingDevUserRegistered, "dev-user-1", [ %EvtSeedingDevUserRegistered{email: "dev@example.com", display_name: "Dev User"} ]) endend
defmodule MyApp.EvtSeedingApplication do use Application
# Chronicle doesn't distinguish between development and production seed # data — decide when to seed based on build configuration or runtime # settings, resolved once at compile time here via Mix.env/0. @dev_seeders (if Mix.env() == :dev do [MyApp.Seeders.EvtSeedingDevelopmentSeeding] else [] end)
@impl true def start(_type, _args) do children = [ {Chronicle.Client, connection_string: "chronicle://localhost:35000", event_store: "my-store", seeders: @dev_seeders} ]
Supervisor.start_link(children, strategy: :one_for_one) endendimport { ICanSeedEvents, IEventSeedingBuilder, seeder } from '@cratis/chronicle';
// Only include this seeder's file in your development build/discovery patterns -// TypeScript has no build-time equivalent of C#'s #if DEBUG, so keep it out of what// ChronicleOptions.discoveryPatterns picks up for production.@seeder()class EvtSeedingDevelopmentSeeding implements ICanSeedEvents { seed(builder: IEventSeedingBuilder): void { builder.for('dev-user-1', [new EvtSeedingUserRegistered('dev@example.com', 'Dev User')]); }}Chronicle does not distinguish between development and production seed data. Decide when to seed based on build configuration or runtime settings.
Organize seeders by feature
Section titled “Organize seeders by feature”For larger solutions, split seeders by domain or feature:
using Cratis.Chronicle.Seeding;
public sealed class EvtSeedingUserFeatureSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingUserRegistered>("test-user-1", [ new("test1@example.com", "Test User 1") ]); }}
public sealed class EvtSeedingOrderFeatureSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { builder.For<EvtSeedingOrderPlaced>("test-order-1", [ new("test-user-1", 100.00m) ]); }}import io.cratis.chronicle.seeding.ICanSeedEventsimport io.cratis.chronicle.seeding.IEventSeedingBuilderimport io.cratis.chronicle.seeding.Seeder
@Seederclass EvtSeedingUserFeatureSeeding : ICanSeedEvents { override fun seed(builder: IEventSeedingBuilder) { builder.forEventType( EvtSeedingUserRegistered::class, "test-user-1", listOf(EvtSeedingUserRegistered("test1@example.com", "Test User 1")) ) }}
@Seederclass EvtSeedingOrderFeatureSeeding : ICanSeedEvents { override fun seed(builder: IEventSeedingBuilder) { builder.forEventType( EvtSeedingOrderPlaced::class, "test-order-1", listOf(EvtSeedingOrderPlaced("test-user-1", 100.00)) ) }}import io.cratis.chronicle.seeding.ICanSeedEvents;import io.cratis.chronicle.seeding.IEventSeedingBuilder;import io.cratis.chronicle.seeding.Seeder;
import io.cratis.chronicle.java.EventSeedingBuilderJavaBridge;
import java.util.List;
@Seederclass EvtSeedingUserFeatureSeeding implements ICanSeedEvents { @Override public void seed(IEventSeedingBuilder builder) { EventSeedingBuilderJavaBridge.forEventType( builder, EvtSeedingUserRegistered.class, "test-user-1", List.of(new EvtSeedingUserRegistered("test1@example.com", "Test User 1"))); }}
@Seederclass EvtSeedingOrderFeatureSeeding implements ICanSeedEvents { @Override public void seed(IEventSeedingBuilder builder) { EventSeedingBuilderJavaBridge.forEventType( builder, EvtSeedingOrderPlaced.class, "test-order-1", List.of(new EvtSeedingOrderPlaced("test-user-1", 100.00))); }}defmodule MyApp.Events.EvtSeedingFeatureUserRegistered do use Chronicle.Events.EventType, id: "evt-seeding-feature-user-registered"
defstruct [:email, :display_name]end
defmodule MyApp.Events.EvtSeedingFeatureOrderPlaced do use Chronicle.Events.EventType, id: "evt-seeding-feature-order-placed"
defstruct [:user_id, :amount]end
defmodule MyApp.Seeders.EvtSeedingUserFeatureSeeding do use Chronicle.Seeding.Seeder
alias MyApp.Events.EvtSeedingFeatureUserRegistered
@impl true def seed(builder) do Chronicle.Seeding.for(builder, EvtSeedingFeatureUserRegistered, "test-user-1", [ %EvtSeedingFeatureUserRegistered{email: "test1@example.com", display_name: "Test User 1"} ]) endend
defmodule MyApp.Seeders.EvtSeedingOrderFeatureSeeding do use Chronicle.Seeding.Seeder
alias MyApp.Events.EvtSeedingFeatureOrderPlaced
@impl true def seed(builder) do Chronicle.Seeding.for(builder, EvtSeedingFeatureOrderPlaced, "test-order-1", [ %EvtSeedingFeatureOrderPlaced{user_id: "test-user-1", amount: 100.00} ]) endendimport { ICanSeedEvents, IEventSeedingBuilder, seeder } from '@cratis/chronicle';
@seeder()class EvtSeedingUserFeatureSeeding implements ICanSeedEvents { seed(builder: IEventSeedingBuilder): void { builder.for('test-user-1', [new EvtSeedingUserRegistered('test1@example.com', 'Test User 1')]); }}
@seeder()class EvtSeedingOrderFeatureSeeding implements ICanSeedEvents { seed(builder: IEventSeedingBuilder): void { builder.for('test-order-1', [new EvtSeedingOrderPlaced('test-user-1', 100.0)]); }}Namespace-scoped seed data
Section titled “Namespace-scoped seed data”By default, seed data applies to all namespaces in the event store. To target a specific namespace, use ForNamespace to get a scoped builder:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Seeding;
[EventType]public record EvtSeedingProductCreated(string Name, decimal Price);
[EventType]public record EvtSeedingOrganizationCreated(string Name);
[EventType]public record EvtSeedingBillingSetUp(string BillingEmail);
public sealed class EvtSeedingTenantSeeding : ICanSeedEvents{ public void Seed(IEventSeedingBuilder builder) { // Global seed data — applied to every namespace builder.For<EvtSeedingProductCreated>("product-1", [ new("Laptop", 1299.00m) ]);
// Namespace-scoped seed data — applied only to the "acme" namespace builder.ForNamespace("acme") .For<EvtSeedingUserRegistered>("user-1", [ new("admin@acme.com", "Acme Admin") ]);
// A second namespace with different seed data builder.ForNamespace("contoso") .For<EvtSeedingUserRegistered>("user-1", [ new("admin@contoso.com", "Contoso Admin") ]) .ForEventSource("org-1", [ new EvtSeedingOrganizationCreated("Contoso"), new EvtSeedingBillingSetUp("contoso@billing.com") ]); }}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.seeding.ICanSeedEventsimport io.cratis.chronicle.seeding.IEventSeedingBuilderimport io.cratis.chronicle.seeding.Seeder
@EventTypedata class EvtSeedingProductCreated(val name: String, val price: Double)
@EventTypedata class EvtSeedingOrganizationCreated(val name: String)
@EventTypedata class EvtSeedingBillingSetUp(val billingEmail: String)
@Seederclass EvtSeedingTenantSeeding : ICanSeedEvents { override fun seed(builder: IEventSeedingBuilder) { // Global seed data - applied to every namespace builder.forEventType( EvtSeedingProductCreated::class, "product-1", listOf(EvtSeedingProductCreated("Laptop", 1299.00)) )
// Namespace-scoped seed data - applied only to the "acme" namespace builder.forNamespace("acme") .forEventType( EvtSeedingUserRegistered::class, "user-1", listOf(EvtSeedingUserRegistered("admin@acme.com", "Acme Admin")) )
// A second namespace with different seed data builder.forNamespace("contoso") .forEventType( EvtSeedingUserRegistered::class, "user-1", listOf(EvtSeedingUserRegistered("admin@contoso.com", "Contoso Admin")) ) .forEventSource( "org-1", listOf( EvtSeedingOrganizationCreated("Contoso"), EvtSeedingBillingSetUp("contoso@billing.com") ) ) }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.seeding.ICanSeedEvents;import io.cratis.chronicle.seeding.IEventSeedingBuilder;import io.cratis.chronicle.seeding.IEventSeedingScopeBuilder;import io.cratis.chronicle.seeding.Seeder;
import io.cratis.chronicle.java.EventSeedingBuilderJavaBridge;import io.cratis.chronicle.java.EventSeedingScopeBuilderJavaBridge;
import java.util.List;
@EventTyperecord EvtSeedingProductCreated(String name, double price) {}
@EventTyperecord EvtSeedingOrganizationCreated(String name) {}
@EventTyperecord EvtSeedingBillingSetUp(String billingEmail) {}
@Seederclass EvtSeedingTenantSeeding implements ICanSeedEvents { @Override public void seed(IEventSeedingBuilder builder) { // Global seed data - applied to every namespace EventSeedingBuilderJavaBridge.forEventType( builder, EvtSeedingProductCreated.class, "product-1", List.of(new EvtSeedingProductCreated("Laptop", 1299.00)));
// Namespace-scoped seed data - applied only to the "acme" namespace IEventSeedingScopeBuilder acme = builder.forNamespace("acme"); EventSeedingScopeBuilderJavaBridge.forEventType( acme, EvtSeedingUserRegistered.class, "user-1", List.of(new EvtSeedingUserRegistered("admin@acme.com", "Acme Admin")));
// A second namespace with different seed data IEventSeedingScopeBuilder contoso = builder.forNamespace("contoso"); EventSeedingScopeBuilderJavaBridge.forEventType( contoso, EvtSeedingUserRegistered.class, "user-1", List.of(new EvtSeedingUserRegistered("admin@contoso.com", "Contoso Admin"))); contoso.forEventSource( "org-1", List.of( new EvtSeedingOrganizationCreated("Contoso"), new EvtSeedingBillingSetUp("contoso@billing.com"))); }}defmodule MyApp.Events.EvtSeedingNsProductCreated do use Chronicle.Events.EventType, id: "evt-seeding-ns-product-created"
defstruct [:name, :price]end
defmodule MyApp.Events.EvtSeedingNsUserRegistered do use Chronicle.Events.EventType, id: "evt-seeding-ns-user-registered"
defstruct [:email, :display_name]end
defmodule MyApp.Events.EvtSeedingNsOrganizationCreated do use Chronicle.Events.EventType, id: "evt-seeding-ns-organization-created"
defstruct [:name]end
defmodule MyApp.Events.EvtSeedingNsBillingSetUp do use Chronicle.Events.EventType, id: "evt-seeding-ns-billing-set-up"
defstruct [:billing_email]end
defmodule MyApp.Seeders.EvtSeedingTenantSeeding do use Chronicle.Seeding.Seeder
alias MyApp.Events.{ EvtSeedingNsBillingSetUp, EvtSeedingNsOrganizationCreated, EvtSeedingNsProductCreated, EvtSeedingNsUserRegistered }
@impl true def seed(builder) do builder # Global seed data — applied to every namespace |> Chronicle.Seeding.for(EvtSeedingNsProductCreated, "product-1", [ %EvtSeedingNsProductCreated{name: "Laptop", price: 1299.00} ]) # Namespace-scoped seed data — applied only to the "acme" namespace |> Chronicle.Seeding.for_namespace("acme", fn scoped -> Chronicle.Seeding.for(scoped, EvtSeedingNsUserRegistered, "user-1", [ %EvtSeedingNsUserRegistered{email: "admin@acme.com", display_name: "Acme Admin"} ]) end) # A second namespace with different seed data |> Chronicle.Seeding.for_namespace("contoso", fn scoped -> scoped |> Chronicle.Seeding.for(EvtSeedingNsUserRegistered, "user-1", [ %EvtSeedingNsUserRegistered{email: "admin@contoso.com", display_name: "Contoso Admin"} ]) |> Chronicle.Seeding.for_event_source("org-1", [ %EvtSeedingNsOrganizationCreated{name: "Contoso"}, %EvtSeedingNsBillingSetUp{billing_email: "contoso@billing.com"} ]) end) endendimport { eventType, ICanSeedEvents, IEventSeedingBuilder, seeder } from '@cratis/chronicle';
@eventType()class EvtSeedingProductCreated { constructor(readonly name: string, readonly price: number) {}}
@eventType()class EvtSeedingOrganizationCreated { constructor(readonly name: string) {}}
@eventType()class EvtSeedingBillingSetUp { constructor(readonly billingEmail: string) {}}
@seeder()class EvtSeedingTenantSeeding implements ICanSeedEvents { seed(builder: IEventSeedingBuilder): void { // Global seed data — applied to every namespace builder.for('product-1', [new EvtSeedingProductCreated('Laptop', 1299.0)]);
// Namespace-scoped seed data — applied only to the "acme" namespace builder.forNamespace('acme') .for('user-1', [new EvtSeedingUserRegistered('admin@acme.com', 'Acme Admin')]);
// A second namespace with different seed data builder.forNamespace('contoso') .for('user-1', [new EvtSeedingUserRegistered('admin@contoso.com', 'Contoso Admin')]) .forEventSource('org-1', [ new EvtSeedingOrganizationCreated('Contoso'), new EvtSeedingBillingSetUp('contoso@billing.com') ]); }}The scoped builder supports the same For<TEvent> and ForEventSource methods as the global builder. Each namespace receives only its own scoped events in addition to any global events.
Register corrected seed definitions without reconnecting
Section titled “Register corrected seed definitions without reconnecting”When registration fails, Chronicle retains the attempted entries in the event store’s default Seeding buffer.
That makes retrying an unchanged idempotent batch safe after a transient failure. If the definitions themselves
were wrong, create an independent empty buffer so the corrected set is not combined with the retained set:
using Cratis.Chronicle;using Cratis.Chronicle.Seeding;
public static class EvtSeedingCorrection{ public static async Task Register(IEventStore eventStore) { var correctedSeeding = eventStore.CreateEventSeeding(); correctedSeeding.For<EvtSeedingUserRegistered>("user-123", [ new("john@example.com", "John Doe") ]);
await correctedSeeding.Register(); }}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.import { IEventStore } from '@cratis/chronicle';
async function registerEvtSeedingCorrection(eventStore: IEventStore): Promise<void> { eventStore.seeding.for('user-123', [new EvtSeedingUserRegistered('john@example.com', 'John Doe')]);
await eventStore.seeding.register();}The new buffer uses the event store’s existing connection, event types, serializer, and client configuration. A
failure from its Register() call still propagates normally.
How it runs
Section titled “How it runs”- Seeders are automatically discovered at application startup.
- Seed batches are sent to the Chronicle Server when the event store connects.
- Every entry is applied once per namespace, and seeding is idempotent across restarts: entries that have already been appended are skipped on the next run.
- An entry that your seeder yields twice is two events, not one. Two events of the same type, on the same event source, carrying the same payload are two facts, and both are appended.
- Events are appended in batches of 100. A batch is validated as a whole before anything is written, so a
single entry that violates a constraint means none of its batch is appended. The server reports the
rejection at
Errorwith the violations, leaves those entries unseeded, and fails registration. The remote client surfaces that failure through its normal transport error. Retry the unchanged buffer for a transient failure; after correcting the definitions, restart the process or useCreateEventSeeding()to offer a fresh set.
Best practices
Section titled “Best practices”- Keep seed data minimal and deterministic.
- Use clear event source IDs to make debugging easier.
- Group seeders by scenario so you can remove or adjust them easily.
- Use build flags or runtime settings to prevent seeding in production.
- Use
ForNamespacewhen seed data is tenant-specific or environment-specific to avoid polluting other namespaces.