Skip to content

feat: async chain end to end#17

Merged
Theauxm merged 1 commit intomainfrom
feat/async-chain
May 4, 2026
Merged

feat: async chain end to end#17
Theauxm merged 1 commit intomainfrom
feat/async-chain

Conversation

@Theauxm
Copy link
Copy Markdown
Member

@Theauxm Theauxm commented May 4, 2026

Summary

Make Junctions() return Task<Either<Exception, TReturn>> and the fluent Monad chain (Chain, Resolve, Extract, ShortCircuit, AddServices) compose asynchronously end to end. Method names are unchanged. The awaitable struct MonadTask<TInput, TReturn> wraps Task<Monad<,>> so .Chain<A>().Chain<B>().Resolve() keeps composing without forcing all generic type arguments at every link.

Eliminates the deadlock class that bit Blazor Server / WPF consumers under single-threaded sync contexts. The previous suppression hack in Monad.Chain was a band-aid that moved the deadlock around rather than fixing it; with the chain truly async, the bug class is structurally impossible.

What's deleted

  • The implicit TReturn(Monad<,>) operator (C# forbids implicit Task<T> conversion)
  • Internal Chain<TJunction, TIn, TOut>(... out Either<,>) overloads (out is illegal in async)
  • The SynchronizationContext suppression block in Monad.Chain
  • TrainSyncContextTests.cs — the deadlock class is structurally impossible now

Internal renames

ChainChainJunction, ShortCircuitChainShortCircuitJunction to free the public name space (so the public surface can offer Chain<TJ, TIn, TOut>(TJ junction) returning MonadTask<,> without colliding with the reflection-invoked async helper). ReflectionHelpers updated to match.

Why MonadTask exists

C# does not perform partial generic inference between explicit type arguments and the receiver type. A plain extension method on Task<Monad<,>> would force callers to write .Chain<JunctionA, MyInput, MyOutput>() at every continuation. Wrapping the Task in a struct that already knows TInput/TReturn at the type level lets callers stick with .Chain<JunctionA>().

Surprise: TrainChainAnalyzer needed no changes

The fluent member-access syntax tree the analyzer walks is unchanged — only the runtime types flowing through it differ. All 24 analyzer tests pass unmodified.

Test plan

  • All four test projects pass (162 unit + 16 sanity + 16 integration + 24 analyzer = 218)
  • dotnet build zero warnings
  • dotnet csharpier check . passes
  • Downstream repos build clean against the new package (covered by paired PRs)

Migration

See Trax.Docs migration guide PR for the consumer-facing migration walkthrough. Versioned as a minor — consumers pinned via Version="1.*" will pull this and need a one-line edit per train override before their projects compile.

@traxsharp
Copy link
Copy Markdown

traxsharp Bot commented May 4, 2026

This PR is included in version 1.5.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant