Skip to content

feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc#1224

Draft
sacOO7 wants to merge 2 commits into
feature/liveobjects-kotlin-implementationfrom
feature/liveobjects-remaining-kotlin-implementation
Draft

feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc#1224
sacOO7 wants to merge 2 commits into
feature/liveobjects-kotlin-implementationfrom
feature/liveobjects-remaining-kotlin-implementation

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

Completes the path-based LiveObjects public API in the liveobjects Kotlin module. The sync/CRDT engine and serialization layers landed earlier on the base branch; this PR fills in everything between them and the public interfaces: value resolution, reads, writes, identity-bound instances, parent references, and subscription event bubbling. ably-js liveobjects plugin is the source of truth throughout, with spec points (objects-features.md, plus the unmerged typed-SDK points from ably/specification#491) anchored inline as code comments.

What was missing before

The module had a complete sync engine but the path/instance layers were skeletons: ResolvedValue wrapped the wrong types (public blueprints instead of the internal CRDT objects), every read/write/subscribe method was a TODO(), and update events carried no source message, no tombstone signal and no path information.

Changes

Internal graph bridge

  • ResolvedValue is now a sealed interface over the internal graph: MapRef(InternalLiveMap) / CounterRef(InternalLiveCounter) / Leaf(WireObjectData), resolved per call via LiveMapEntry.getResolvedValue() (tombstone- and dangling-ref-aware, RTLM5d2).
  • PathSegments handles dot-delimited path ↔ segment conversion with escape-aware parsing (RTPO4, RTPO6). Deviation from ably-js: join escapes backslashes as well as dots, because here the joined string is the storage and gets re-parsed on every resolution (ably-js stores segment arrays and only renders the escaped string). Without this, a key ending in \ collides with the escaped-dot separator and breaks lookups/subscriptions.

Typed update model

  • ObjectUpdate is a sealed class (NoOp / MapUpdate / CounterUpdate) carrying the source WireObjectMessage and a tombstone flag (RTLO4b4). Both managers thread the message through every apply/merge/diff path (RTLM6h, RTLC6h, RTLM23c).

Parent references & event bubbling

  • Every object tracks its parents (parentReferences, keyed by parent objectId per RTLO3f), maintained at all mutation points and rebuilt wholesale after sync (RTO5c10, ordered before the RTO5c7 notifications).
  • getFullPaths() computes all simple paths from root with cycle safety (RTLO4f); notifyUpdated fans out to instance listeners, then bubbles to path subscribers via PathObjectSubscriptionRegister (RTO24: prefix + depth coverage, most-preferred candidate path, at-most-once per subscription, per-listener error isolation), then tears down instance listeners on tombstone (RTLO4b4c3).
  • Sync-time messages are filtered from public events (operation == null), and surfaced messages are converted once via toPublicMessage (PAOM3).

Reads, writes and instances

  • RealtimeObject#get() returns the root LiveMapPathObject after attach + sync (RTO23), bridged from coroutines with sequentialScope.future {}.
  • All path reads re-resolve on every call (RTPO3) — typed value()s, map iteration, compactJson() with cycle markers (RTPO13/14).
  • Writes evaluate value-type blueprints recursively (LiveMapValueType/LiveCounterValueType, RTLMV4/RTLCV4): nested create messages first, own MAP_CREATE last, published together with the MAP_SET in a single protocol message (RTLM20h1).
  • All 8 Instance types are value-bound (RTINS2a): live map/counter instances delegate to the internal objects; primitives capture the extracted value (binary decoded once, defensively copied out).

Fix found during cross-validation against ably-js history

  • handleStateChange(ATTACHED) now clears buffered operations (RTO4d) and starts a new sync unconditionally (RTO4c). The previous conditional was a port of pre-e280bff1 ably-js code that upstream has since removed.

Testing & validation

  • New PathSegmentsTest covers the stored-vs-supplied path invariants and the backslash round-trip regression.
  • runUnitTests, runLiveObjectsUnitTests, checkWithCodenarc, checkstyleMain/Test all green; zero TODOs remain in the module.
  • Every spec anchor in the module (683 unique points) was verified against objects-features.md / spec PR README: add a note about the push example/test app #491; behavioral markers (boundary comparisons, error codes, skip predicates, message ordering) were cross-checked against ably-js.
  • Proguard: no new reflective entry points; blueprint constructor signatures ((Number) / (Map)) unchanged, existing keep rules remain valid.

Notes for reviewers

  • UTS LiveObjects suites are re-enabled in uts/build.gradle.kts but failures there are pending triage in a follow-up — check the spec anchor before assuming an engine bug.
  • RTTS/typed-SDK spec points reference the unmerged Add typed-SDK LiveObjects API spec section (RTTS1-RTTS10) specification#491; anchors should be re-verified once it merges.
  • Known deliberate deviations from ably-js (each documented at the code site): empty map keys are rejected with 40003 (ably-js accepts them), instance-layer entries() skips dangling references (RTLM11d3a) while keys() does not, and the backslash escaping described above.

🤖 Generated with Claude Code

…ubscriptions and event bubbling

Completes the path-addressed LiveObjects API on top of the sync engine:

- Bridge ResolvedValue (MapRef/CounterRef/Leaf) to the internal graph
  objects so path resolution returns live references
- Replace the untyped ObjectUpdate with a sealed model
  (MapUpdate/CounterUpdate/NoOp) carrying the source objectMessage and
  tombstone flag (RTLO4b4, RTLM18, RTLC11)
- Track parent references on every graph object and derive
  getFullPaths() for event bubbling (RTLO3f, RTLO4f-RTLO4h)
- Implement RTPO3 path resolution and the read APIs: RealtimeObject#get
  (RTO23), PathObject value/exists/instance/compactJson (RTPO8,
  RTPO13/RTPO14)
- Add creation value-type evaluation and the path write APIs
- Add value-bound typed Instances and PathObject#instance (RTINS5,
  RTTS6/RTTS7)
- Add path/instance subscriptions with depth windows and event bubbling
  via PathObjectSubscriptionRegister (RTO24, RTPO19)
- RTO4c fix: always start a new sync sequence on ATTACHED regardless of
  the HAS_OBJECTS flag, matching current ably-js
- Make stored path strings round-trip-safe by escaping backslashes as
  well as dots in PathSegments#join
- Remove dead createMap/createCounter code paths and re-enable the UTS
  liveobjects test suites
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 84b7aaa6-ddf4-4fa7-a9dd-6ed5303ba145

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/liveobjects-remaining-kotlin-implementation

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.

@github-actions github-actions Bot temporarily deployed to staging/pull/1224/features July 3, 2026 09:54 Inactive
@sacOO7 sacOO7 changed the title feat(liveobjects): Implement remaining PathObject implementation from new API i.e. bubbling events and parentReferenes feat(liveobjects): Implement remaining Path based API i.e. parentReferenes and bubbling events Jul 3, 2026
@github-actions github-actions Bot temporarily deployed to staging/pull/1224/javadoc July 3, 2026 09:56 Inactive
@sacOO7 sacOO7 changed the title feat(liveobjects): Implement remaining Path based API i.e. parentReferenes and bubbling events feat(liveobjects): implement remaining path-based API — parent references, event bubbling, reads/writes and instances Jul 3, 2026
@sacOO7 sacOO7 changed the title feat(liveobjects): implement remaining path-based API — parent references, event bubbling, reads/writes and instances feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc Jul 3, 2026
…rrected spec

Proxy session creation failed with HTTP 400 for any rule using a frame-action
match: the uts-proxy's MatchConfig.match.action is a Go string (action name or
numeric string), but the rule builders serialised messageAction as a JSON
number. wsFrameToClientRule/wsFrameToServerRule now send it as a string.

Add withRealTimeout to the shared infra: inside runTest, a bare withTimeout
measures virtual (kotlinx.coroutines.test) time, which fast-forwards while the
test idles - a timeout wrapping a real network await fires instantly. All
real-network waits in integration tests must run on a real-thread dispatcher,
matching the existing awaitState/awaitChannelState/pollUntil helpers.

Rework ObjectsFaultsTest against the corrected source spec
(objects/integration/proxy/objects_faults.md, ably/specification#501):

- bound every object.get() with withRealTimeout instead of virtual-time
  withTimeout (all five tests previously failed instantly)
- RTO7/RTO8: fetch the channel handle once and reuse it - re-calling
  Channels.get() with mode options on an attached channel throws
- RTO20e: exercise the RTO20e1 sequence (mutation in flight while SYNCING,
  then the channel enters FAILED -> 92008/400 with the injected 90000 channel
  error as cause), mirroring ably-js's sync-wait rejection test; the spec's
  original mutate-after-FAILED steps hit the RTO26b precondition (90001)
  instead and were fixed at source
- implement the spec's Common Cleanup: close clients and await CLOSED (with
  the connection-state guard) before tearing down the proxy session

Update proxy spec path references (ObjectsFaultsTest/AuthReauthTest KDocs,
uts-to-kotlin skill) to uts/docs/proxy.md per the relocation in
ably/specification#501; uts/README.md's GitHub URLs are left until that PR
reaches main. See PROXY_UTS_SPEC_FIX.md for the full evidence chain.

All five ObjectsFaultsTest tests pass against the sandbox through uts-proxy.
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.

1 participant