Repository navigation
Release v18.3.0
Added
.FromAll()fluent projections can nowCount,Increment, andDecrementinto a dictionary-typed read model property, with the key resolved per event from anEventContextvalue (e.g. one counter per event type) (#2464)- The Projection Declaration Language's
allblock supports the same dynamic dictionary-key syntax oncount/increment/decrement(#2464) - The Workbench's per-language client-code preview (C#, Kotlin, TypeScript, Elixir) now renders the
all/everyblock and.FromAll()/[FromAll]mappings, including the C# dynamic dictionary-key shape - Read models can now recover automatically when a projection or reducer definition changes shape (new event types added, filters changed) instead of requiring a manual rebuild, governed by a new evolution policy (automatic / partial-only / manual)
Fixed
- A projection built only from the
allblock or.FromAll(), with no separatefromblock, never processed a single event at runtime — every event was filtered out before it reached a property mapper (#2464) - A dynamic dictionary key resolved against an
EventContextvalue could silently pick up an unrelated read-model property's schema (e.g. a stringidproperty) and produce the wrong value type instead of the expectedint64(#2464) - A dynamic dictionary-key mapping targeting a property that already has a concrete, non-dynamic schema now fails with a clear error instead of silently succeeding (#2464)
- Regenerating a projection declaration containing a keyword-named property nested in a dotted path (e.g.
eventType.id) no longer produces a declaration that fails to re-parse - Reducer definitions are now compared by value, and a passive reducer's fingerprint and definition survive round-tripping through MongoDB and SQL storage correctly
- An observer subscribed to all events (
SubscribeToAllEvents, the mechanism behindall/.FromAll()) could get stuck disconnecting itself every time it entered its routing state, because the "no event types on the subscription" disconnect check does not distinguish a genuinely empty subscription from the empty-by-design subscription an all-events observer always has - The same all-events observer could also silently drop every live event it was handed instead of dispatching it to the subscriber, for the same reason
- The catch-up job for an all-events observer resolved zero schemas and zero events to read, because it asked for event types from the observer's static, subscribe-time definition instead of the current, full set of registered event types
ObserverKeys(used to enumerate the partitions an observer must catch up) built its query with an unconditional$infilter on event type; MongoDB's$inwith an empty array matches no document at all, so an all-events observer — whose definition carries no fixed event type list — always resolved zero partitions to catch up
See also
Consolidates #4029 (automatic read-model evolution) into this PR so both ship together. Investigating #2464 surfaced that the dictionary half was already merged in a prior PR but never actually worked at runtime, alongside a broader gap where all/.FromAll()-only projections received no events at all, and that none of the Workbench's four per-language code generators rendered the all/every block. This PR fixes all of that, adds the missing fluent client API, extends the code generators, and covers the new behavior with unit, component, and out-of-process integration specs.
The shared Screenplay compiler and its Monaco editor extension were also audited (Cratis/Screenplay#195): no compiler defects were found, but the Monaco completion provider was extended to offer completions for the dynamic dictionary-key syntax.
Known gap: a real, MongoDB-backed, out-of-process integration scenario for .FromAll() counting into a dictionary was written and used to find the four observer/catch-up fixes above, but even after all four fixes and a generously widened (5 minute) subscription timeout, the scenario still did not reliably reach the observer's Active state in CI. The four fixes are each independently correct and covered by a dedicated Core.Specs unit specification against the exact class involved (including one that exercises the real ProjectionFactory end to end), but whatever remains wrong for that one specific out-of-process scenario was not conclusively identified, so the scenario was removed rather than left hanging or masked behind a wide timeout. Follow-up: file an issue to track root-causing the remaining out-of-process gap for all-events catch-up against real MongoDB-backed storage.