Skip to content

Observable collection change streams

The observable-query hub can compare collection snapshots with stable item identities and send a ChangeSet describing added, replaced, and removed items. This is a transport-level comparison of query results, not a Chronicle event stream or a promise of durable replay.

Set transferMode on the hub subscription request. The modes below describe subject-backed collection emissions whose items have an Id property. Without one, every mode sends full data without a change set:

ModeFirst emissionLater emissions
Omitted (legacy)Full data plus change set (initial items added)Full data plus change set
fullFull data, no change setFull data, no change set
deltaFull data, no change setChange set; full data is null/omitted

Legacy additive deltas can reduce client reconciliation work, but sending both the snapshot and delta does not reduce payload bytes. Delta mode is the bandwidth-saving choice. Single-object and async-enumerable streams are not covered by this collection-delta contract. Direct per-query transports do not automatically inherit hub transfer-mode behavior.

For items with an Id property (case-insensitive), ChangeSetComputor compares identity and serialized JSON:

  • added: an identity appears in the new snapshot.
  • replaced: the identity remains but its serialized representation differs.
  • removed: an identity disappears; entries are removed items, not merely ID strings.

If collection items have no Id property, Arc sends the complete current snapshot in data without a change set on every emission, even in delta mode, and logs one warning per subscription. To get deltas, give each item a stable, unique Id. A concept-valued Id works too. The public ChangeSetComputor.ComputeByJson utility remains available for explicit JSON comparisons, but observable-query transfer does not use that fallback.

Previous delivered snapshot

Compare identities and JSON

Current intercepted snapshot

Added / replaced / removed

Apply subscription transfer mode

Client state

Illustrative payload fragment for a later delta-mode frame; the surrounding QueryResult metadata and hub envelope are intentionally not repeated:

{
"data": null,
"changeSet": {
"added": [{ "id": "account-2", "balance": 50 }],
"replaced": [{ "id": "account-1", "balance": 120 }],
"removed": [{ "id": "account-3", "balance": 0 }]
}
}

When no change set is present, replace client state with data as the current snapshot (including after reconnect). A reconnect/replacement subscription starts with a fresh snapshot; a change set is not a resume token. Under delta mode, do not replace client state with null just because the later frame omits full data.

ChangeSet has IEnumerable<object> properties Added, Replaced, and Removed. ChangeSetComputor takes JsonSerializerOptions; its Compute(previousItems, currentItems) returns a change set, and FindIdentityProperty(Type) locates the conventional ID property.

These APIs compare snapshots you supply; they do not observe a database independently. For database observation, see MongoDB-backed observable queries. For client reconstruction, see frontend change streams.