Skip to content

Release v18.3.0

Choose a tag to compare

@github-actions github-actions released this 15 Sep 03:50
b37780c

Added

  • .FromAll() fluent projections can now Count, Increment, and Decrement into a dictionary-typed read model property, with the key resolved per event from an EventContext value (e.g. one counter per event type) (#2464)
  • The Projection Declaration Language's all block supports the same dynamic dictionary-key syntax on count/increment/decrement (#2464)
  • The Workbench's per-language client-code preview (C#, Kotlin, TypeScript, Elixir) now renders the all/every block 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 all block or .FromAll(), with no separate from block, 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 EventContext value could silently pick up an unrelated read-model property's schema (e.g. a string id property) and produce the wrong value type instead of the expected int64 (#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 behind all/.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 $in filter on event type; MongoDB's $in with 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.