Repository navigation
JasperFx 2.67.0
Event query tags, made composable — plus two consistency fixes found by store-parity work.
EventQuery can now AND a tag filter with everything else (#801)
EventQuery composed every filter it carried with AND — event types, time and sequence windows, stream id, tenant — except tags. The dictionary form of a tag query lived only on the non-composable IEventStore.QueryByTagsAsync(IReadOnlyDictionary<string,string>, …), so a caller filtered by tags or by everything else, never both. Taking that path also gave up paging and TotalCount.
New EventQuery.TagValues (Dictionary<string,string>) sits beside the rich TagConditions:
- Entries AND with each other and with every other filter — the opposite combinator from
TagConditions, whose conditions OR. - The answer stays on the
PagedEventspath, so a tag query keeps paging and a truthfulTotalCountof distinct matching events. - Its own
EventQueryFilters.TagValuesbit, so the guard rail refuses it by name on a store that hasn't implemented it. - The two tag members are alternatives, not a combination: supplying both throws
ArgumentExceptionfrom the newEventQuery.AssertIsWellFormed(), whichAssertFiltersAreSupportednow calls first thing.
Tag names are contract now, not an implementation detail. New TagTypeRegistrationExtensions.FindByTagName / RequireByTagName match a name against the tag type's CLR simple name ("StudentId") or its registered table suffix ("student"), case-insensitively, refusing an unknown name loudly rather than answering empty. This moved into the abstraction because the stores had already drifted — Marten matched only the CLR name and compared values case-sensitively while Polecat accepted either spelling case-insensitively — under a surface with no compliance coverage at all. A caller holding a store descriptor cannot discover which spelling an engine takes.
event-query --tags was broken for every namespaced tag type (#803)
The flag turned each JSON property into a condition whose TypeDescriptor.FullName was the bare property name. EventTagQuerySpec.ResolverFor matches on full name and nothing else, so the lookup missed for any tag type in a namespace and the flag threw UnknownTagQueryTypeException against every real store. It was wrong twice over: even with resolution, the documented '{"StudentId":"s-1"}' could not deserialize into a wrapper record, because a tag value is serialized under its runtime type and a record id round-trips as {"Value":"s-1"}.
Both failures are the same mistake — a name is not a type — so --tags now builds TagValues, where the store resolves the name against its own graph. Consequently entries AND rather than OR (what an operator naming two tags means), and a composite JSON value is refused rather than stringified into something that matches nothing.
ResolverFor's doc comment claimed a simple-name fallback the code never had; it now describes the exact match it performs and why guessing would be worse.
Compaction consistency (#800)
- The default
CompactStreamAsync<T>refusal said "Use Marten or Polecat" — stale in both directions, since Fisher implements compacting and Polecat implements the typed overload while refusing the untyped one. Support is per-overload, so no store list is safe; the message now names the capability. - The stores disagreed on whether compaction requires a registered aggregation projection for
T(Marten refused, Fisher inferred the fold and got it right). The compliance suite pinned neither, so the same policy declaration worked on one store and was refused on another, invisibly until runtime. The suite now states inference: the typed overload namesToutright, and requiring a registration on top would mean a compaction policy could only ever target an aggregate the application already snapshots.
Compliance suite
EventQueryCompliance gains a TagValues section — both name spellings, case-insensitive names and values, AND-across-entries with no duplication of a doubly-tagged event, composition with type and sequence windows, paging with interleaved noise, the empty/refused/malformed cases, a kitchen-sink query whose decoy fails only the second tag entry, and an end-to-end fact running the event-query command's own output through the store. StreamCompactingCompliance gains the inference fact, asserting the whole round trip rather than that the call didn't throw.
⚠️ Upgrading a store
EventQueryFilters.TagValues joined EventQueryFilters.All. A store that declares All and takes this upgrade silently claims support for a filter it has not written — the exact failure the guard rail exists to prevent. Implement it, or subtract it (EventQueryFilters.All & ~EventQueryFilters.TagValues) until you do.
Tracking issues: marten#5365, marten#5366, polecat#575, fisher#230.
Full changelog: V2.66.1...V2.67.0