Structural Dependencies
ChronicleOptions is designed to hold runtime configuration that can be bound from appsettings.json or environment variables — connection strings, timeouts, TLS settings, naming policies, and so on.
Services and providers that must be resolved at registration time (before the DI container is built) are called structural dependencies. These are not configuration values and cannot be meaningfully bound from appsettings.json. They are passed directly as constructor arguments to ChronicleClient or set on IChronicleBuilder when using the DI-hosted setup (IHostApplicationBuilder, WebApplicationBuilder).
Structural dependencies
Section titled “Structural dependencies”| Dependency | Purpose | Default |
|---|---|---|
IClientArtifactsProvider | Discovers event types, projections, reactors, reducers, and other artifacts at startup | DefaultClientArtifactsProvider (auto-discovers from loaded assemblies) |
IIdentityProvider | Supplies the current user’s identity for event metadata | BaseIdentityProvider (empty identity) |
ICorrelationIdAccessor | Provides the current correlation ID for event metadata | CorrelationIdAccessor (generates a new ID per call) |
IEventStoreNamespaceResolver | Resolves the event store namespace for each operation | DefaultEventStoreNamespaceResolver (always returns the default namespace) |
ILoggerFactory | Creates loggers for the Chronicle client internals | LoggerFactory (no-op) |
Passing to ChronicleClient directly
Section titled “Passing to ChronicleClient directly”For console applications or other non-DI scenarios, pass structural dependencies as named constructor parameters:
using Microsoft.Extensions.Logging;
public static class StructuralDependenciesDirectClient{ public static ChronicleClient Create(ChronicleOptions options, IClientArtifactsProvider myProvider) => new( options, artifactsProvider: myProvider, loggerFactory: LoggerFactory.Create(b => b.AddConsole()));}import io.cratis.chronicle.ChronicleClientimport io.cratis.chronicle.ChronicleOptionsimport io.cratis.chronicle.artifacts.IArtifactActivatorimport io.cratis.chronicle.artifacts.IClientArtifactsimport io.cratis.chronicle.connection.ChronicleConnectionString
/** * Unlike the .NET client, structural dependencies are not separate constructor parameters on * [ChronicleClient] — they are carried on [ChronicleOptions] itself, which is where every named * dependency below actually lives. */fun createClient(artifacts: IClientArtifacts, artifactActivator: IArtifactActivator): ChronicleClient { val options = ChronicleOptions( connectionString = ChronicleConnectionString.DEVELOPMENT, artifacts = artifacts, artifactActivator = artifactActivator ) return ChronicleClient(options)}import io.cratis.chronicle.ChronicleOptions;import io.cratis.chronicle.artifacts.IArtifactActivator;import io.cratis.chronicle.artifacts.IClientArtifacts;import io.cratis.chronicle.connection.ChronicleConnectionString;import io.cratis.chronicle.java.BlockingChronicleClient;
// Unlike the .NET client, structural dependencies are not separate constructor parameters on the// client — they are carried on ChronicleOptions itself, which is where every named dependency// below actually lives.class StructuralDependenciesDirectClient { BlockingChronicleClient create(IClientArtifacts artifacts, IArtifactActivator artifactActivator) { ChronicleOptions options = new ChronicleOptions( ChronicleConnectionString.Companion.getDEVELOPMENT(), "Unknown", io.cratis.chronicle.sinks.WellKnownSinkTypes.MONGODB, true, artifacts, artifactActivator); return BlockingChronicleClient.connect(options); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.All parameters are optional. Any parameter you omit uses the default shown in the table above.
Configuring with IChronicleBuilder
Section titled “Configuring with IChronicleBuilder”In DI-hosted applications (ASP.NET Core, worker services, or any IHostApplicationBuilder-based host), use the configure callback on AddCratisChronicle to set structural dependencies via the IChronicleBuilder fluent API. This callback runs at registration time, before the DI container is built.
using Cratis.Chronicle.Identities;using Microsoft.Extensions.Hosting;
public static class StructuralDependenciesChronicleBuilderRegistration{ public static void Configure(string[] args, IClientArtifactsProvider myCustomProvider, IIdentityProvider myIdentityProvider) { var builder = Host.CreateApplicationBuilder(args);
builder.AddCratisChronicle( configureOptions: options => // runtime config — bindable from appsettings.json { options.EventStore = "my-store"; options.ConnectionString = "chronicle://server:35000"; }, configure: b => b // structural dependencies .WithArtifactsProvider(myCustomProvider) .WithIdentityProvider(myIdentityProvider)); }}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.TypeScript does not support this workflow yet.The same pattern works with WebApplicationBuilder in ASP.NET Core:
using Cratis.Chronicle.Identities;using Microsoft.AspNetCore.Builder;
public static class StructuralDependenciesAspNetCoreBuilderRegistration{ public static void Configure(string[] args, IIdentityProvider myIdentityProvider) { // ASP.NET Core — WebApplicationBuilder var builder = WebApplication.CreateBuilder(args); builder.AddCratisChronicle( configureOptions: options => options.EventStore = "my-store", configure: b => b .WithIdentityProvider(myIdentityProvider)); }}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.TypeScript does not support this workflow yet.The two callbacks are intentionally separate:
configureOptionsfeeds the options pipeline and can be overridden byappsettings.jsonorIConfiguration.configuresets structural dependencies at registration time and is not overridable by configuration.
IChronicleBuilder fluent methods
Section titled “IChronicleBuilder fluent methods”| Method | Sets |
|---|---|
WithArtifactsProvider(IClientArtifactsProvider) | Custom artifact discovery |
WithIdentityProvider(IIdentityProvider) | Custom identity resolution |
WithCorrelationIdAccessor(ICorrelationIdAccessor) | Custom correlation ID accessor |
WithNamespaceResolver(IEventStoreNamespaceResolver) | Custom namespace resolution |
Custom artifact discovery
Section titled “Custom artifact discovery”Implement IClientArtifactsProvider when you need to control exactly which types Chronicle discovers. This is useful in modular applications or when using assembly scanning that differs from the default:
using Cratis.Chronicle.Events;using Cratis.Chronicle.Projections;
[EventType]public record StructuralDepsBookBorrowed(string BookId);
[EventType]public record StructuralDepsBookReturned(string BookId);
public record StructuralDepsBorrowedBook(string BookId);
public class StructuralDepsBorrowedBooksProjection : IProjectionFor<StructuralDepsBorrowedBook>{ public void Define(IProjectionBuilderFor<StructuralDepsBorrowedBook> builder) => builder .From<StructuralDepsBookBorrowed>(_ => _.Set(m => m.BookId).To(e => e.BookId));}
public class StructuralDepsMyArtifactsProvider : IClientArtifactsProvider{ public IEnumerable<Type> EventTypes => [typeof(StructuralDepsBookBorrowed), typeof(StructuralDepsBookReturned)]; public IEnumerable<Type> Projections => [typeof(StructuralDepsBorrowedBooksProjection)]; public IEnumerable<Type> ModelBoundProjections => []; public IEnumerable<Type> Reactors => []; public IEnumerable<Type> ReadModelReactors => []; public IEnumerable<Type> Reducers => []; public IEnumerable<Type> ReactorMiddlewares => []; public IEnumerable<Type> ComplianceForTypesProviders => []; public IEnumerable<Type> ComplianceForPropertiesProviders => []; public IEnumerable<Type> AdditionalEventInformationProviders => []; public IEnumerable<Type> ConstraintTypes => []; public IEnumerable<Type> UniqueConstraints => []; public IEnumerable<Type> UniqueEventTypeConstraints => []; public IEnumerable<Type> RemoveConstraintEventTypes => []; public IEnumerable<Type> EventTypeMigrators => []; public IEnumerable<Type> EventSeeders => [];}import io.cratis.chronicle.artifacts.IClientArtifactsimport io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.projections.IProjectionForimport io.cratis.chronicle.projections.IProjectionBuilderForimport io.cratis.chronicle.readModels.ReadModelimport kotlin.reflect.KClass
@EventTypedata class StructuralDepsBookBorrowed(val bookId: String = "")
@EventTypedata class StructuralDepsBookReturned(val bookId: String = "")
@ReadModeldata class StructuralDepsBorrowedBook(val bookId: String = "")
class StructuralDepsBorrowedBooksProjection : IProjectionFor<StructuralDepsBorrowedBook> { override fun define(builder: IProjectionBuilderFor<StructuralDepsBorrowedBook>) { builder.from(StructuralDepsBookBorrowed::class) { it.set(StructuralDepsBorrowedBook::bookId).to { e -> e.bookId } } }}
class StructuralDepsMyArtifacts : IClientArtifacts { override val eventTypes: List<KClass<*>> = listOf(StructuralDepsBookBorrowed::class, StructuralDepsBookReturned::class) override val eventTypeMigrations: List<KClass<*>> = emptyList() override val readModels: List<KClass<*>> = listOf(StructuralDepsBorrowedBook::class) override val projections: List<KClass<*>> = listOf(StructuralDepsBorrowedBooksProjection::class) override val modelBoundProjections: List<KClass<*>> = emptyList() override val reactors: List<KClass<*>> = emptyList() override val reducers: List<KClass<*>> = emptyList() override val constraints: List<KClass<*>> = emptyList() override val eventSeeders: List<KClass<*>> = emptyList() override val webhooks: List<KClass<*>> = emptyList() override val captures: List<KClass<*>> = emptyList() override val reactorMiddlewares: List<KClass<*>> = emptyList() override val reactorArgumentResolvers: List<KClass<*>> = emptyList()}import io.cratis.chronicle.artifacts.IClientArtifacts;import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.projections.IProjectionBuilderFor;import io.cratis.chronicle.projections.IProjectionFor;import io.cratis.chronicle.readModels.ReadModel;import io.cratis.chronicle.java.ProjectionBuilderJavaBridge;
import java.util.Collections;import java.util.List;
import kotlin.reflect.KClass;import kotlin.jvm.JvmClassMappingKt;
@EventTyperecord StructuralDepsBookBorrowed(String bookId) {}
@EventTyperecord StructuralDepsBookReturned(String bookId) {}
@ReadModelclass StructuralDepsBorrowedBook { public String bookId = "";}
class StructuralDepsBorrowedBooksProjection implements IProjectionFor<StructuralDepsBorrowedBook> { @Override public void define(IProjectionBuilderFor<StructuralDepsBorrowedBook> builder) { // AutoMap matches StructuralDepsBookBorrowed.bookId to StructuralDepsBorrowedBook.bookId by name. ProjectionBuilderJavaBridge.from(builder, StructuralDepsBookBorrowed.class); }}
class StructuralDepsMyArtifacts implements IClientArtifacts { private static KClass<?> kotlin(Class<?> type) { return JvmClassMappingKt.getKotlinClass(type); }
@Override public List<KClass<?>> getEventTypes() { return List.of(kotlin(StructuralDepsBookBorrowed.class), kotlin(StructuralDepsBookReturned.class)); }
@Override public List<KClass<?>> getEventTypeMigrations() { return Collections.emptyList(); }
@Override public List<KClass<?>> getReadModels() { return List.of(kotlin(StructuralDepsBorrowedBook.class)); }
@Override public List<KClass<?>> getProjections() { return List.of(kotlin(StructuralDepsBorrowedBooksProjection.class)); }
@Override public List<KClass<?>> getModelBoundProjections() { return Collections.emptyList(); }
@Override public List<KClass<?>> getReactors() { return Collections.emptyList(); }
@Override public List<KClass<?>> getReducers() { return Collections.emptyList(); }
@Override public List<KClass<?>> getConstraints() { return Collections.emptyList(); }
@Override public List<KClass<?>> getEventSeeders() { return Collections.emptyList(); }
@Override public List<KClass<?>> getWebhooks() { return Collections.emptyList(); }
@Override public List<KClass<?>> getCaptures() { return Collections.emptyList(); }
@Override public List<KClass<?>> getReactorMiddlewares() { return Collections.emptyList(); }
@Override public List<KClass<?>> getReactorArgumentResolvers() { return Collections.emptyList(); }}Elixir does not support this workflow yet.import { Constructor } from '@cratis/fundamentals';import { eventType, IClientArtifactsProvider, IProjectionBuilderFor, IProjectionFor, projection } from '@cratis/chronicle';
@eventType()class StructuralDepsBookBorrowed { constructor(readonly bookId: string) {}}
@eventType()class StructuralDepsBookReturned { constructor(readonly bookId: string) {}}
class StructuralDepsBorrowedBook { bookId = '';}
@projection()class StructuralDepsBorrowedBooksProjection implements IProjectionFor<StructuralDepsBorrowedBook> { define(builder: IProjectionBuilderFor<StructuralDepsBorrowedBook>): void { builder.from(StructuralDepsBookBorrowed, _ => _ .set(m => m.bookId).to(e => e.bookId)); }}
class StructuralDepsMyArtifactsProvider implements IClientArtifactsProvider { readonly eventTypes: Constructor[] = [StructuralDepsBookBorrowed, StructuralDepsBookReturned]; readonly readModels: Constructor[] = []; readonly reactors: Constructor[] = []; readonly reducers: Constructor[] = []; readonly seeders: Constructor[] = []; readonly constraints: Constructor[] = []; readonly projections: Constructor[] = [StructuralDepsBorrowedBooksProjection]; readonly webhooks: Constructor[] = []; readonly eventTypeMigrations: Constructor[] = [];}using Microsoft.Extensions.Hosting;
public static class StructuralDepsCustomArtifactsProviderUsageRegistration{ public static void Configure(string[] args) { var builder = Host.CreateApplicationBuilder(args);
builder.AddCratisChronicle( configureOptions: options => options.EventStore = "my-store", configure: b => b.WithArtifactsProvider(new StructuralDepsMyArtifactsProvider())); }}import io.cratis.chronicle.ChronicleOptionsimport io.cratis.chronicle.connection.ChronicleConnectionString
fun optionsWithCustomArtifacts(): ChronicleOptions = ChronicleOptions( connectionString = ChronicleConnectionString.DEVELOPMENT, artifacts = StructuralDepsMyArtifacts())import io.cratis.chronicle.ChronicleOptions;import io.cratis.chronicle.connection.ChronicleConnectionString;
class StructuralDepsCustomArtifactsProviderUsage { ChronicleOptions create() { return new ChronicleOptions( ChronicleConnectionString.Companion.getDEVELOPMENT(), "Unknown", io.cratis.chronicle.sinks.WellKnownSinkTypes.MONGODB, true, new StructuralDepsMyArtifacts()); }}Elixir does not support this workflow yet.import { ChronicleOptions } from '@cratis/chronicle';
function createStructuralDependenciesCustomArtifactsProviderOptions(): ChronicleOptions { return ChronicleOptions.fromConnectionString('chronicle://localhost:35000', { clientArtifactsProvider: new StructuralDepsMyArtifactsProvider() });}DefaultClientArtifactsProvider
Section titled “DefaultClientArtifactsProvider”DefaultClientArtifactsProvider scans loaded assemblies for artifacts at first access (lazy initialization). You can construct it with a custom assembly provider:
using Cratis.Types;
public static class StructuralDepsDefaultArtifactsProvider{ public static DefaultClientArtifactsProvider Create() { var assembliesProvider = new CompositeAssemblyProvider( ProjectReferencedAssemblies.Instance, PackageReferencedAssemblies.Instance);
return new DefaultClientArtifactsProvider(assembliesProvider); }}import io.cratis.chronicle.artifacts.ClientArtifactsimport io.cratis.chronicle.artifacts.IClientArtifacts
// Scans only the given packages and everything beneath them, instead of the whole classpath.fun scopedArtifacts(): IClientArtifacts = ClientArtifacts("com.acme.ordering", "com.acme.shipping")
// The classpath-wide instance used when no artifacts are configured, shared across every event// store in the process so the classpath is scanned at most once.fun defaultArtifacts(): IClientArtifacts = ClientArtifacts.defaultimport io.cratis.chronicle.artifacts.ClientArtifacts;import io.cratis.chronicle.artifacts.IClientArtifacts;
class StructuralDepsDefaultArtifactsProvider { // Scans only the given packages and everything beneath them, instead of the whole classpath. ClientArtifacts create() { return new ClientArtifacts("com.acme.ordering", "com.acme.shipping"); }
// The classpath-wide instance used when no artifacts are configured, shared across every // event store in the process so the classpath is scanned at most once. IClientArtifacts getDefault() { return ClientArtifacts.Companion.getDefault(); }}Elixir does not support this workflow yet.import { DefaultClientArtifactsProvider, TypeDiscoverer } from '@cratis/chronicle';
// TypeScript discovers artifacts by scanning files matching glob patterns rather than// scanning loaded assemblies - TypeDiscoverer.default is backed by ChronicleOptions'// discoveryPatterns.function createStructuralDependenciesDefaultArtifactsProvider(): DefaultClientArtifactsProvider { return new DefaultClientArtifactsProvider(TypeDiscoverer.default);}The static DefaultClientArtifactsProvider.Default instance is used when no provider is supplied.
Note:
DefaultClientArtifactsProviderinitializes lazily on first property access; no explicit initialization is required.