Configuration
All Trax services are configured through a single entry point: AddTrax. This method accepts a lambda that receives a TraxBuilder, which provides a fluent API for adding effects, the mediator, and the scheduler.
The builder uses a step builder pattern that enforces ordering at compile time. Each configuration step returns a different type that only exposes valid next steps:
TraxBuilder-- the initial builder, exposesAddEffects()TraxBuilderWithEffects-- returned byAddEffects(), exposesAddMediator()TraxBuilderWithMediator-- returned byAddMediator(), exposesAddScheduler()
This means you cannot call AddMediator() without first calling AddEffects(), and you cannot call AddScheduler() without first calling AddMediator(). The compiler catches incorrect ordering before you run your application.
Effect-specific methods are nested inside .AddEffects(effects => ...), which receives a TraxEffectBuilder. The AddEffects lambda must return the builder from the last chained call (Func<TraxEffectBuilder, TraxEffectBuilder>).
Data provider methods (UsePostgres, UseInMemory) return TraxEffectBuilderWithData — a subclass of TraxEffectBuilder that unlocks data-dependent methods like AddDataContextLogging(). This provides compile-time safety: you cannot call AddDataContextLogging() without first configuring a data provider.
Entry Point
services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres(connectionString)
.AddDataContextLogging()
.AddJson()
.SaveTrainParameters()
.AddJunctionLogger()
.AddJunctionProgress()
)
.AddMediator(typeof(Program).Assembly)
.AddScheduler(scheduler => scheduler
.MaxActiveJobs(10)
)
);AddEffects Signature
// With configuration
public static TraxBuilderWithEffects AddEffects(
this TraxBuilder builder,
Func<TraxEffectBuilder, TraxEffectBuilder> configure
)
// Parameterless defaults (no data provider)
public static TraxBuilderWithEffects AddEffects(
this TraxBuilder builder
)The AddEffects callback is a Func — the lambda must return the builder from the last chained call. For expression-body lambdas (the common case), this happens naturally:
.AddEffects(effects => effects.UsePostgres(connectionString).AddJson())For multi-statement lambdas, explicitly return the builder:
.AddEffects(effects =>
{
effects.ServiceCollection.AddSingleton(myService);
return effects.UsePostgres(connectionString).AddJson();
})The parameterless overload registers effects with no data provider. Features that require a data provider (such as AddScheduler() or AddJunctionProgress()) will throw at build time with a helpful error message.
AddTrax Signature
public static IServiceCollection AddTrax(
this IServiceCollection services,
Action<TraxBuilder> configure
)AddTrax registers a TraxMarker singleton in the DI container. This marker is checked at runtime by AddTraxDashboard() and AddTraxGraphQL() -- if the marker is missing, they throw InvalidOperationException with a message directing you to call AddTrax() first.
Step Builder Types
| Type | Returned By | Exposes |
|---|---|---|
TraxBuilder | AddTrax() lambda | AddEffects() |
TraxBuilderWithEffects | AddEffects() | AddMediator() |
TraxBuilderWithMediator | AddMediator(), AddScheduler() | AddScheduler() |
Effect Builder Types
Inside the AddEffects() callback, data provider methods return a more specific type:
| Type | Returned By | Exposes |
|---|---|---|
TraxEffectBuilder | AddEffects() lambda | SkipMigrations(), UsePostgres(), UseInMemory(), AddJson(), SaveTrainParameters(), AddJunctionLogger(), AddJunctionProgress(), SetEffectLogLevel(), UseBroadcaster() |
TraxEffectBuilderWithData | UsePostgres(), UseInMemory() | Everything on TraxEffectBuilder plus AddDataContextLogging() |
Generic effect methods (AddJson, SaveTrainParameters, AddJunctionLogger, AddJunctionProgress, SetEffectLogLevel, UseBroadcaster) preserve the concrete builder type through chaining — if you start with TraxEffectBuilderWithData, it stays TraxEffectBuilderWithData.
Ordering Enforcement
The step builder pattern provides two levels of ordering enforcement:
Compile-Time (Step Builder)
The fluent chain AddEffects() -> AddMediator() -> AddScheduler() is enforced by the type system. Each method returns a different builder type that only exposes valid next steps. If you try to call methods out of order, the code will not compile:
// Compiles -- correct order
services.AddTrax(trax => trax
.AddEffects(effects => effects.UsePostgres(connectionString))
.AddMediator(typeof(Program).Assembly)
.AddScheduler()
);
// Compiles -- AddDataContextLogging() is available because UsePostgres() returns TraxEffectBuilderWithData
services.AddTrax(trax => trax
.AddEffects(effects => effects
.UsePostgres(connectionString)
.AddDataContextLogging()
)
.AddMediator(typeof(Program).Assembly)
);
// Does NOT compile -- AddDataContextLogging() requires TraxEffectBuilderWithData
services.AddTrax(trax => trax
.AddEffects(effects => effects
.AddJson()
.AddDataContextLogging() // Error: AddDataContextLogging is not available on TraxEffectBuilder
)
.AddMediator(typeof(Program).Assembly)
);
// Does NOT compile -- AddMediator() is not available on TraxBuilder
services.AddTrax(trax => trax
.AddMediator(typeof(Program).Assembly) // Error: TraxBuilder has no AddMediator
);
// Does NOT compile -- AddScheduler() is not available on TraxBuilderWithEffects
services.AddTrax(trax => trax
.AddEffects(effects => effects.UsePostgres(connectionString))
.AddScheduler() // Error: TraxBuilderWithEffects has no AddScheduler
);Runtime (TraxMarker)
AddTraxDashboard() and AddTraxGraphQL() are called on IServiceCollection / WebApplicationBuilder, not on the step builder chain. They perform a runtime check instead: if AddTrax() was not called first, they throw InvalidOperationException with a clear message:
InvalidOperationException: AddTrax() must be called before AddTraxDashboard().
Call services.AddTrax(...) in your service configuration before calling AddTraxDashboard().
Builder Properties
These properties can be set directly on the TraxEffectBuilder:
| Property | Type | Default | Description |
|---|---|---|---|
ServiceCollection | IServiceCollection | (from constructor) | Direct access to the DI container for manual registrations |
SerializeJunctionData | bool | false | Whether junction input/output data should be serialized globally |
LogLevel | LogLevel | LogLevel.Debug | Minimum log level for effect logging |
TrainParameterJsonSerializerOptions | JsonSerializerOptions | TraxJsonSerializationOptions.Default | System.Text.Json options for parameter serialization |
NewtonsoftJsonSerializerSettings | JsonSerializerSettings | TraxJsonSerializationOptions.NewtonsoftDefault | Newtonsoft.Json settings for legacy serialization |
Extension Methods
| Method | Description |
|---|---|
| SkipMigrations | Disables automatic database migration in UsePostgres() for Lambda/serverless environments |
| UsePostgres | Adds PostgreSQL database support for metadata persistence |
| UseInMemory | Adds in-memory database support for testing/development |
| AddDataContextLogging | Enables logging for database operations |
| AddJson | Adds JSON change detection for tracking model mutations |
| SaveTrainParameters | Serializes train input/output to JSON for persistence (optionally configurable) |
| AddJunctionLogger | Adds per-junction execution logging |
| AddJunctionProgress | Adds junction progress tracking and cross-server cancellation checking |
| AddMediator | Registers the TrainBus and discovers trains via assembly scanning. Accepts params Assembly[] shorthand or Func<TraxMediatorBuilder, TraxMediatorBuilder> for full control (custom lifetime, multiple assemblies). Called on TraxBuilderWithEffects, returns TraxBuilderWithMediator |
| AddEffect / AddJunctionEffect | Registers custom effect provider factories |
| AddLifecycleHook | Registers lifecycle hooks that fire on train state transitions |
| SetEffectLogLevel | Sets the minimum log level for effect logging |