Land F# code generation Phase B on main (GH-2969)#2974
Merged
Conversation
Phase B of the F# code-generation audit (#2969): teach the in-memory stateful saga frame set to emit F#, and render a real saga's start + continue chains to compilable F#. A string-keyed CountingSaga (Start + Handle) drives the fixture. Saga frame F# overrides: - CreateNewSagaFrame: `let saga = T()`. - ResolveSagaFrame / SagaStoreOrDeleteFrame / ConditionalSagaInsertFrame: composites that delegate to their inner frames' F# and render the surrounding `if/else` (IsCompleted -> delete/update; not-completed -> insert). - SetSagaIdFrame / SetSagaIdFromSagaFrame: SetSagaId + a null-guarded SetTag block (no `?.`, pipe SetTag to ignore). - AssertSagaStateExistsFrame: `raise (UnknownSagaException(typeof<T>, sagaId))`. - PullSagaIdFromMessageFrame / PullSagaIdFromEnvelopeFrame: the saga-id resolvers. The string branch uses `if isNull x then envelope.SagaId else x`; the Guid/numeric branch uses F#'s auto-tupled `match Type.TryParse(s) with | true, v -> ... | _ -> ...`, with Unchecked.defaultof<T> as the zero/default sentinel. In-memory Load/Store/Delete are plain MethodCalls (already emit F#); the not-found null-guard comes from JasperFx's IfElseNullGuardFrame (already F#). fsharp-coverage after Phase B: 24 implemented / 2 skipped / 18 remaining of 44 Frame types in Wolverine.dll (up from 15/2/27 at Phase A). Deferred to a Phase B follow-up (not reachable from a minimal in-memory saga, so not exercised here): CreateMissingSagaFrame (reassigns the loaded-saga var; needs the load variable marked mutable), the store-specific LoadSagaOperation / SagaOperation / EnrollAndFetchSagaStorageFrame wrappers, ShouldProceedGuardFrame (ResequencerSaga), the Marten/Polecat data-requirement frames (separate assemblies / per-store fixtures), and the behavioural run-step (TypeLoadMode.Static). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F# code generation Phase B: in-memory saga frames (GH-2969)
…H-2969) Phase C of the F# code-generation audit (#2969) — Wolverine.Http. Stands up the HTTP F# fixture surface and renders a real HttpChain to compilable F# for the static-response (GET) path. - New per-surface trio under src/Testing/: Wolverine.Http.FSharpContracts (endpoint classes), Wolverine.Http.FSharpFixture (F#, checked-in Generated.fs), Wolverine.Http.FSharpTests (compile gate + HTTP fsharp-coverage smoke test). Added to wolverine_fsharp.slnx; CI now runs `dotnet test wolverine_fsharp.slnx` across both the Core and Http surfaces. - HTTP chains render with no web host via HttpChain.ChainFor<T>(x => x.Method(), httpGraph) + httpGraph.StartAssembly(rules) + AssembleTypes + GenerateFSharpCode. The generated type subclasses the public HttpHandler base. - WriteStringFrame emits F# (HttpHandler.WriteString is static, so it resolves without a `this`). A `[WolverineGet]` endpoint returning a string renders and compiles end to end. Deferred — blocked on the JasperFx F# self-identifier gap (to be filed upstream): HttpHandler.ReadJsonAsync<T> and WriteJsonAsync<T> are INSTANCE methods called unqualified in the generated handler, which F# cannot resolve from a `member _.Handle` body. So ReadJsonBody / WriteJsonFrame (the JSON POST path) can't emit F# until JasperFx emits a named self for generated members. The POST endpoint is kept in the contracts for that follow-up but is not rendered yet. Same root gap as RecordMessageCausationFrame (Phase B). fsharp-coverage with Wolverine.Http loaded: 25 implemented / 2 skipped / 54 remaining of 81 Frame types. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
F# code generation Phase C (partial): static-response HTTP endpoints (GH-2969)
This was referenced Jun 1, 2026
Merged
This was referenced Jun 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Catch-up PR. #2971 merged the
feat-2969-fsharp-codegen-foundationbranch tomainat its foundation+Phase-A state, but #2973 (Phase B — in-memory saga frames) merged into this same branch ~18 seconds later. So Phase B is in the branch but never reachedmain. This PR brings it across.Diff is Phase B only (foundation + Phase A are already on
main):CreateNewSagaFrame,ResolveSagaFrame,SagaStoreOrDeleteFrame,ConditionalSagaInsertFrame,SetSagaIdFrame,SetSagaIdFromSagaFrame,AssertSagaStateExistsFrame,PullSagaIdFromMessageFrame,PullSagaIdFromEnvelopeFrame).CountingSagafixture + multi-chain rendering.Already reviewed/merged as #2973 into the branch; this is purely the branch→main hop.
fsharp-coverageafter this lands: 24 implemented / 2 skipped / 18 remaining.Part of #2969.
🤖 Generated with Claude Code