DX-1211: RealtimeChannel interface docstrings#2243
Conversation
|
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 |
m-hulbert
left a comment
There was a problem hiding this comment.
A few more comments on this one outside of language. There are some things I think make the descriptions more complicated rather than simpler and a few things to tidy up in terms of naming.
| readonly name: string; | ||
| /** | ||
| * An {@link ErrorInfo} object describing the last error which occurred on the channel, if any. | ||
| * An {@link ErrorInfo} object describing the last error which occurred on the channel, if any. This is `null` until an error occurs, for example a transition to `failed` or `suspended`, so guard against `null` despite the declared type; inspect it to diagnose a failed or suspended channel. |
There was a problem hiding this comment.
| * An {@link ErrorInfo} object describing the last error which occurred on the channel, if any. This is `null` until an error occurs, for example a transition to `failed` or `suspended`, so guard against `null` despite the declared type; inspect it to diagnose a failed or suspended channel. | |
| * An {@link ErrorInfo} object describing the last error which occurred on the channel, if any. This is `null` until an error occurs, for example a transition in channel state to `failed` or `suspended`. Guard against `null` despite the declared type and inspect it to diagnose a channel in the failed or suspended state. |
| readonly state: ChannelState; | ||
| /** | ||
| * Optional [channel parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) that configure the behavior of the channel. | ||
| * Optional [channel parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) that configure the behavior of the channel. After the channel attaches this reflects the parameters the server confirmed on the most recent attach, which may differ from those requested via {@link ChannelOptions.params}, and is `undefined` before the channel attaches for the first time, so guard against `undefined` despite the declared type. |
There was a problem hiding this comment.
| * Optional [channel parameters](https://ably.com/docs/realtime/channels/channel-parameters/overview) that configure the behavior of the channel. After the channel attaches this reflects the parameters the server confirmed on the most recent attach, which may differ from those requested via {@link ChannelOptions.params}, and is `undefined` before the channel attaches for the first time, so guard against `undefined` despite the declared type. | |
| * Optional channel parameters that configure the behavior of the channel. After the channel attaches this reflects the parameters the server confirmed on the most recent attach, which may differ from those requested via {@link ChannelOptions.params}. It is `undefined` before the channel attaches for the first time, so guard against `undefined` despite the declared type. |
Appreciate this existed before, but it's odd that we link out to the docs on this and the TypeDoc reference and then the @see link is to some different docs again. I think drop the initial link to docs.
| params: ChannelParams; | ||
| /** | ||
| * An array of {@link ResolvedChannelMode} objects. | ||
| * An array of {@link ResolvedChannelMode} objects reflecting the modes the server granted on the most recent attach, which may differ from the modes requested via {@link ChannelOptions.modes}. The value is `undefined` before the channel attaches for the first time, and is also `undefined` (rather than an empty array) when the server grants no modes, so callers should guard against `undefined` despite the declared type. |
There was a problem hiding this comment.
which may differ from the modes requested via {@link ChannelOptions.modes} how and why?
I'd also remove the brackets and long sentences as per other suggestions.
| modes: ResolvedChannelMode[]; | ||
| /** | ||
| * Deregisters the given listener for the specified event name. This removes an earlier event-specific subscription. | ||
| * Deregisters the given listener for the specified event name, removing an earlier event-specific subscription. This only removes the local listener and does not detach the channel, so messages may continue to arrive for any other registered listeners. |
There was a problem hiding this comment.
I think if we're saying other listeners may still be receiving messages then we should also state that messages will still be streamed to the client if they have permissions, unless they detach maybe?
There was a problem hiding this comment.
Yes I think we need to be very clear here. It does not detach the channel and any registered listeners may receive messages. But the key part is not detaching the channel - messages will still be sent by the server and will be billed accordingly
| unsubscribe(event: string, listener: messageCallback<InboundMessage>): void; | ||
| /** | ||
| * Deregisters the given listener from all event names in the array. | ||
| * Deregisters the given listener from all event names in the array. This only removes the local listeners and does not detach the channel. |
There was a problem hiding this comment.
Is there a reason to only include the additional info on 1 overload?
| history(params: RealtimeHistoryParams | null, callback: StandardCallback<PaginatedResult<InboundMessage>>): never; | ||
| /** | ||
| * Sets the {@link ChannelOptions} for the channel. | ||
| * Sets the {@link ChannelOptions} for the channel. If the channel is already attached or attaching and the new {@link ChannelOptions.modes} or {@link ChannelOptions.params} differ from the current ones, this triggers a live re-attach to apply them, and the returned promise resolves only once the server confirms the new options, rejecting with an {@link ErrorInfo} if the channel detaches or fails in the meantime. Otherwise the new options are stored for the next attach. The promise also rejects with an {@link ErrorInfo} when the supplied options are invalid. |
There was a problem hiding this comment.
This is a very long sentence and it isn't clear what a "live re-attach" is.
| setOptions(options: ChannelOptions): Promise<void>; | ||
| /** | ||
| * Registers a listener for messages with a given event name on this channel. The caller supplies a listener function, which is called each time one or more matching messages arrives on the channel. | ||
| * Registers a listener for messages with a given event name on this channel. The caller supplies a listener function, which is called each time one or more matching messages arrives on the channel. Implicitly attaches the channel unless {@link ChannelOptions.attachOnSubscribe} is `false`. Requires the `subscribe` mode (granted by default unless {@link ChannelOptions.modes} excludes it); if the channel attaches without it the server never delivers messages, so the listener silently never fires and a warning is logged rather than the call rejecting (or it rejects with a hinted {@link ErrorInfo} when {@link ClientOptions.strictMode} is enabled). |
There was a problem hiding this comment.
Mmm, this is a lot of extra info getting into modes here - do you think that's really required?
| subscribe(event: string, listener: messageCallback<InboundMessage>, callback: ErrorCallback): never; | ||
| /** | ||
| * Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. | ||
| * Publishes a single message to the channel with the given event name and payload. Publishing does not attach the channel, so unlike {@link RealtimeChannel.subscribe | `subscribe()`}, {@link RealtimePresence.enter | `enter()`}, and {@link RealtimePresence.get | `get()`} a publish-only client need not attach or subscribe first. |
There was a problem hiding this comment.
I don't think we should be saying unlike X, Y or Z as someone is just looking at this method. If you think we really need to state something, I'd go more along the lines of "the client does not need to attach to the channel before publishing to it", but even that seems redundant to a certain extent.
| whenState(targetState: ChannelState): Promise<ChannelStateChange | null>; | ||
| /** | ||
| * Retrieves the latest version of a specific message by its serial identifier. | ||
| * Retrieves the latest version of a specific message by its serial identifier. Retrievable messages require message interactions (mutable messages) to be enabled for the channel by a channel rule. The supplied serial or {@link Message} must carry a populated `serial`, which is present on a {@link Message} received from a subscribe callback but not on a freshly constructed one; if the serial is missing the promise rejects with an {@link ErrorInfo}. |
There was a problem hiding this comment.
We don't call things "mutable messages", "message interactions" nor "channel rules".
'Retrievable messages' also reads strangely - as does what the subsequent sentence is trying to say. I think you're saying serial only exists if you've received a message via subscribe(), not if you've constructed one which is arguable more confusing to read than what it's trying to solve IMO.
| getMessage(serialOrMessage: string | Message): Promise<Message>; | ||
| /** | ||
| * Publishes an update to an existing message with patch semantics. Non-null `name`, `data`, and `extras` fields in the provided message will replace the corresponding fields in the existing message, while null fields will be left unchanged. | ||
| * Publishes an update to an existing message with patch semantics. Non-null `name`, `data`, and `extras` fields in the provided message will replace the corresponding fields in the existing message, while null fields will be left unchanged. The namespace must have message interactions (updates) enabled by a channel rule, and the supplied `message` must carry the `serial` it was given when published (pass the {@link Message} received in a subscribe callback, not a freshly constructed object); otherwise the promise rejects with an {@link ErrorInfo}. |
There was a problem hiding this comment.
As above (haven't been commenting when a previous comment applies to every overload) but also should Message and Serial be capitalised?
There was a problem hiding this comment.
not serial, no. serial is the field on Message (lowercase in the API)
| * ```ts | ||
| * const result = await channel.history({ limit: 25 }); | ||
| * ``` | ||
| * @see https://ably.com/docs/pub-sub/api/javascript/realtime/realtime-channel#history |
There was a problem hiding this comment.
A general question - do we have a plan for keeping this in sync with the docs to avoid 404s?
There was a problem hiding this comment.
not directly, the pr description highlights that some of these point at ably/docs PR #3400 which is yet to be merged.
I'll create a ticket to track how we can avoid 404s going forwards, likely some CI step
| * {@label WITH_MESSAGE_FILTER} | ||
| * | ||
| * Registers a listener for messages on this channel that match the supplied filter. | ||
| * Registers a listener for messages on this channel that match the supplied filter. Implicitly attaches the channel unless {@link ChannelOptions.attachOnSubscribe} is `false`. Requires the `subscribe` mode (granted by default unless {@link ChannelOptions.modes} excludes it); if the channel attaches without it the server never delivers messages, so the listener silently never fires and a warning is logged rather than the call rejecting (or it rejects with a hinted {@link ErrorInfo} when {@link ClientOptions.strictMode} is enabled). |
There was a problem hiding this comment.
Granted by default IFF the key/token has the necessary capability
There was a problem hiding this comment.
fair! I've moved towards keeping things as simple as possible and just mentioned that subscribe is required
| publish(name: string, data: any, callback: ErrorCallback): never; | ||
| /** | ||
| * If the channel is already in the given state, returns a promise which immediately resolves to `null`. Else, calls {@link EventEmitter.once | `once()`} to return a promise which resolves the next time the channel transitions to the given state. | ||
| * If the channel is already in the given state, returns a promise which immediately resolves to `null`. Else, calls {@link EventEmitter.once | `once()`} to return a promise which resolves with the {@link ChannelStateChange} the next time the channel transitions to the given state. |
There was a problem hiding this comment.
Calling EventEmitter.once is an internal detail, does the consumer need to know how it works?
| whenState(targetState: ChannelState): Promise<ChannelStateChange | null>; | ||
| /** | ||
| * Retrieves the latest version of a specific message by its serial identifier. | ||
| * Retrieves the latest version of a specific message by its serial identifier. Retrievable messages require message interactions (mutable messages) to be enabled for the channel by a channel rule. The supplied serial or {@link Message} must carry a populated `serial`, which is present on a {@link Message} received from a subscribe callback but not on a freshly constructed one; if the serial is missing the promise rejects with an {@link ErrorInfo}. |
There was a problem hiding this comment.
Mutable messages != message interactions - the latter is an unrelated, deprecated feature
| deleteMessage(message: Message, operation?: MessageOperation, options?: PublishOptions): Promise<UpdateDeleteResult>; | ||
| /** | ||
| * Appends data to an existing message. The supplied `data` field is appended to the previous message's data, while all other fields (`name`, `extras`) replace the previous values if provided. | ||
| * Appends data to an existing message. The supplied `data` field is appended to the previous message's data, while all other fields (`name`, `extras`) replace the previous values if provided. Requires message interactions (mutable messages) to be enabled for the channel by a channel rule, and the supplied {@link Message} must carry the `serial` it received from a subscribe callback, otherwise the call rejects with an {@link ErrorInfo}. |
There was a problem hiding this comment.
Ditto here and other places mentioning "message interactions" - its unrelated to this
Apply reviewer suggestions and corrections from m-hulbert and AndyTWF:
- errorReason, params, push, history: apply the reviewers' suggested
wording; drop the inline docs link on params.
- push: state that neither the default nor modular build bundles the
Push plugin and to import it from `ably/push`; fix naming
("push notification", backticked `Push`).
- Remove deprecated/incorrect terms across the message-mutation methods:
"message interactions" and "channel rule" -> "a rule"; "durable
storage" -> "storage". State the populated-serial requirement plainly.
- attach/detach: drop internal EventEmitter references, describe the
emitted state change as prose; add implicit-attach triggers (presence
update()/get(), LiveObjects get()) and the detaching state; note that
detaching stops server delivery while local listeners remain; link
release()/get().
- subscribe: collapse the mode-gate narration to a one-line headline.
- publish: drop the sibling-contrast sentence; unify the verb mood.
- setOptions: remove the "live re-attach" coinage.
- unsubscribe: state the no-detach consequence (server keeps sending,
billed) consistently across all overloads.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Declaration only — no runtime use yet. Documented silent-failure paths will read this option in subsequent commits to gate a hint-carrying throw; the default stays `false` in v2.x. Per DXRFC-022 work item B5 the default flips to `true` in v3 with no per-call opt-out. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The guard at line 73 parenthesised `(state === 'attached' && _mode & flag) === 0` so the `=== 0` compared against `(boolean && number)`. It happened to behave correctly — `false === 0` is `false` so the throw skipped when not attached, and the bitwise result === 0 was correct when attached — but the comparison was structural luck rather than the obvious reading of the predicate. Re-parenthesise to `state === 'attached' && (_mode & flag) === 0`. Also emit an always-on warning log adjacent to the throw so the diagnostic fires in the SDK output even when the caller swallows the throw. No silentFailureLogSuffix here because this throw is unconditional (pre-DXRFC-022) and not strictMode-gated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
A short suffix appended to silent-failure warning logs emitted when clientOptions.strictMode is off, so the reader knows the same path will throw in a future major version. Co-locating on Logger keeps the import surface tight; callers do `Logger.silentFailureLogSuffix()` next to `Logger.logActionNoStrip(...)`. Log-only by design — the suffix is not put into ErrorInfo.hint, because the hint is also shown when the throw fires (strictMode on), where the suffix would be misleading. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a channel was attached without the presence_subscribe mode, the
server never delivers presence members, so presence.get() resolves to
[] regardless of who is actually present. Today there is no signal
distinguishing "no one is present" from "this client cannot see anyone".
This commit detects the case after ensureAttached() populates the
server-granted mode set, then:
- emits an always-on warning log carrying the hint + a suffix telling
the reader that strictMode will throw in a future major version.
- throws ErrorInfo with err.hint when clientOptions.strictMode === true.
Code 93002 sits next to 93001 (annotation_subscribe missing) in the
SDK-internal precondition class. It would also be defensible to use
40160 (server-side capability denied), but the hint-coverage rubric
already pins 40160 to the "no auth options" hint shape, so a second
40160 throw site would either weaken that pin or need a rubric refactor.
Suspended-state and {waitForSync: false} paths return earlier and are
unaffected.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When a channel was attached without the subscribe mode, the server never delivers messages to the listener, so channel.subscribe() appears to succeed but no callback ever fires. This commit closes the gap symmetrically to presence.get() in the previous commit: - After the implicit attach completes (RTL7g), if subscribe mode was not granted by the server, emit an always-on warning log carrying the hint + the future-throw suffix. - One-shot per channel: the warning fires once per attach cycle to keep noise down on long-lived listeners. Reset _silentSubscribeWarned on the ATTACHED message so a channels.release() + re-attach with corrected modes restores signalling. - Throw ErrorInfo (code 93003) when clientOptions.strictMode === true. attachOnSubscribe: false is out of scope — the check requires an attach to have populated _mode. Document this caveat separately if needed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Terser, technical error messages for 93002/93003 - Hints now offer setOptions() or omitting modes + capability check; drop confusing channel.modes note - Keep listener registered on strict-mode throw (matches existing subscribe semantics) - Remove ticket IDs from test names Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…grade The 93001 hint lumped two distinct failure modes under "if the subsequent attach is rejected": a missing namespace rule (Mutable Messages) does reject the attach, but a missing annotation-subscribe capability does NOT - the server silently drops the mode and this same error re-fires. Confirmed by live sandbox traces and the canonical error registry. Reword to key off the symptom the caller observes: attach rejected => enable the namespace rule; attach succeeds but annotations still undelivered => grant the capability. Addresses review feedback that the hint was presumptuous and over-enumerated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… off colliding 93002 Two fixes to the presence.get()/channel.subscribe() missing-mode errors: - Hint correctness (review feedback): the presence hint named a "presence-subscribe" capability that does not exist. Presence delivery is governed by the "subscribe" capability, matching the subscribe-mode hint. - Error-code collision: 93002 is the canonical server code for "namespace needs Mutable Messages" (ably-common errors.json, faqs.ably.com/error-code-93002). presence.get() reused it client-side. Move presence to 91008 (presence block, next to 91005) and channel.subscribe() to 90009 (channel block); 93xxx is the annotations/mutable-messages block. Both codes are new on this unreleased branch, so the renumber is non-breaking. Reserved in ably/ably-common#345. Update the two tests asserting them. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts, failure modes) Apply docstringRules.md to the public Auth interface in ably.d.ts so silent and architectural call-site prerequisites are discoverable at the call site: - revokeTokens: basic-auth (API key, not token) requirement; non-code "revocable tokens enabled on the key" prerequisite, with a feature-page @see. - requestToken: token-issuing prerequisite (authCallback/authUrl/key); callback contract detail (content-types, size flags) offloaded to the @see. - createTokenRequest: a local API key must be available to sign the request. - authorize: re-authenticates the live connection (resolves once the token takes effect on a connected connection), token-issuing prerequisite, and the RSA10a key-immutability rule (authorize() cannot change the API key). - clientId: adds the canonical @see. Folded prose, no numeric error codes; every behavioural method carries one simple @example and every member an @see to the canonical JS API reference. TypeDoc (treatWarningsAsErrors), eslint, and prettier are clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
c81b62a to
75ca7f8
Compare
… side-effects, failure modes) Surface call-site prerequisites and failure modes across the RealtimeChannel public surface per docstringRules.md (terse folded prose + @see companion): - subscribe(): silent missing-`subscribe`-mode trap + strictMode aside (DX-1211 core) - params/errorReason: guard against undefined/null despite the declared type - history(): persistence channel-rule prerequisite; fix "presence members" @param bug - message ops (get/update/delete/append/getMessageVersions): async-reject framing and message-interactions channel-rule prerequisite - push: universal missing-plugin throw kept; presence/annotations left as-is (bundled by the default build) TypeDoc (treatWarningsAsErrors), prettier, and eslint all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apply reviewer suggestions and corrections from m-hulbert and AndyTWF:
- errorReason, params, push, history: apply the reviewers' suggested
wording; drop the inline docs link on params.
- push: state that neither the default nor modular build bundles the
Push plugin and to import it from `ably/push`; fix naming
("push notification", backticked `Push`).
- Remove deprecated/incorrect terms across the message-mutation methods:
"message interactions" and "channel rule" -> "a rule"; "durable
storage" -> "storage". State the populated-serial requirement plainly.
- attach/detach: drop internal EventEmitter references, describe the
emitted state change as prose; add implicit-attach triggers (presence
update()/get(), LiveObjects get()) and the detaching state; note that
detaching stops server delivery while local listeners remain; link
release()/get().
- subscribe: collapse the mode-gate narration to a one-line headline.
- publish: drop the sibling-contrast sentence; unify the verb mood.
- setOptions: remove the "live re-attach" coinage.
- unsubscribe: state the no-detach consequence (server keeps sending,
billed) consistently across all overloads.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bec860c to
ae2344a
Compare
|
Thanks both - @AndyTWF @m-hulbert I've heavily refactored the prompt/workflow this was generated from, and introduced a new review step which covers some language and content style guidelines with your feedback from this PR in mind. Latest commit shows the output of this which I've also checked through. Might still be some stragglers but leaning into this approach so it's easier to apply to all the other docstrings too |
2a86111 to
7906161
Compare
Stacked on #2242 (Auth docstrings, base
DX-1211/auth-docstrings); review/merge that first. Rewrites the JSDoc on theRealtimeChannelpublic surface inably.d.tsperdocstringRules.md: terse folded prose that surfaces call-site prerequisites, side-effects, and failure modes (the DX-1211 silent-failure class), with an@seecompanion link to the canonical reference.Scope
ably.d.tsonly (+135/−29). 19 members rewritten, 2 deliberately left as-is.Highlights
subscribe()(DX-1211 core): documents the silent missing-subscribe-mode trap — the listener never fires, the SDK warn-logs rather than rejecting (or rejects with a hintedErrorInfounderstrictMode). On all four active overloads.params/errorReason: declared non-optional but actuallyundefined/nulluntil first attach/error — now carry a guard-against-undefined/null caveat (parallel tomodes).history(): non-code persistence channel-rule prerequisite; fixes a@paramcopy-paste bug ("presence members" → "messages").getMessage/updateMessage/deleteMessage/appendMessage/getMessageVersions): async reject framing (not synchronous throw) + message-interactions channel-rule prerequisite.push: universal missing-plugin throw kept at the call site;presence/annotationsleft unchanged (bundled by the default build, so the throw is modular-only).Validation
npm run docs(TypeDoctreatWarningsAsErrors),prettier --check, andeslintall pass. A clean TypeDoc build confirms every{@link}resolves and nothing became undocumented.Reviewer notes
@seeanchors follow the kebab-case convention (matching the Auth PR) but are unverified — ably/docs #3400 isn't live yet and@seeis not TypeDoc-validated.@seefor the five message-interaction members was deliberately omitted pending a confirmed slug.🤖 Generated with Claude Code