You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Reframed 2026-08-03. The original plan — hand-write the 15 missing overloads — is obsolete. ProjectionScenario has been lifted into JasperFx.Events so both stores share one implementation, which closes this gap without porting anything. The original gap analysis is preserved at the bottom; it is still the best record of what diverged.
What to do now
Consume JasperFx.Events.TestSupport.ProjectionScenario<TOperations, TQuerySession> (JasperFx/jasperfx#616, shipping in JasperFx.Events 2.38.0) instead of maintaining Polecat's own port.
Delete Polecat's seven Events/TestSupport/ files and replace them with one subclass closing the generic pair over Polecat's session types. Marten's equivalent adoption is JasperFx/marten#5133 — a ~70-line class, −692/+51 lines — and is the reference to copy.
The subclass implements seven abstract seam members, all one-liners:
Member
What it does
DeleteExistingDataAsync(ct)
Wipe event + projection storage
HasAnyAsyncProjections
Whether to stand up a daemon
BuildDaemonAsync(tenantId)
Build the projection daemon
OpenSession(tenantId)
Open a writable session
SaveChangesAsync(session, ct)
Commit (no shared interface declares it)
EventsFor(session)
Session's IEventOperations
LoadDocumentAsync<T>(session, object id, ct)
Load by id, dispatching on the id's runtime type
This should be nearly free for Polecat, because PolecatComplianceFixture already implements every one of these — the seam was deliberately shaped to match EventStoreComplianceFixture, including the object-id load dispatch. Lift the bodies straight across.
What the lift changes about the API
Worth knowing before adopting, since Polecat's port matches the old Marten shape:
The 15 missing overloads simply exist now, inherited. Nothing to write.
Append / StartStream return void, not StreamAction — the old return value was a throwaway disconnected from the queued operation. The StartStream overloads that generate their own id return that Guid.
DocumentShouldExist<T> / DocumentShouldNotExist<T> take object ids — one overload each, not four per identity type. Typed call sites still compile.
DoNotDeleteExistingData → DeleteExistingData (default true); no more double negative.
Assertion failures throw ProjectionScenarioAssertionException; a failed action now stops the scenario instead of letting later steps run against unintended state; a scenario can only execute once; Timeout is configurable and the CancellationToken is honored.
Original issue (2026-08-02): the 15-overload gap analysis
Polecat/Events/TestSupport/ProjectionScenario mirrors Marten's design file-for-file (same seven files, same type names, same entry point store.Advanced.EventProjectionScenario(...)), but the event-operations half is implemented much more shallowly. A test written against Marten's scenario API frequently will not compile against Polecat's.
Measured against Marten/Events/TestSupport/ at marten master and Polecat/Events/TestSupport/ at polecat main.
Assertions: already at parity
Nine members each, identical signatures — AssertAgainstProjectedData, DocumentShouldExist<T> and DocumentShouldNotExist<T> over Guid/int/long/string. The only difference is the generic constraint (where T : notnull in Marten, where T : class in Polecat), which is a deliberate divergence, not a gap.
Event operations: 11 members vs Marten's 28
Present in Marten, missing in Polecat:
Every IEnumerable<object> overload — Polecat is params object[] only:
StreamActionStartStream<TAggregate>(paramsobject[]events);// server-assigned id
Stream compacting — struck after reading Marten's implementation. Marten's two CompactStreamAsync<T> overloads exist only to satisfy IEventOperations and both bodies are:
thrownewNotSupportedException();
So this is not a capability Polecat is missing; Polecat simply does not declare the stubs. Nothing to do here, and arguably Marten's shape is the worse of the two. Excluded from the count below.
Suggested scope
Add the IEnumerable<object> overloads — mechanical, and the highest value per line.
Add the Type-argument and no-identity StartStream overloads.
That is the whole gap: 15 overloads, all of them shape rather than capability.
Related
There is a standing question of whether ProjectionScenario should be lifted into JasperFx.Events generic on <TOperations, TQuerySession> instead of maintained twice — see the companion Marten issue. Closing this gap first is not wasted work either way: it establishes what the shared surface would have to cover, and it is useful immediately regardless of whether the lift happens.
Found by
Compliance wave 2 (marten#5118), while checking whether the two Events/TestSupport/ trees are duplicates. They are not — same design, different depth.
Note
Reframed 2026-08-03. The original plan — hand-write the 15 missing overloads — is obsolete.
ProjectionScenariohas been lifted intoJasperFx.Eventsso both stores share one implementation, which closes this gap without porting anything. The original gap analysis is preserved at the bottom; it is still the best record of what diverged.What to do now
Consume
JasperFx.Events.TestSupport.ProjectionScenario<TOperations, TQuerySession>(JasperFx/jasperfx#616, shipping in JasperFx.Events 2.38.0) instead of maintaining Polecat's own port.Delete Polecat's seven
Events/TestSupport/files and replace them with one subclass closing the generic pair over Polecat's session types. Marten's equivalent adoption is JasperFx/marten#5133 — a ~70-line class, −692/+51 lines — and is the reference to copy.The subclass implements seven abstract seam members, all one-liners:
DeleteExistingDataAsync(ct)HasAnyAsyncProjectionsBuildDaemonAsync(tenantId)OpenSession(tenantId)SaveChangesAsync(session, ct)EventsFor(session)IEventOperationsLoadDocumentAsync<T>(session, object id, ct)This should be nearly free for Polecat, because
PolecatComplianceFixturealready implements every one of these — the seam was deliberately shaped to matchEventStoreComplianceFixture, including theobject-id load dispatch. Lift the bodies straight across.What the lift changes about the API
Worth knowing before adopting, since Polecat's port matches the old Marten shape:
Append/StartStreamreturnvoid, notStreamAction— the old return value was a throwaway disconnected from the queued operation. TheStartStreamoverloads that generate their own id return thatGuid.DocumentShouldExist<T>/DocumentShouldNotExist<T>takeobjectids — one overload each, not four per identity type. Typed call sites still compile.DoNotDeleteExistingData→DeleteExistingData(defaulttrue); no more double negative.ProjectionScenarioAssertionException; a failed action now stops the scenario instead of letting later steps run against unintended state; a scenario can only execute once;Timeoutis configurable and theCancellationTokenis honored.Execute→ExecuteAsync.Background on why the shape changed first: JasperFx/marten#5127 and JasperFx/marten#5132.
Blocked on
JasperFx/jasperfx#616 merging and JasperFx.Events 2.38.0 publishing.
Original issue (2026-08-02): the 15-overload gap analysis
Polecat/Events/TestSupport/ProjectionScenariomirrors Marten's design file-for-file (same seven files, same type names, same entry pointstore.Advanced.EventProjectionScenario(...)), but the event-operations half is implemented much more shallowly. A test written against Marten's scenario API frequently will not compile against Polecat's.Measured against
Marten/Events/TestSupport/at marten master andPolecat/Events/TestSupport/at polecat main.Assertions: already at parity
Nine members each, identical signatures —
AssertAgainstProjectedData,DocumentShouldExist<T>andDocumentShouldNotExist<T>over Guid/int/long/string. The only difference is the generic constraint (where T : notnullin Marten,where T : classin Polecat), which is a deliberate divergence, not a gap.Event operations: 11 members vs Marten's 28
Present in Marten, missing in Polecat:
Every
IEnumerable<object>overload — Polecat isparams object[]only:This is the one that bites in practice: a caller with a
List<object>of events has to spread it at every call site.The
Type-argument aggregate overloads — for code that has aTyperather than a generic parameter:The no-identity generic overload:
Stream compacting— struck after reading Marten's implementation. Marten's twoCompactStreamAsync<T>overloads exist only to satisfyIEventOperationsand both bodies are:So this is not a capability Polecat is missing; Polecat simply does not declare the stubs. Nothing to do here, and arguably Marten's shape is the worse of the two. Excluded from the count below.
Suggested scope
IEnumerable<object>overloads — mechanical, and the highest value per line.Type-argument and no-identityStartStreamoverloads.That is the whole gap: 15 overloads, all of them shape rather than capability.
Related
There is a standing question of whether
ProjectionScenarioshould be lifted intoJasperFx.Eventsgeneric on<TOperations, TQuerySession>instead of maintained twice — see the companion Marten issue. Closing this gap first is not wasted work either way: it establishes what the shared surface would have to cover, and it is useful immediately regardless of whether the lift happens.Found by
Compliance wave 2 (marten#5118), while checking whether the two
Events/TestSupport/trees are duplicates. They are not — same design, different depth.🤖 Generated with Claude Code