Skip to content

Implicit attach on channel.objects.getRoot() call#472

Open
lawrence-forooghian wants to merge 3 commits into
mainfrom
AIT-119/implicit-attach-getroot
Open

Implicit attach on channel.objects.getRoot() call#472
lawrence-forooghian wants to merge 3 commits into
mainfrom
AIT-119/implicit-attach-getroot

Conversation

@lawrence-forooghian
Copy link
Copy Markdown
Collaborator

As already implemented in JS in ably/ably-js@9bde15.

Split out of #427 since it's a standalone behaviour.

As already implemented in JS in 9bde15e.

Co-Authored-By: Lawrence Forooghian <lawrence.forooghian@ably.com>
@lawrence-forooghian lawrence-forooghian added the live-objects Related to LiveObjects functionality. label May 12, 2026
@github-actions github-actions Bot temporarily deployed to staging/pull/472 May 12, 2026 20:07 Inactive
@sacOO7
Copy link
Copy Markdown
Collaborator

sacOO7 commented May 18, 2026

Review against ably-js implementation

I've cross-checked this PR against ably-js realtimechannel.ts:1135-1150 (ensureAttached) and realtimeobject.ts:88-101 (get()). The new RTO1e/f line up with the implementation for the four states they enumerate, but there are a couple of gaps worth tightening before merge.

🟠 SUSPENDED state behaviour not addressed

The new RTO1e/f cover INITIALIZED, DETACHED, DETACHING, ATTACHING, and FAILED, but say nothing about SUSPENDED. The implementation's ensureAttached (source) explicitly passes SUSPENDED through without re-attaching:

async ensureAttached(): Promise<void> {
  switch (this.state) {
    case 'attached':
    case 'suspended':
      break;
    case 'initialized':
    case 'detached':
    case 'detaching':
    case 'attaching':
      await this.attach();
      break;
    case 'failed':
    default:
      throw ErrorInfo.fromValues(this.invalidStateError());
  }
}

That's deliberate — a SUSPENDED channel may have local object data that the user can still read while the connection is being re-established. The current RTO1 wording is ambiguous about this case (a reader might assume SUSPENDED falls into "or any other non-attached state" and so gets re-attached, which is wrong).

Suggest adding:

- (RTO1g) If the channel is in the `ATTACHED` or `SUSPENDED` state, proceed without attaching.

…and renumbering / making the relationship between RTO1e/f/g explicit. The simplest framing matches the impl's switch statement:

State Behaviour
ATTACHED, SUSPENDED Pass through; do not re-attach.
INITIALIZED, DETACHED, DETACHING, ATTACHING Implicit attach and wait.
FAILED Throw ErrorInfo 400/90001.

🟠 Behaviour during pending implicit-attach if the channel transitions to FAILED

RTO1e says "implicitly attach the RealtimeChannel and wait for the attach to complete" but doesn't define what happens if the attach attempt itself fails, or if the channel transitions to FAILED during the wait. The impl simply propagates the attach() rejection (realtimechannel.ts:1144 → standard attach() failure semantics).

Worth being explicit:

- (RTO1e1) If the implicit attach fails (e.g. the channel transitions to `FAILED` during the wait), the returned promise MUST reject with an `ErrorInfo` 400/90001.

🟡 RFC 2119 keyword precision

(RTO1f) says "the library should throw" — should this be MUST? The pre-existing (RTO1b) used the same "should" wording, but per CLAUDE.md's writing principles ("Use RFC 2119 keywords precisely"), throwing for a FAILED channel is a hard requirement. Suggest "MUST throw".

⚪ Minor: cross-reference to RTL27 lifecycle

For readers landing on this clause without context, a one-line cross-reference to channel-state transitions (RTL2) and the implicit-attach pattern used elsewhere (e.g. RTP11b for presence.get(), mentioned in the PR description) would help with consistency framing.


These observations correspond to issue B-3 in LIVEOBJECTS_SPEC_REVIEW.md (cross-branch review) and Task B-6 in LIVEOBJECTS_UPDATE_SPEC_ACTIONABLE_PLAN.md.

Copy link
Copy Markdown
Collaborator

@sacOO7 sacOO7 left a comment

Choose a reason for hiding this comment

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

LGTM.
Addressed changes as per review comment and now behaviour is exactly similar to JS code.

@sacOO7 sacOO7 force-pushed the AIT-119/implicit-attach-getroot branch from 969aade to b5d9a12 Compare May 19, 2026 12:53
@github-actions github-actions Bot temporarily deployed to staging/pull/472 May 19, 2026 12:53 Inactive
@sacOO7 sacOO7 force-pushed the AIT-119/implicit-attach-getroot branch from b5d9a12 to 2c9b91b Compare May 19, 2026 13:07
@github-actions github-actions Bot temporarily deployed to staging/pull/472 May 19, 2026 13:07 Inactive
Comment thread specifications/objects-features.md Outdated
- `(RTO1a)` Requires the `OBJECT_SUBSCRIBE` channel mode to be granted per [RTO2](#RTO2)
- `(RTO1b)` If the channel is in the `DETACHED` or `FAILED` state, the library should throw an `ErrorInfo` error with `statusCode` 400 and `code` 90001
- `(RTO1b)` This clause has been replaced by [RTO1e](#RTO1e).
- `(RTO1e)` Performs the *ensure-attached* procedure to make sure the underlying `RealtimeChannel` is in the `ATTACHED` state before proceeding.
Copy link
Copy Markdown
Collaborator

@sacOO7 sacOO7 May 20, 2026

Choose a reason for hiding this comment

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

As per ably-js#ensureAttached method comment description,

   * This method is intended for use by features like Presence or Objects that need to
   * implicitly attach the channel when an operation is called (e.g., `presence.get()` per RTP11b,
   * or `objects.get()`). This guarantees that the corresponding sync sequence will start and
   * that the operation will resolve for callers even if they did not explicitly attach beforehand.
   */

Currently, I have limited scope to objects spec, otherwise RTO1e is supposed to be shared by both presence.get and objects.get. Currently, the spec RTP11b is incomplete for presence.get : (

So, maybe we can extract RTO1e and share it with both presence.get and objects.get
wdyt @lawrence-forooghian @ttypic

Copy link
Copy Markdown
Collaborator

@sacOO7 sacOO7 May 20, 2026

Choose a reason for hiding this comment

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

Updated 0d680ca

Comment thread specifications/objects-features.md Outdated
- `(RTO1a)` Requires the `OBJECT_SUBSCRIBE` channel mode to be granted per [RTO2](#RTO2)
- `(RTO1b)` If the channel is in the `DETACHED` or `FAILED` state, the library should throw an `ErrorInfo` error with `statusCode` 400 and `code` 90001
- `(RTO1b)` This clause has been replaced by [RTO1e](#RTO1e).
- `(RTO1e)` Performs the *ensure-attached* procedure to make sure the underlying `RealtimeChannel` is in the `ATTACHED` state before proceeding.
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

to make sure the underlying RealtimeChannel is in the ATTACHED state before proceeding

This isn't quite true since the procedure indicates success when SUSPENDED, too. Ditto calling it "ensure attached" (I know the name is taken from ably-js but I think it's wrong there too)

And yes, to your other question I think we should refactor the spec to share the same logic between presence and objects (but this shared procedure should go in the main spec, not objects spec)

Copy link
Copy Markdown
Collaborator

@sacOO7 sacOO7 May 20, 2026

Choose a reason for hiding this comment

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

Updated 0d680ca

Comment thread specifications/objects-features.md Outdated
- `(RTO1b)` If the channel is in the `DETACHED` or `FAILED` state, the library should throw an `ErrorInfo` error with `statusCode` 400 and `code` 90001
- `(RTO1b)` This clause has been replaced by [RTO1e](#RTO1e).
- `(RTO1e)` Performs the *ensure-attached* procedure to make sure the underlying `RealtimeChannel` is in the `ATTACHED` state before proceeding.
- `(RTO1e1)` If the channel is in the `INITIALIZED`, `DETACHED`, `DETACHING`, or `ATTACHING` state, implicitly attach the `RealtimeChannel` and wait for the attach to complete
Copy link
Copy Markdown
Collaborator Author

@lawrence-forooghian lawrence-forooghian May 20, 2026

Choose a reason for hiding this comment

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

Can this be more explicit i.e. literally just defer to calling RTL4 RealtimeChannel#attach, and just state that we fail if it fails (and then we don't need the "for example" in RTO1e1a)

Copy link
Copy Markdown
Collaborator

@sacOO7 sacOO7 May 20, 2026

Choose a reason for hiding this comment

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

Fixed 0d680ca

Copy link
Copy Markdown
Collaborator

@sacOO7 sacOO7 May 20, 2026

Choose a reason for hiding this comment

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

Actually, when performing callback-style operations in some languages ( like java channel#attach), it's easy to miss out on error callback where it can be silently omitted, so we need explicit spec point to make it clear.

@github-actions github-actions Bot temporarily deployed to staging/pull/472 May 20, 2026 19:28 Inactive
@sacOO7 sacOO7 force-pushed the AIT-119/implicit-attach-getroot branch from b0638be to 0d680ca Compare May 20, 2026 19:39
@github-actions github-actions Bot temporarily deployed to staging/pull/472 May 20, 2026 19:39 Inactive
…ve-channel` procedure to avoid spec

duplication
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

live-objects Related to LiveObjects functionality.

Development

Successfully merging this pull request may close these issues.

3 participants