[Feature] Path based LiveObjects Implementation#1214
Conversation
sacOO7
commented
Jun 11, 2026
- PathObject documentation -> https://ably.com/docs/liveobjects/concepts/path-object
- Public API doc -> PathObject based LiveObjects API
- PathObject spec -> https://sdk.ably.com/builds/ably/specification/main/objects-features/
- PathObjectSubscriptionOptions: validate depth fail-fast per RTPO19c1a,
throwing AblyException with ErrorInfo(400, 40003) when depth <= 0.
Depth is now a primitive int; the "no depth / infinite depth" state is
expressed via a new no-arg constructor (mirrors ably-js `{}` options),
so no null handling is needed
- LiveMapValue: defensively copy binary payloads on creation and access,
making the RTLMV3d immutability guarantee real for byte[] values
- ObjectData#getBytes: document that the returned array is the underlying
message payload and must be treated as read-only
- JsonObjectPathObject/JsonArrayPathObject: reword "primitive resolution"
javadoc for clarity
- LiveMapPathObject#at: fix javadoc equivalence example to compile
(get() returns base PathObject, so chain via asLiveMap())
…al-public-api [LiveObjects] Implement path-based LiveObjects public API
|
Important Review skippedToo many files! This PR contains 233 files, which is 83 over the limit of 150. To get a review, narrow the scope: Upgrade to a paid plan to raise the limit. ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (233)
You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…ccessor Introduce the public, strongly-typed, path-based LiveObjects entry point on a realtime channel, accessed via `channel.object`. - RealtimeObject: exposes `get()` returning the root LiveMapPathObject, and extends ObjectStateChange to subscribe to objects sync-state events (on/off/offAll). - ObjectStateChange / ObjectStateEvent: the SYNCING/SYNCED sync-state subscription API surface. - ChannelBase.object: a public field providing `channel.object` access. When the LiveObjects plugin is not installed, the field is assigned RealtimeObject.Unavailable - a null-object guard whose methods fail fast with a clear plugin-missing error (statusCode 400, code 40019) instead of an NPE, keeping the `channel.object.<method>()` syntax consistent in both cases. The plugin-present branch is intentionally left as a TODO until the LiveObjects plugin exposes the new io.ably.lib.object.RealtimeObject type (getInstance currently returns the legacy io.ably.lib.objects.RealtimeObjects). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ture - Replaced ObjectsSubscription import with Subscription as per requirement
…tion-for-path-based-interfaces [AIT-928] feat(liveobjects): add path-based RealtimeObject and channel.object accessor
…mplementation' into chore/liveobjects-add-basic-implementation
… relevant error codes under `Errors.kt`
- Marked PathObject#getValue as nullable when value doesn't exist at given path
- Implemented JsonSerializer annotation for better json handling
Point the JSON and MsgPack serializers in io.ably.lib.object.serialization
at the new WireObjectMessage wire model instead of the legacy
io.ably.lib.objects.ObjectMessage, so the new `object` package has no
dependency on the legacy `objects` package.
- DefaultSerialization: implement the new ObjectSerializer interface and
(de)serialize WireObjectMessage arrays (reflectively loaded via
ObjectSerializer.Holder).
- Json/MsgpackSerialization: bind the Wire* types; replace legacy
objectError with the object package's objectStateError (same 500/92000).
- WireObjectMessage: restore the gson annotations required for wire-format
fidelity - @SerializedName("object") on objectState and
@JsonAdapter(WireObjectDataJsonSerializer) on WireObjectData.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes checkstyle AvoidStarImport violation on com.google.gson.*. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…er overloads The parameterless increment()/decrement() on both DefaultLiveCounterPathObject and DefaultLiveCounterInstance duplicated the full body of their Number counterparts with a hardcoded 1. Make them delegate to increment(1)/decrement(1) instead. All spec checks live in the parameterized overload, and the default-of-1 spec points (RTPO17a1/RTPO18a1, RTINS14a1/RTINS15a1) are preserved on the delegating methods. Pure refactor, no behaviour change.
…opagation
Rename the DefaultRealtimeObject async bridges for clarity: asyncApi -> asyncFuture
(generic, value-returning) and asyncVoidApi -> asyncVoidFuture (the Unit->Void adapter
required by the Java write APIs), updating all call sites. Behaviour is unchanged; the
thenApply { null } bridge stays because kotlinx's future builder cannot produce a
CompletableFuture<Void> (its type param is non-null, Void's only value is null).
Add DefaultRealtimeObjectAsyncTest asserting result and exception propagation for both
helpers, and - crucially - that a failed operation does not cancel the shared sequential
scope for subsequent operations, locking in the SupervisorJob failure-isolation guarantee.
[AIT-1008] Translate LiveObjects `objects` UTS test specs to Kotlin tests
…ntation [AIT-934] feat(liveobjects): `LiveObjects` implementation to new path-based API (Kotlin)
getOrPut on a ConcurrentHashMap may invoke its factory on a losing thread during a concurrent first-time getInstance, constructing a duplicate DefaultRealtimeObject whose init-started coroutine would then leak. computeIfAbsent runs the factory at most once per channel, so exactly one instance is created. It needs Java 8 / Android API 24, already required by this module's CompletableFuture-based public API.
…ync waiters (RTO20e/e1) Two genuine spec-compliance bugs surfaced by cross-checking the UTS unit suite against ably-js (the reference implementation) — both confirmed at source level against objects-features.md, with ably-js compliant on each. RTO23e — get() threw 90001 on a DETACHED channel instead of re-attaching: get() ran the full access-config check (which gates on channel state) before getRootAsync's ensure-active-channel could run. It now uses a mode-only check (throwIfMissingObjectSubscribeMode, RTO23a) and delegates channel state to ensureAttached (RTO23e/RTL33): DETACHED re-attaches and resolves, only FAILED rejects with 90001. The full check remains on the access methods on/off/offAll (RTO25b). RTO20e/RTO20e1 — orphaned sync waiter could hang publishAndApply forever: the shared syncCompletionWaiter field was overwritten by every startNewSync, so a write awaiting SYNCED across the ordinary ATTACHED(HAS_OBJECTS) -> OBJECT_SYNC re-sync pair awaited a deferred nobody would ever complete (hang), and a DETACHED racing the ACK nulled the field instead of failing the wait with 92008. Replaced with an event-driven waiter set: awaitSyncCompletion() registers a tracked deferred + one-shot once(SYNCED) listener (whichever sync completes resolves it), and failSyncWaiters(error) rejects all pending waiters on DETACHED/SUSPENDED/FAILED. Mirrors ably-js publishAndApply's once(synced) + reject-on-channel-state pattern, including symmetric listener cleanup; ensureSynced gets the same off()-in-finally hygiene. UTS suite: un-gate the now-passing RTO23e/RTO20e/RTO20e1 tests; adopt the canonical serial helpers (POOL_SERIAL/ackSerial/remoteSerial/ belowAckSerial) from the spec's new Canonical Constants; align counter fixtures with the corrected spec (SI-1: count 0 + createOp initial); single-object RTPO19e2 sync fixture (root retained per RTO5c2a); fix an RTO24c1 test race (seed's own update leaking into the depth-2 listener); rename LiveMapApiTest/LiveCounterApiTest to Internal* to match the renamed spec files. deviations.md: restructure into four actionability groups (open bugs / shared gap / expected typed-SDK adaptations / intentional), remove the fixed RTO23e + RTO20e/e1 entries, reframe RTO18d as an intentional deviation (spec point questioned), and reconcile every cited test name, UTS id and spec point against the suite (audited programmatically). Verified: :uts:runUtsUnitTests liveobjects 183/0, runLiveObjectUnitTests green, checkWithCodenarc/checkstyleMain/checkstyleTest clean.
…Serial; doc typo - objectsClient emitted ATTACHED with the caller's attachedSerial but always followed with an OBJECT_SYNC for "sync1:"; a caller overriding the serial (e.g. "sync2:cursor") would get mismatched sync ids that register as a second new sync (RTO5a2). The sync id is now derived from attachedSerial. No current caller hits the mismatch — latent footgun only; all 183 liveobjects UTS tests unchanged and green. - Fix the dangling ";." in throwIfMissingObjectSubscribeMode's doc.
fix(uts): wait for outbound publish before injecting ACK in RTO20 test The ack-after-echo test raced: increment() dispatches its publish on sequentialScope, so the manually injected ACK for msgSerial 0 could reach ConnectionManager before the publish was pending, where PendingMessages.ack discards it - incFuture never completed and the test hung with UncompletedCoroutinesError (flaky on CI, e.g. runs 29081250922 and 29236093941).
…in-implementation [AIT-1019] feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc
[AIT-1103] fix(liveobjects): Fix UTS unit tests
…overhead of updating the same, going forward it's better to grab context from README.md or generate `index.html` doc file from `README.md`