feat(liveobjects): implement remaining path-based API — parent references, event bubbling etc#1224
Draft
sacOO7 wants to merge 2 commits into
Conversation
…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
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 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 |
PathObject implementation from new API i.e. bubbling events and parentReferenesPath based API i.e. parentReferenes and bubbling events
Path based API i.e. parentReferenes and bubbling events…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Completes the path-based LiveObjects public API in the
liveobjectsKotlin 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-jsliveobjectsplugin 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:
ResolvedValuewrapped the wrong types (public blueprints instead of the internal CRDT objects), every read/write/subscribe method was aTODO(), and update events carried no source message, no tombstone signal and no path information.Changes
Internal graph bridge
ResolvedValueis now a sealed interface over the internal graph:MapRef(InternalLiveMap)/CounterRef(InternalLiveCounter)/Leaf(WireObjectData), resolved per call viaLiveMapEntry.getResolvedValue()(tombstone- and dangling-ref-aware, RTLM5d2).PathSegmentshandles dot-delimited path ↔ segment conversion with escape-aware parsing (RTPO4, RTPO6). Deviation from ably-js:joinescapes 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
ObjectUpdateis a sealed class (NoOp/MapUpdate/CounterUpdate) carrying the sourceWireObjectMessageand atombstoneflag (RTLO4b4). Both managers thread the message through every apply/merge/diff path (RTLM6h, RTLC6h, RTLM23c).Parent references & event bubbling
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);notifyUpdatedfans out to instance listeners, then bubbles to path subscribers viaPathObjectSubscriptionRegister(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).operation == null), and surfaced messages are converted once viatoPublicMessage(PAOM3).Reads, writes and instances
RealtimeObject#get()returns the rootLiveMapPathObjectafter attach + sync (RTO23), bridged from coroutines withsequentialScope.future {}.value()s, map iteration,compactJson()with cycle markers (RTPO13/14).LiveMapValueType/LiveCounterValueType, RTLMV4/RTLCV4): nested create messages first, ownMAP_CREATElast, published together with theMAP_SETin a single protocol message (RTLM20h1).Instancetypes 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-e280bff1ably-js code that upstream has since removed.Testing & validation
PathSegmentsTestcovers the stored-vs-supplied path invariants and the backslash round-trip regression.runUnitTests,runLiveObjectsUnitTests,checkWithCodenarc,checkstyleMain/Testall green; zeroTODOs remain in the module.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.(Number)/(Map)) unchanged, existing keep rules remain valid.Notes for reviewers
uts/build.gradle.ktsbut failures there are pending triage in a follow-up — check the spec anchor before assuming an engine bug.entries()skips dangling references (RTLM11d3a) whilekeys()does not, and the backslash escaping described above.🤖 Generated with Claude Code