Skip to content

[AIT-836][LiveObjects] Implement path-based LiveObjects public API#1213

Merged
sacOO7 merged 5 commits into
feature/path-based-liveobjects-implementationfrom
feature/path-based-liveobjects-final-public-api
Jun 11, 2026
Merged

[AIT-836][LiveObjects] Implement path-based LiveObjects public API#1213
sacOO7 merged 5 commits into
feature/path-based-liveobjects-implementationfrom
feature/path-based-liveobjects-final-public-api

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Out of scope

  • Default implementation for interface methods ( This will be handled by downstream kotlin classes )

  • Integration with channel.object

    Reason: Current public API only focuses on new path-based liveobjects interface. It's intentionally kept clean and currently doesn't interfare with existing objects interfaces

Summary by CodeRabbit

  • New Features
    • Expanded LiveObjects API: typed LiveMap and LiveCounter support with both path-based navigation and instance-based views
    • Typed primitive & container value support (strings, numbers, booleans, binary, JSON, maps, counters) and value-construction helpers for mutations
    • Async mutation operations with acknowledgement futures (set/remove/increment/decrement)
    • Subscription improvements: listener events, subscription handles, and depth-limited path subscription options

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ee43686-c605-46e4-8ce9-be086a4196c6

📥 Commits

Reviewing files that changed from the base of the PR and between 59a5ecc and 11e87a7.

📒 Files selected for processing (6)
  • lib/src/main/java/io/ably/lib/object/message/ObjectData.java
  • lib/src/main/java/io/ably/lib/object/path/PathObjectSubscriptionOptions.java
  • lib/src/main/java/io/ably/lib/object/path/types/JsonArrayPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/JsonObjectPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/LiveMapPathObject.java
  • lib/src/main/java/io/ably/lib/object/value/LiveMapValue.java
🚧 Files skipped from review as they are similar to previous changes (4)
  • lib/src/main/java/io/ably/lib/object/path/types/JsonArrayPathObject.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectData.java
  • lib/src/main/java/io/ably/lib/object/value/LiveMapValue.java
  • lib/src/main/java/io/ably/lib/object/path/types/LiveMapPathObject.java

Walkthrough

Adds a comprehensive LiveObjects API: core subscription/value types, instance- and path-based view models, message/operation contracts, and write-side value-holder factories for map/counter mutations.

Changes

LiveObjects Type System and API

Layer / File(s) Summary
Core subscription and value type foundation
lib/src/main/java/io/ably/lib/object/Subscription.java, lib/src/main/java/io/ably/lib/object/ValueType.java, lib/src/main/java/io/ably/lib/object/package-info.java
Subscription interface enables listener deregistration and cleanup; ValueType enum defines supported resolved kinds; root package documentation describes the LiveObjects API surface.
Instance-addressed (direct) LiveObjects view and types
lib/src/main/java/io/ably/lib/object/instance/Instance.java, lib/src/main/java/io/ably/lib/object/instance/InstanceListener.java, lib/src/main/java/io/ably/lib/object/instance/InstanceSubscriptionEvent.java, lib/src/main/java/io/ably/lib/object/instance/types/LiveMapInstance.java, lib/src/main/java/io/ably/lib/object/instance/types/LiveCounterInstance.java, lib/src/main/java/io/ably/lib/object/instance/types/*.java, lib/src/main/java/io/ably/lib/object/instance/package-info.java
Instance is an identity-addressed view for resolved objects/primitives; LiveMapInstance/LiveCounterInstance expose typed inspection, mutation (futures), and subscriptions; primitive Instance types provide read-only value access.
Message and operation contracts for object updates
lib/src/main/java/io/ably/lib/object/message/ObjectMessage.java, lib/src/main/java/io/ably/lib/object/message/ObjectOperation.java, lib/src/main/java/io/ably/lib/object/message/ObjectOperationAction.java, lib/src/main/java/io/ably/lib/object/message/MapCreate.java, lib/src/main/java/io/ably/lib/object/message/MapSet.java, lib/src/main/java/io/ably/lib/object/message/MapRemove.java, lib/src/main/java/io/ably/lib/object/message/CounterCreate.java, lib/src/main/java/io/ably/lib/object/message/CounterInc.java, lib/src/main/java/io/ably/lib/object/message/ObjectDelete.java, lib/src/main/java/io/ably/lib/object/message/MapClear.java, lib/src/main/java/io/ably/lib/object/message/ObjectData.java, lib/src/main/java/io/ably/lib/object/message/ObjectsMapEntry.java, lib/src/main/java/io/ably/lib/object/message/ObjectsMapSemantics.java, lib/src/main/java/io/ably/lib/object/message/package-info.java
ObjectMessage is the inbound message entrypoint with metadata and an ObjectOperation; ObjectOperation and ObjectOperationAction enumerate and expose operation-specific payloads; operation payload types and ObjectData model message contents and entries.
Path-addressed (lazy) LiveObjects view and types
lib/src/main/java/io/ably/lib/object/path/PathObject.java, lib/src/main/java/io/ably/lib/object/path/PathObjectListener.java, lib/src/main/java/io/ably/lib/object/path/PathObjectSubscriptionEvent.java, lib/src/main/java/io/ably/lib/object/path/PathObjectSubscriptionOptions.java, lib/src/main/java/io/ably/lib/object/path/types/LiveMapPathObject.java, lib/src/main/java/io/ably/lib/object/path/types/LiveCounterPathObject.java, lib/src/main/java/io/ably/lib/object/path/types/*.java, lib/src/main/java/io/ably/lib/object/path/package-info.java
PathObject provides lazy dot-delimited navigation with best-effort resolution; LiveMapPathObject/LiveCounterPathObject expose hierarchical navigation, reads, write futures, and subscription options including depth limiting; primitive path types are terminal read-only accesses.
Value holders and factories for mutations
lib/src/main/java/io/ably/lib/object/value/LiveCounter.java, lib/src/main/java/io/ably/lib/object/value/LiveMap.java, lib/src/main/java/io/ably/lib/object/value/LiveMapValue.java, lib/src/main/java/io/ably/lib/object/value/package-info.java
LiveCounter and LiveMap are immutable factory holders that reflectively instantiate runtime implementations; LiveMapValue is a typed union with predicates, typed getters, and safe binary handling for map mutation payloads.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🐰 A LiveObjects warren springs to code,
Paths and instances in tidy mode,
Maps and counters, futures in tow,
Subscriptions hum as updates flow,
A rabbit cheers: the API's aglow!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[LiveObjects] Implement path-based LiveObjects public API' accurately summarizes the main change—adding a comprehensive path-based LiveObjects public API surface for Java with multiple new interfaces, enums, and value types.
Docstring Coverage ✅ Passed Docstring coverage is 81.71% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/path-based-liveobjects-final-public-api

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.

@sacOO7 sacOO7 changed the base branch from main to feature/path-based-liveobjects-implementation June 8, 2026 12:41
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/features June 8, 2026 12:42 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/javadoc June 8, 2026 12:43 Inactive
@sacOO7 sacOO7 force-pushed the feature/path-based-liveobjects-final-public-api branch from a2531ed to 0410432 Compare June 8, 2026 12:50
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/features June 8, 2026 12:51 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/javadoc June 8, 2026 12:53 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/features June 9, 2026 10:45 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/javadoc June 9, 2026 10:46 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/features June 10, 2026 12:00 Inactive
@sacOO7 sacOO7 force-pushed the feature/path-based-liveobjects-final-public-api branch from 8264313 to 99d9dd9 Compare June 10, 2026 12:01
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/features June 10, 2026 12:02 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/javadoc June 10, 2026 12:04 Inactive
@sacOO7 sacOO7 marked this pull request as ready for review June 10, 2026 12:15
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/features June 10, 2026 12:15 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1213/javadoc June 10, 2026 12:17 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
lib/src/main/java/io/ably/lib/object/instance/types/BinaryInstance.java (1)

24-24: ⚡ Quick win

Inconsistent annotation style with other primitive instances.

All other primitive instance types (BooleanInstance, StringInstance, NumberInstance, JsonArrayInstance, JsonObjectInstance) place @NotNull on a separate line before the return type, but BinaryInstance uses inline type-use syntax byte @NotNull []. While both are valid, consistency improves maintainability.

♻️ Align with the annotation style used by other primitive instances
     /**
      * Returns the wrapped binary value.
      *
      * <p>Spec: RTINS4 / RTTS10c
      *
      * `@return` the wrapped bytes
      */
-    byte `@NotNull` [] value();
+    `@NotNull`
+    byte[] value();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/main/java/io/ably/lib/object/instance/types/BinaryInstance.java` at
line 24, The annotation placement in BinaryInstance's value() method is
inconsistent—change the type-use form `byte `@NotNull` [] value()` to the same
style used by other primitives by placing `@NotNull` on its own line above the
return type so the signature reads with `@NotNull` on a separate line prior to
`byte[]` in the BinaryInstance interface's value() method to match
BooleanInstance, StringInstance, NumberInstance, JsonArrayInstance, and
JsonObjectInstance.
lib/src/main/java/io/ably/lib/object/message/ObjectData.java (1)

53-60: ⚡ Quick win

Clarify defensive-copy semantics for binary payloads.

Line 60 returns a mutable byte[]; without a copy/immutability contract, callers can mutate shared state if implementations return backing storage.

♻️ Suggested contract hardening
     /**
      * Returns the binary value.
      *
      * <p>Spec: OD2c
      *
-     * `@return` the binary value, or {`@code` null} if not applicable
+     * <p>Implementations should return a defensive copy (or otherwise guarantee
+     * immutability) so callers cannot mutate internal state.
+     *
+     * `@return` the binary value, or {`@code` null} if not applicable
      */
     byte `@Nullable` [] getBytes();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/main/java/io/ably/lib/object/message/ObjectData.java` around lines 53
- 60, The Javadoc for ObjectData.getBytes() is ambiguous about whether the
returned byte[] is a live backing array or a defensive copy; update the contract
in the getBytes() Javadoc of class ObjectData to state explicitly whether
callers may mutate the returned array or not and what implementations must do
(preferably: implementations must return a defensive copy of internal storage
and callers may freely modify the returned array), and mention performance
implications so implementers can optimize (e.g., offer an alternative method
like getBytesView() returning an unmodifiable/readonly view if needed);
reference the getBytes() method and ensure the Javadoc mentions nullability,
copy semantics, and responsibilities for callers and implementers.
lib/src/main/java/io/ably/lib/object/value/LiveMapValue.java (1)

289-306: ⚡ Quick win

Binary array stored and returned without defensive copies.

BinaryValue stores the byte[] reference directly (line 293) and returns it directly (line 305), allowing external code to mutate the array after construction or after calling getAsBinary(). This can lead to unexpected state changes in the LiveMapValue after it has been created or retrieved.

🛡️ Recommended fix: add defensive copies
 BinaryValue(byte `@NotNull` [] value) {
-    this.value = value;
+    this.value = value.clone();
 }

 `@Override`
 public byte `@NotNull` [] getAsBinary() { 
-    return value; 
+    return value.clone(); 
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/main/java/io/ably/lib/object/value/LiveMapValue.java` around lines
289 - 306, BinaryValue stores and exposes the mutable byte[] directly; to
prevent external mutation, make defensive copies: in the BinaryValue(byte[]
value) constructor copy the incoming array (e.g., Arrays.copyOf) into the
private field, and in both getValue() and getAsBinary() return a copy of the
internal array rather than the raw reference; update imports if needed and keep
the field final and type byte[].
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@lib/src/main/java/io/ably/lib/object/path/PathObjectSubscriptionOptions.java`:
- Around line 21-23: The constructor in PathObjectSubscriptionOptions currently
accepts zero and negative depths; update the
PathObjectSubscriptionOptions(Integer depth) constructor to enforce the
documented invariant by validating that if depth is non-null it must be > 0, and
throw an IllegalArgumentException (or similar) when depth <= 0; leave null
accepted as before and assign this.depth only after validation.

In `@lib/src/main/java/io/ably/lib/object/path/types/JsonObjectPathObject.java`:
- Around line 10-13: Update the Javadoc on JsonObjectPathObject to remove the
incorrect reference to “primitive resolution” (since this type represents a
JsonObject, not a primitive): reword the sentence that explains why
PathObject#instance() returns null to say that this is a terminal/object
resolution for JsonObject rather than a primitive resolution, and clarify that
navigation via at(...) is not available because it’s only defined on
LiveMapPathObject while value() and inherited read APIs remain available.

In `@lib/src/main/java/io/ably/lib/object/path/types/LiveMapPathObject.java`:
- Around line 48-53: The Javadoc example incorrectly chains
liveMapPath.get("a").get("b").get("c") even though get(...) returns PathObject,
so update the example to be consistent by either using at(...) for each step
(e.g., liveMapPath.at("a").at("b").at("c")) or show the explicit cast using
PathObject#asLiveMap between get calls (e.g.,
liveMapPath.get("a").asLiveMap().get("b")...), and adjust the surrounding text
to reference LiveMapPathObject, get, at, and PathObject#asLiveMap accordingly.

---

Nitpick comments:
In `@lib/src/main/java/io/ably/lib/object/instance/types/BinaryInstance.java`:
- Line 24: The annotation placement in BinaryInstance's value() method is
inconsistent—change the type-use form `byte `@NotNull` [] value()` to the same
style used by other primitives by placing `@NotNull` on its own line above the
return type so the signature reads with `@NotNull` on a separate line prior to
`byte[]` in the BinaryInstance interface's value() method to match
BooleanInstance, StringInstance, NumberInstance, JsonArrayInstance, and
JsonObjectInstance.

In `@lib/src/main/java/io/ably/lib/object/message/ObjectData.java`:
- Around line 53-60: The Javadoc for ObjectData.getBytes() is ambiguous about
whether the returned byte[] is a live backing array or a defensive copy; update
the contract in the getBytes() Javadoc of class ObjectData to state explicitly
whether callers may mutate the returned array or not and what implementations
must do (preferably: implementations must return a defensive copy of internal
storage and callers may freely modify the returned array), and mention
performance implications so implementers can optimize (e.g., offer an
alternative method like getBytesView() returning an unmodifiable/readonly view
if needed); reference the getBytes() method and ensure the Javadoc mentions
nullability, copy semantics, and responsibilities for callers and implementers.

In `@lib/src/main/java/io/ably/lib/object/value/LiveMapValue.java`:
- Around line 289-306: BinaryValue stores and exposes the mutable byte[]
directly; to prevent external mutation, make defensive copies: in the
BinaryValue(byte[] value) constructor copy the incoming array (e.g.,
Arrays.copyOf) into the private field, and in both getValue() and getAsBinary()
return a copy of the internal array rather than the raw reference; update
imports if needed and keep the field final and type byte[].
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b9e4b17-fc76-48eb-92d3-5620a4a45e55

📥 Commits

Reviewing files that changed from the base of the PR and between 0e5e684 and 59a5ecc.

📒 Files selected for processing (48)
  • lib/src/main/java/io/ably/lib/object/Subscription.java
  • lib/src/main/java/io/ably/lib/object/ValueType.java
  • lib/src/main/java/io/ably/lib/object/instance/Instance.java
  • lib/src/main/java/io/ably/lib/object/instance/InstanceListener.java
  • lib/src/main/java/io/ably/lib/object/instance/InstanceSubscriptionEvent.java
  • lib/src/main/java/io/ably/lib/object/instance/package-info.java
  • lib/src/main/java/io/ably/lib/object/instance/types/BinaryInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/BooleanInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/JsonArrayInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/JsonObjectInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/LiveCounterInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/LiveMapInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/NumberInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/StringInstance.java
  • lib/src/main/java/io/ably/lib/object/instance/types/package-info.java
  • lib/src/main/java/io/ably/lib/object/message/CounterCreate.java
  • lib/src/main/java/io/ably/lib/object/message/CounterInc.java
  • lib/src/main/java/io/ably/lib/object/message/MapClear.java
  • lib/src/main/java/io/ably/lib/object/message/MapCreate.java
  • lib/src/main/java/io/ably/lib/object/message/MapRemove.java
  • lib/src/main/java/io/ably/lib/object/message/MapSet.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectData.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectDelete.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectMessage.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectOperation.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectOperationAction.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectsMapEntry.java
  • lib/src/main/java/io/ably/lib/object/message/ObjectsMapSemantics.java
  • lib/src/main/java/io/ably/lib/object/message/package-info.java
  • lib/src/main/java/io/ably/lib/object/package-info.java
  • lib/src/main/java/io/ably/lib/object/path/PathObject.java
  • lib/src/main/java/io/ably/lib/object/path/PathObjectListener.java
  • lib/src/main/java/io/ably/lib/object/path/PathObjectSubscriptionEvent.java
  • lib/src/main/java/io/ably/lib/object/path/PathObjectSubscriptionOptions.java
  • lib/src/main/java/io/ably/lib/object/path/package-info.java
  • lib/src/main/java/io/ably/lib/object/path/types/BinaryPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/BooleanPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/JsonArrayPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/JsonObjectPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/LiveCounterPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/LiveMapPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/NumberPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/StringPathObject.java
  • lib/src/main/java/io/ably/lib/object/path/types/package-info.java
  • lib/src/main/java/io/ably/lib/object/value/LiveCounter.java
  • lib/src/main/java/io/ably/lib/object/value/LiveMap.java
  • lib/src/main/java/io/ably/lib/object/value/LiveMapValue.java
  • lib/src/main/java/io/ably/lib/object/value/package-info.java

Comment thread lib/src/main/java/io/ably/lib/object/path/PathObjectSubscriptionOptions.java Outdated
- 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())

@ttypic ttypic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@sacOO7 sacOO7 merged commit 4f35df8 into feature/path-based-liveobjects-implementation Jun 11, 2026
14 of 15 checks passed
@sacOO7 sacOO7 deleted the feature/path-based-liveobjects-final-public-api branch June 11, 2026 12:38
@sacOO7 sacOO7 changed the title [LiveObjects] Implement path-based LiveObjects public API [AIT-836][LiveObjects] Implement path-based LiveObjects public API Jun 11, 2026
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