Skip to content

Align path-based LiveObjects API spec with ably-js implementation#477

Draft
sacOO7 wants to merge 1 commit into
AIT-30/liveobjects-path-based-api-specfrom
fix/liveobjects-path-spec-based-on-ably-js
Draft

Align path-based LiveObjects API spec with ably-js implementation#477
sacOO7 wants to merge 1 commit into
AIT-30/liveobjects-path-based-api-specfrom
fix/liveobjects-path-spec-based-on-ably-js

Conversation

@sacOO7
Copy link
Copy Markdown
Collaborator

@sacOO7 sacOO7 commented May 18, 2026

Summary

Cross-validation against ably-js commit 3deeee8e surfaced multiple gaps between the path-based LiveObjects API spec and the implementation. This PR applies every spec change that doesn't already live in an in-flight PR on this repo.

Scope is deliberately limited to non-PR-delegated items. Three upstream PRs already cover the BatchContext, implicit-attach, and REST API surfaces, and review comments have been posted there with the corresponding spec gaps; this PR doesn't duplicate that work.

Major additions

  • Path event semanticsRTO24cRTO24e: bubbling vs non-bubbling dispatch rules; identity-change non-bubbling event when a parent MAP_SET overwrites a subscribed path.
  • Multi-parent reference graphRTLO3f, RTLO3g, RTO5c10: parentReferences map maintained on every map mutation, getFullPaths returns all distinct root-to-object paths (cycle-safe), pool rebuild after a sync sequence.
  • Tombstone auto-deregistrationRTLO4b8RTLO4b10: instance listeners receive the tombstone update, then are deregistered; path subscriptions unaffected; listener errors caught and logged per ably-js EventEmitter.callListener.
  • Raw LiveMap/LiveCounter rejectionRTLM20e8: validation rejects raw live-object references in MAP_SET; RTLM20e1 marked replaced.
  • PathObject#unsubscribe / Instance#unsubscribe removedRTPO20, RTINS17 marked deleted; deregistration is exclusively via Subscription.unsubscribe() (matches impl).

Validation tightening

  • Channel-mode + state + echo checks at every PathObject/Instance write wrapper (RTPO15eRTPO18e, RTINS12dRTINS15d) and subscribe wrapper (RTPO19i, RTINS16i, RTPO19h).
  • at() path-string type check (RTPO6e).
  • Instance#get conditional key-type check (RTINS5d).
  • subscribeIterator cancellation contract (RTPO21d, RTINS18d).
  • amount validation on increment/decrement wrappers (RTPO17f/18f, RTINS14e/15e).
  • LiveMap value-type entries null rejection (RTLMV4c rewritten).
  • LiveCounter value-type defaulting simplified (RTLCV4a/b1).

Editorial / non-normative additions

  • Compact memoisation broadened (RTPO13b5, RTPO14a2): cyclic OR shared reference dedup.
  • LiveMap value-type immutability scope clarified (RTLMV3d): shallow only.
  • Brand-equivalent return types on LiveMap.create / LiveCounter.create (RTLMV3e, RTLCV3e).
  • Listener ordering and duplicate-subscription independence (RTO24f/g).
  • Subscription idempotency (SUB2b in features.md).
  • RTPO6f path edge cases (empty segment, empty middle segment, trailing backslash, at(p.path()) round-trip).
  • RTLMV4l/m/n: validation-error order, parallelisable server-time fetches, Number type scope (BigInt-equivalent optional).
  • RTO23e: typed channel.object.get<T>() usage hint.

Spec hygiene

  • RTO11/RTO12 deprecation cross-references remapped from umbrella RTLMV3/RTLCV3 to specific sub-clauses (e.g. RTO11f14aRTLMV4e1, RTO12f7RTLCV4g1); post-publish-lookup sub-clauses (RTO11g/h/i, RTO12g/h/i) marked deleted because the wrap-result-as-LiveObject semantics no longer apply.
  • RFC 2119: lowercase shouldMUST on new-in-branch validation clauses (RTLMV4a/b, RTLCV4a).
  • IDL: named type aliases (Primitive, LiveObjectType, Value, ObjectIdReference); internal count/entries fields on value types; unsubscribe lines removed from PathObject/Instance.

Out of scope (delegated to upstream PRs)

These items were intentionally NOT applied locally because in-flight PRs already cover them. Review comments with detailed cross-references to ably-js are posted on each:

Concern Upstream PR Review comment
BatchContext spec + batch() on PathObject/Instance #471 Initial + follow-up
Implicit attach on RealtimeObject#get #472 Comment
REST objects API #476 Comment

Also out of scope:

  • ably-js code changes (raw LiveMap/Counter rejection in validateKeyValue, cheapRandStrrandomString for nonces, depth-validation tightening). Will be raised separately in ably-js.
  • api-docstrings.md rewrites — deferred to a follow-up PR (large editorial task).

Verification

  • (cd build && npm run lint) → ✓ no duplicate IDs across all three spec files.
  • Every applied clause cross-checked against ably-js src/plugins/liveobjects/ at commit 3deeee8e.

Test plan

  • Reviewer spot-checks 3–5 applied clauses against ably-js source.
  • CI lint passes on the PR.
  • Hugo render check (no flat list-item artefacts in deprecation blocks).
  • Confirm the three out-of-scope PRs land separately with their own review comments addressed.

🤖 Generated with Claude Code

Cross-validation against ably-js commit 3deeee8e surfaced multiple gaps
between the path-based API spec and the implementation. This commit
applies all non-PR-delegated spec changes identified in the actionable
review plan.

Major additions:
- Path event semantics (RTO24c-e): bubbling vs non-bubbling event
  dispatch rules
- Multi-parent reference graph (RTLO3f/g, RTO5c10): parentReferences
  maintenance and getFullPaths for multi-path subscription dispatch;
  post-sync rebuild
- Tombstone auto-deregistration (RTLO4b8-b10): instance listeners
  deregistered after the final tombstone update; path subscriptions
  unaffected
- Raw LiveMap/Counter rejection (RTLM20e8): value-type-only consumption
  in MAP_SET; replaces RTLM20e1
- PathObject/Instance unsubscribe(listener) removed (RTPO20, RTINS17):
  deregistration only via Subscription.unsubscribe()

Validation tightening:
- Channel-mode/state/echo checks on PathObject/Instance write methods
  (RTPO15e-18e, RTINS12d-15d) and subscribe wrappers (RTPO19i, RTINS16i,
  RTPO19h)
- at() path string check (RTPO6e)
- Instance#get conditional key-type check (RTINS5d)
- subscribeIterator cancellation (RTPO21d, RTINS18d)
- amount validation on increment/decrement wrappers (RTPO17f/18f,
  RTINS14e/15e)
- LiveMap value-type entries null rejection (RTLMV4c)
- LiveCounter value-type defaulting simplified (RTLCV4a/b1)

Editorial / non-normative:
- Compact memoization broadened (RTPO13b5, RTPO14a2): cyclic OR shared
  reference dedup
- LiveMap value-type immutability shallow-only (RTLMV3d)
- Brand-equivalent return types on LiveMap.create / LiveCounter.create
  (RTLMV3e, RTLCV3e)
- Listener ordering and duplicate-subscription independence (RTO24f/g)
- Subscription idempotency (SUB2b)
- RTPO6f path-parsing edge cases (empty segment, trailing backslash,
  at(p.path()) round-trip)
- RTLMV4l/m/n: validation-error order, parallelisable server-time
  fetches, Number type scope
- RTO23e: typed channel.object.get<T>() usage hint
- Listener-error isolation (RTLO4b10): aligned with ably-js EventEmitter
  callListener pattern

Spec hygiene:
- RTO11/RTO12 deprecation cross-references remapped to specific
  RTLMV3/4 and RTLCV3/4 sub-clauses; post-publish-lookup sub-clauses
  marked deleted
- RFC 2119: lowercase should -> MUST on new-in-branch validation
  clauses (RTLMV4a/b, RTLCV4a, RTLMV4c)
- IDL: named type aliases (Primitive, LiveObjectType, Value,
  ObjectIdReference); internal value-type fields; unsubscribe lines
  removed from PathObject/Instance

Out of scope (delegated to upstream PRs):
- BatchContext spec + batch() on PathObject/Instance (PR #471)
- Implicit attach on RealtimeObject#get (PR #472)
- REST objects API (PR #476)
- ably-js code changes (separate PRs in ably-js repo)

Verified: build/npm-run-lint passes; cross-checked every applied clause
against ably-js src/plugins/liveobjects at commit 3deeee8e.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `(RTPO17b)` Resolves the path using the path resolution procedure ([RTPO3](#RTPO3)). On failure, throws per [RTPO3c2](#RTPO3c2)
- `(RTPO17c)` If the resolved value is a `LiveCounter`, delegates to `LiveCounter#increment` ([RTLC12](#RTLC12)) with the provided `amount`
- `(RTPO17d)` If the resolved value is not a `LiveCounter`, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 92007
- `(RTPO17e)` Before resolving the path, the wrapper MUST perform the `OBJECT_PUBLISH` channel-mode check ([RTO2](#RTO2)), the channel-state check of [RTLC12c](#RTLC12c), and the `echoMessages` check of [RTLC12d](#RTLC12d)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm a bit confused by this (and similar ones here); if we're already delegating to LiveCounter#increment then why would we need to re-perform its checks?

- `(RTPO6c)` Returns a new `PathObject` with the same `root` and with the parsed segments appended to the current `path` segments
- `(RTPO6d)` This is a convenience for chaining multiple `PathObject#get` calls. For example, `pathObject.at("a.b.c")` is equivalent to `pathObject.get("a").get("b").get("c")`
- `(RTPO6e)` If `path` is not of type `String`, the library MUST throw an `ErrorInfo` error with `statusCode` 400 and `code` 40003
- `(RTPO6f)` (non-normative) Path parsing edge cases:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would this not be better off in the UTS?

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

I'm holding off on reviewing this until you've reviewed it yourself but at a glance it seems like it would definitely benefit from you looking through it and deciding what actually needs changing

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

lawrence-forooghian commented May 18, 2026

The stuff to do with bubbles and parentReferences seems like it might genuinely be necessary because it sounds like core logic (and when I looked at the base PR I didn't fully understand the logic too, see my comments there); I'd suggest focussing on that first

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

Would you mind taking a look at my comments on #427 to see whether there's any overlap with your changes here please?

@sacOO7
Copy link
Copy Markdown
Collaborator Author

sacOO7 commented May 19, 2026

Would you mind taking a look at my comments on #427 to see whether there's any overlap with your changes here please?

Sure, going through liveobject spec PRs to better understand existing context and avoid relevant duplications

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