fix: one field-resolution law across aggregation hooks, source.where, removeMany, and find() spellings
Four fixes from a consumer conformance report, one root disease — two field-resolution regimes where there must be one: - The delete/update aggregation hooks fed the engine a partial entity view (type/service/data/metadata only), so a reserved-field groupBy (subtype, visibility, ...) resolved to a nonexistent group on the way down: counts drifted upward forever after deletes, and updates moving an entity between reserved-field groups double-counted. The hooks now pass the full-fidelity view via entityForAggFromRawRecord (every reserved field top-level, mirroring the add path); the update sites pass the full get() view instead of a hand-rolled subset. - Aggregation source.where resolved fields only against the custom metadata bag, so where on a reserved field silently matched nothing. The matcher now resolves each filtered field through resolveEntityField — the same single source of truth groupBy uses. - removeMany() with no usable selector (bare array passed positionally, empty params, ids: []) resolved successfully having deleted nothing. All three now throw; the two legacy tests that pinned the silent no-op as 'graceful' now pin the refusal. - find() where keys accept both spellings: a metadata.-prefixed key falls back to its flattened spelling when the prefixed one is not indexed (metadata is flattened at index time). A literal nested custom key named metadata still wins when indexed as spelled. Five regression pins in aggregate-reserved-fields.test.ts (4 of 5 vary red on the unfixed code).
This commit is contained in:
parent
42037d0cd0
commit
945d92d29e
7 changed files with 303 additions and 45 deletions
|
|
@ -88,10 +88,18 @@ function matchesSource(entity: Record<string, unknown>, source: AggregateDefinit
|
|||
if (entity.service !== source.service) return false
|
||||
}
|
||||
|
||||
// Metadata where filter — match against the entity's metadata sub-object
|
||||
// Where filter — resolve each filtered field through resolveEntityField,
|
||||
// the SAME single source of truth groupBy uses (top-level standard fields
|
||||
// + custom metadata). Matching only the metadata sub-object made
|
||||
// where:{subtype}/{visibility}/… a silent no-op: reserved fields never
|
||||
// live in the custom bag, so those filters could never match anything.
|
||||
if (source.where && Object.keys(source.where).length > 0) {
|
||||
const metadata = (entity.metadata ?? entity) as Record<string, unknown>
|
||||
if (!matchesMetadataFilter(metadata, source.where)) return false
|
||||
const e = entity as unknown as HNSWNounWithMetadata
|
||||
const resolved: Record<string, unknown> = {}
|
||||
for (const key of Object.keys(source.where)) {
|
||||
resolved[key] = resolveEntityField(e, key)
|
||||
}
|
||||
if (!matchesMetadataFilter(resolved, source.where)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue