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:

  1. TraxBuilder -- the initial builder, exposes AddEffects()
  2. TraxBuilderWithEffects -- returned by AddEffects(), exposes AddMediator()
  3. TraxBuilderWithMediator -- returned by AddMediator(), exposes AddScheduler()

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

TypeReturned ByExposes
TraxBuilderAddTrax() lambdaAddEffects()
TraxBuilderWithEffectsAddEffects()AddMediator()
TraxBuilderWithMediatorAddMediator(), AddScheduler()AddScheduler()

Effect Builder Types

Inside the AddEffects() callback, data provider methods return a more specific type:

TypeReturned ByExposes
TraxEffectBuilderAddEffects() lambdaSkipMigrations(), UsePostgres(), UseInMemory(), AddJson(), SaveTrainParameters(), AddJunctionLogger(), AddJunctionProgress(), SetEffectLogLevel(), UseBroadcaster()
TraxEffectBuilderWithDataUsePostgres(), 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:

PropertyTypeDefaultDescription
ServiceCollectionIServiceCollection(from constructor)Direct access to the DI container for manual registrations
SerializeJunctionDataboolfalseWhether junction input/output data should be serialized globally
LogLevelLogLevelLogLevel.DebugMinimum log level for effect logging
TrainParameterJsonSerializerOptionsJsonSerializerOptionsTraxJsonSerializationOptions.DefaultSystem.Text.Json options for parameter serialization
NewtonsoftJsonSerializerSettingsJsonSerializerSettingsTraxJsonSerializationOptions.NewtonsoftDefaultNewtonsoft.Json settings for legacy serialization

Extension Methods

MethodDescription
SkipMigrationsDisables automatic database migration in UsePostgres() for Lambda/serverless environments
UsePostgresAdds PostgreSQL database support for metadata persistence
UseInMemoryAdds in-memory database support for testing/development
AddDataContextLoggingEnables logging for database operations
AddJsonAdds JSON change detection for tracking model mutations
SaveTrainParametersSerializes train input/output to JSON for persistence (optionally configurable)
AddJunctionLoggerAdds per-junction execution logging
AddJunctionProgressAdds junction progress tracking and cross-server cancellation checking
AddMediatorRegisters 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 / AddJunctionEffectRegisters custom effect provider factories
AddLifecycleHookRegisters lifecycle hooks that fire on train state transitions
SetEffectLogLevelSets the minimum log level for effect logging