Railway Oriented Programming for .NET. Build trains that carry data through a sequence of stops, with automatic derailment handling when something goes wrong.
Trax is a layered framework split across several repos. You can stop at whatever layer solves your problem. You are here: Trax.Core.
| 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.
Error handling tends to bury the actual logic:
publicasyncTask<OrderReceipt>ProcessOrder(OrderRequestrequest){varinventory=await_inventory.CheckAsync(request.Items);if(!inventory.Available)returnError("Items out of stock");varpayment=await_payments.ChargeAsync(request.PaymentMethod,request.Total);if(!payment.Success)returnError("Payment failed");varshipment=await_shipping.CreateAsync(request.Address,request.Items);if(shipment==null)returnError("Shipping setup failed");returnnewOrderReceipt(payment,shipment);}Every junction needs its own null check, error branch, and early return. The business logic (check inventory, charge payment, create shipment) gets lost in the noise.
publicclassProcessOrderTrain:Train<OrderRequest,OrderReceipt>{protectedoverrideTask<Either<Exception,OrderReceipt>>Junctions()=>Chain<CheckInventoryJunction>().Chain<ChargePaymentJunction>().Chain<CreateShipmentJunction>().Resolve();}A train picks up its cargo, visits each stop along the route (.Chain<T>), and arrives at its destination (Resolve). If CheckInventoryJunction throws, the train derails and ChargePaymentJunction and CreateShipmentJunction are never reached. The exception propagates through the chain automatically.
Main Track: Input → [Stop 1] → [Stop 2] → [Stop 3] → Output
↓
Derailed: Exception → [Skip] → [Skip] → Exception
Each junction is its own class with its own dependencies, testable in isolation.
Requires net10.0.
dotnet add package Trax.Core1. Define a junction. Each junction takes one type of cargo in and produces one type of cargo out:
publicclassValidateEmailJunction(IUserRepositoryrepo):Junction<CreateUserRequest,Unit>{publicoverrideasyncTask<Unit>Run(CreateUserRequestinput){varexisting=awaitrepo.GetByEmailAsync(input.Email);if(existingis not null)thrownewValidationException($"Email {input.Email} is already taken");returnUnit.Default;}}2. Build a route by chaining junctions into a train:
publicclassCreateUserTrain:Train<CreateUserRequest,User>{protectedoverrideTask<Either<Exception,User>>Junctions()=>Chain<ValidateEmailJunction>().Chain<CreateUserInDatabaseJunction>().Chain<SendWelcomeEmailJunction>().Resolve();}When the train is run with an input, the cargo is loaded automatically. At each stop, .Chain<T> picks up the cargo T needs from what the train is carrying, runs the junction, and loads the output back on. Resolve unloads the final delivery at the destination.
The train carries all of this in Memory, a type-keyed store that accumulates as the train moves through its route. Each stop can use anything a previous stop produced.
3. Run it:
vartrain=newCreateUserTrain();Either<Exception,User>result=awaittrain.RunEither(request);// Or throw on failure:Useruser=awaittrain.Run(request);Trax.Core ships with a Roslyn analyzer that validates your route at build time. If a stop expects cargo that no previous stop has loaded, you get a compiler error, not a runtime derailment.
| Diagnostic | Meaning |
|---|---|
| CHAIN001 | A junction expects cargo that isn't on the train at that point in the route |
| CHAIN002 | The train's final delivery type isn't on board when Resolve() is called |
Inlay hint extensions show TIn → TOut types inline for each .Chain<TJunction>() call, so you can see what cargo flows through each stop at a glance.
- VSCode: Trax.Core Chain Hints on the Marketplace
- Rider / ReSharper: Search for Trax.Core Chain Hints in JetBrains Marketplace
When you need execution logging, DI, or persistent metadata, move up to Trax.Effect.
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.