Timetable management for Trax trains — recurring schedules, automatic retries, dead-letter handling, and dependent departures.
Trax is a layered framework split across several repos. You can stop at whatever layer solves your problem. You are here: Trax.Scheduler.
| Repo | Adds |
|---|---|
| Trax.Core | Pipelines, junctions, railway error propagation |
| Trax.Effect | Execution logging, DI, pluggable storage |
| Trax.Mediator | Decoupled dispatch via TrainBus |
| Trax.Scheduler | Cron schedules, retries, dead-letter queues |
| Trax.Api | GraphQL API for remote access |
| Trax.Dashboard | Blazor monitoring UI |
| Trax.Cli | trax-cli project scaffolding tool |
| Trax.Samples | Sample apps and a dotnet new template |
Full documentation: traxsharp.net/docs.
If you have trains that need to run on a timetable — ETL pipelines, data syncs, nightly reports, periodic cleanup — Trax.Scheduler handles the dispatch. You write a manifest for each train (what cargo it carries, when it departs, how many times to retry if it derails), and the scheduler takes care of the rest.
Every scheduled run is a normal train journey, so you get the same journey logging, station services, and control room visibility as any other train.
dotnet add package Trax.SchedulerYou'll also need a storage depot for persistent scheduling:
dotnet add package Trax.Effect.Data.PostgresAdd the scheduler inside your AddTrax configuration:
builder.Services.AddTrax(trax =>
trax.AddEffects(effects =>
effects.UsePostgres(connectionString).SaveTrainParameters().AddStepProgress()
)
.AddMediator(typeof(Program).Assembly)
.AddScheduler(scheduler =>
scheduler.Schedule<IGenerateReportTrain>(
"nightly-report",
new GenerateReportInput { Format = "pdf" },
Cron.Daily(hour: 3)
)
)
);A manifest describes a scheduled train: which service to run, what cargo it carries, and when it departs. Just like a shipping manifest lists what's on board and where it's going.
scheduler.Schedule<IHealthCheckTrain>(
"health-check",
new HealthCheckInput(),
Every.Minutes(5)
);scheduler.Schedule<ISyncCustomersTrain>(
"sync-customers",
new SyncCustomersInput { Source = "crm" },
Cron.Hourly(minute: 0)
);Available cron helpers: Cron.Minutely(), Cron.Hourly(), Cron.Daily(), Cron.Weekly(), Cron.Monthly(), and Cron.Expression("...") for arbitrary cron strings.
scheduler.Schedule<IImportDataTrain>(
"import-data",
new ImportDataInput(),
Every.Hours(1),
options: o => o.MaxRetries(5)
);A train that derails gets re-dispatched up to MaxRetries times. If it keeps failing, the manifest moves to the dead-letter queue — the lost shipment office where undeliverable work sits until someone investigates.
Dispatch a fleet of the same train type with different cargo:
scheduler.ScheduleMany<IExtractTrain>(
"extract",
Enumerable.Range(0, 10).Select(i =>
new ManifestItem($"{i}", new ExtractInput { TableIndex = i })),
Every.Minutes(5)
);This creates 10 manifests (extract-0 through extract-9), each departing on the same interval with different cargo.
Schedule trains so that one departs only after another arrives:
scheduler
.Schedule<IExtractTrain>(
"extract",
new ExtractInput(),
Every.Hours(1)
)
.Include<ITransformTrain>(
"transform",
new TransformInput()
);transform departs automatically when extract arrives successfully. You can chain further with .ThenInclude<T>(), or fan out with .IncludeMany<T>() and .ThenIncludeMany<T>() for fleet-scale dependent scheduling.
Sometimes a dependent train should only depart conditionally. Mark it as dormant — it sits in the yard, ready to go, waiting for a signal:
// In scheduler config
scheduler
.Schedule<IExtractTrain>("extract", input, Every.Hours(1))
.IncludeMany<IQualityCheckTrain>(
"quality",
items,
options: o => o.Dormant()
);
// In a step, when you decide it's needed — signal the departure
public class CheckDataJunction(IDormantDependentContext dormants) : Junction<ExtractInput, Unit>
{
public override async Task<Unit> Run(ExtractInput input)
{
if (anomaliesDetected)
{
await dormants.ActivateAsync<IQualityCheckTrain, QualityCheckInput>(
"quality-0",
new QualityCheckInput { /* ... */ }
);
}
return Unit.Default;
}
}The scheduler runs as three internal trains — all visible in the control room with full journey logging:
- ManifestManager — the yard master. Polls on an interval, checks which manifests are due, and queues departures.
- JobDispatcher — the dispatcher. Reads the departure queue, respects per-line capacity limits, and assigns trains to the track.
- TaskServerExecutor — the engineer. Picks up an assigned train and drives it through its route.
Long-running timetables accumulate journey records. Configure automatic archival per train type:
scheduler.AddMetadataCleanup(cleanup =>
{
cleanup.AddTrainType<IHealthCheckTrain>();
cleanup.AddTrainType<ISyncCustomersTrain>();
});When you need a programmatic interface for external consumers (queuing jobs, running trains on demand, querying state over HTTP), move up to Trax.Api.
MIT
Trax is an open-source .NET framework provided by TraxSharp. This project is an independent community effort and is not affiliated with, sponsored by, or endorsed by the Utah Transit Authority, Trax Retail, or any other entity using the "Trax" name in other industries.