Skip to content

[AIT-38] Protocol v6 and support for partial sync of objects#2152

Open
VeskeR wants to merge 2 commits intomainfrom
AIT-38/partial-objects-sync
Open

[AIT-38] Protocol v6 and support for partial sync of objects#2152
VeskeR wants to merge 2 commits intomainfrom
AIT-38/partial-objects-sync

Conversation

@VeskeR
Copy link
Contributor

@VeskeR VeskeR commented Jan 23, 2026

Spec for this ably/specification#413.

Resolves AIT-38.

This also removes unnecessary SyncObjectsDataPool class and simplifies a sync logic a bit by moving it to RealtimeObject.

Summary by CodeRabbit

  • New Features

    • Improved LiveObjects synchronization now reliably handles partial sync scenarios across multiple messages and gracefully manages unknown object types, ensuring better object state accumulation and merging.
  • Updates

    • Protocol version updated to v6.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 23, 2026

Walkthrough

The changes implement support for partial object synchronization in the AblyJS SDK by bumping the protocol version to 6, replacing the SyncObjectsDataPool utility with an internal Map-based accumulation structure in RealtimeObject, and adding comprehensive tests for handling partial OBJECT_SYNC messages across multiple sequential messages.

Changes

Cohort / File(s) Summary
Protocol Version Updates
src/common/lib/util/defaults.ts, test/realtime/init.test.js, test/rest/http.test.js
Updated protocol/API version expectation from 5 to 6 across defaults, connection URI assertions, and HTTP header validation.
Sync Pool Removal & Replacement
src/plugins/liveobjects/syncobjectsdatapool.ts, src/plugins/liveobjects/realtimeobject.ts, scripts/moduleReport.ts
Deleted SyncObjectsDataPool module entirely (98 lines removed). Refactored RealtimeObject to use internal Map\<string, ObjectMessage\> (_syncObjectsPool) with new methods (_applyObjectSyncMessages, _mergeMapSyncState) for accumulating and merging partial object state across multiple OBJECT_SYNC messages. Removed allowlist reference in module validation.
Partial Sync Test Coverage
test/realtime/liveobjects.test.js
Added 201 lines of new tests covering resilience to unknown object types mid-sync, multi-message partial OBJECT_SYNC sequences (root, counter, map), and cross-message map entry merging with interleaved initial/materialized entries.

Sequence Diagram

sequenceDiagram
    participant Network
    participant RealtimeObject
    participant _syncObjectsPool
    participant LiveObjects

    Network->>RealtimeObject: OBJECT_SYNC (partial: root entry)
    RealtimeObject->>_syncObjectsPool: accumulate root
    
    Network->>RealtimeObject: OBJECT_SYNC (partial: counter)
    RealtimeObject->>_syncObjectsPool: accumulate counter entry
    
    Network->>RealtimeObject: OBJECT_SYNC (partial: map entries)
    RealtimeObject->>_syncObjectsPool: merge map state
    RealtimeObject->>RealtimeObject: _mergeMapSyncState()
    
    RealtimeObject->>RealtimeObject: _applyObjectSyncMessages()
    RealtimeObject->>LiveObjects: materialize accumulated state
    RealtimeObject->>_syncObjectsPool: clear() on sync end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

🐰 The pool is gone, replaced with maps so fine,
Partial syncs now work in perfect time!
Messages merge across the network's flight,
Objects gather data, piece by piece, just right!
Version six brings streaming light. 🌟

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main changes: Protocol v6 upgrade and partial sync of objects support, which align with the primary objectives and code changes throughout the PR.
Linked Issues check ✅ Passed The PR successfully implements support for partial sync of objects (AIT-38) with client-side changes to accept and apply partial object sync messages, plus Protocol v6 introduction.
Out of Scope Changes check ✅ Passed All changes are in-scope: protocol version bump, partial sync implementation, test updates, and file restructuring directly support the linked AIT-38 objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch AIT-38/partial-objects-sync

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/plugins/liveobjects/realtimeobject.ts`:
- Around line 372-381: The code uses objectState.objectId as a Map key without
validation which can cause messages to pile up under undefined; inside the loop
in realtimeobject.ts where objectState and objectId are extracted (symbols:
objectState, objectId, this._syncObjectsPool), add a guard that checks if
objectId is null/undefined and if so skip storing/merging that message and emit
a warning/error (use the existing logger on the class, e.g., this.logger.warn or
console.warn) before continue; ensure no entry is created in _syncObjectsPool
for missing objectId and include the offending message or minimal context in the
log to aid debugging.
🧹 Nitpick comments (1)
test/realtime/liveobjects.test.js (1)

1182-1242: Consider asserting on a post-sync root to avoid timing races.
The assertions currently use entryPathObject created before the partial sync; if OBJECT_SYNC application ever becomes async, this can become flaky. Grabbing the root after the final sync message guarantees you’re asserting on the fresh state.

♻️ Suggested tweak
-            expect(entryPathObject.get('stringKey').value()).to.equal('hello', 'Check root has correct string value');
-            expect(entryPathObject.get('counter').value()).to.equal(15, 'Check counter has correct aggregated value');
-            expect(entryPathObject.get('map').get('foo').value()).to.equal('bar', 'Check map has initial entries');
-            expect(entryPathObject.get('map').get('baz').value()).to.equal('qux', 'Check map has materialised entries');
+            const root = await channel.object.get(); // ensure OBJECT_SYNC has fully applied
+            expect(root.get('stringKey').value()).to.equal('hello', 'Check root has correct string value');
+            expect(root.get('counter').value()).to.equal(15, 'Check counter has correct aggregated value');
+            expect(root.get('map').get('foo').value()).to.equal('bar', 'Check map has initial entries');
+            expect(root.get('map').get('baz').value()).to.equal('qux', 'Check map has materialised entries');

Copy link
Collaborator

@lawrence-forooghian lawrence-forooghian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, some minor comments. I'd suggest we finalise the spec (have left some comments there) and update the spec point references before merging.

* for the same object, so only the entries need to be merged.
* @spec RTO5b2b1
*/
private _mergeMapSyncState(existingEntry: ObjectMessage, newObjectMessage: ObjectMessage): void {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question on spec re which ObjectMessage's serialTimestamp property should be kept: ably/specification#413 (comment)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was your motivation for removing this class? I'm a bit surprised that you did it as part of this work since it seems that it would have been an ideal place to put the new logic for merging ObjectMessages into the pool, instead of expanding RealtimeObject.

}

if (newObjectState.map?.entries) {
// During partial sync, no two messages contain the same map key,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Worth mentioning in the spec, I'd say

},

{
description: 'partial OBJECT_SYNC builds object tree across multiple messages',
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel I must have missed something with this first test — in what sense is it testing a partial object sync? There doesn't seem to be any object whose state is split across multiple messages, which I thought was the defining characteristic of a partial sync.

initialEntries: {
initialKey: { timeserial: lexicoTimeserial('aaa', 0, 0), data: { string: 'initial' } },
},
// materialisedEntries are merged across partial messages
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • the usage of "are" here is inconsistent — in the first one it describes a characteristic of the data sent by the server (makes sense), and in the second it describes an expected behaviour of the SDK (this note would be more appropriate against the assertions I think)
  • I think that the injection of the final three messages would be easier to read if it were done in a loop in which the only thing that varies is the syncSerial and the materialisedEntries

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants