Skip to content

Conversation

@SimonWoolf
Copy link
Member

@SimonWoolf SimonWoolf commented Dec 15, 2025

AIT-94

Summary by CodeRabbit

  • New Features

    • Publish now returns per-message serials; update/delete/append return version info.
    • Message append operation added.
    • Default protocol baseline bumped to v5.
  • Behavior / Bug Fixes

    • Acknowledgements now deliver per-message publish responses to callers.
    • Publish/update/delete/append APIs consistently return result objects with serial/version details.
  • Tests

    • Added realtime and REST tests for publish/update/delete/append (serial/version validation).
    • Removed an old bundling test.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Walkthrough

Adds PublishResult and UpdateDeleteResult types and threads publish/update/delete/append responses through REST and Realtime channel APIs, transport, and queue callbacks; introduces message.append action; bumps protocol version to 5; updates internal transport/queue/connection flow and adds tests for serials, updates, deletes, and appends.

Changes

Cohort / File(s) Summary
Type Declarations & Public API
ably.d.ts
Added PublishResult and UpdateDeleteResult; BatchPublishSuccessResult.serials added; MESSAGE_APPEND and MessageAction extended; channel publish/update/delete/append signatures now return PublishResult / UpdateDeleteResult.
Realtime channel & annotations
src/common/lib/client/realtimechannel.ts, src/common/lib/client/realtimeannotations.ts
publish/sendMessage now return PublishResult; added appendMessage; updateMessage/deleteMessage return UpdateDeleteResult; introduced private sendUpdate helper; annotations now await send results.
REST channel & mixin
src/common/lib/client/restchannel.ts, src/common/lib/client/restchannelmixin.ts
publish/_publish now return PublishResult; added appendMessage; updateMessage/deleteMessage return UpdateDeleteResult; updateDeleteMessage refactored to accept explicit action and always encode a request Message.
Transport & Queue
src/common/lib/transport/protocol.ts, src/common/lib/transport/connectionmanager.ts, src/common/lib/transport/messagequeue.ts, src/common/lib/transport/transport.ts
Introduced PublishCallback (StandardCallback); PendingMessage callbacks now accept publish results; ACK emit includes optional res?: PublishResult[] propagated to completeMessages; removed queue bundling logic.
Protocol message types
src/common/lib/types/protocolmessage.ts
ProtocolMessage gains optional res?: API.PublishResult[].
Message utilities
src/common/lib/types/message.ts
Exported stringifyAction returning API.MessageAction, added encodeAction, and added 'message.append' to actions.
Defaults
src/common/lib/util/defaults.ts
Bumped Defaults.protocolVersion from 4 to 5.
Utilities
src/common/lib/util/utils.ts
Added exported stringifyValues(params: Record<string, any>): Record<string,string>.
Tests & test adjustments
test/realtime/message.test.js, test/realtime/updates-deletes.test.js, test/rest/updates-deletes.test.js, test/realtime/init.test.js, test/rest/http.test.js, test/browser/modular.test.js
Removed skipped bundling test; added realtime and REST tests for publish serials, update/delete/append flows and error cases; updated tests to expect protocol v5 / X-Ably-Version '5'; adjusted content-type interception and added an import of ErrorInfo in one test.
Hooks
src/platform/react-hooks/src/hooks/useChannelAttach.ts
Added ably to useEffect dependency array.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Channel as Channel (Rest|Realtime)
    participant Protocol as Transport/Protocol
    participant Queue as MessageQueue
    participant Backend as Server

    rect rgb(235,248,255)
    Note over App,Backend: Publish (single or batch)
    App->>Channel: publish(messages)
    Channel->>Protocol: send(ProtocolMessage with body)
    Protocol->>Queue: queue(PendingMessage with PublishCallback)
    Queue->>Backend: network send
    Backend-->>Protocol: ACK(serial, count, res[])
    Protocol->>Queue: completeMessages({serial,count}, null, res[])
    Queue->>Queue: invoke each callback(err, publishResponse)
    Channel-->>App: resolve Promise<PublishResult> { serials: [...] }
    end

    rect rgb(255,245,235)
    Note over App,Backend: Update / Delete / Append
    App->>Channel: updateMessage(...) / deleteMessage(...) / appendMessage(...)
    Channel->>Channel: sendUpdate(message, action, op)
    Channel->>Protocol: send(ProtocolMessage action=message.update|.delete|.append)
    Protocol->>Queue: queue(PendingMessage with PublishCallback)
    Queue->>Backend: network send
    Backend-->>Protocol: ACK(serial, count, res[] with version)
    Protocol->>Queue: completeMessages({serial,count}, null, res[])
    Queue->>Queue: invoke callback(err, publishResponse)
    Channel-->>App: resolve Promise<UpdateDeleteResult> { versionSerial: "..." }
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Poem

🐇 I hop through queues with serials in paw,
I nudge, append, update — then listen for awe.
Versions as breadcrumbs across the byte track,
Callbacks return answers and stitch data back.
A rabbit's small patch, tidy, swift — here's the knack!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely describes the main changes: implementing message append, upgrading to protocol v5, and adding publish/update return values plus realtime update/delete operations.
Linked Issues check ✅ Passed The pull request implements the core functionality described in AIT-94 with comprehensive changes to message append operations, protocol v5 support, publish/update return values, and realtime update/delete operations.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the PR objectives. Minor utility additions (stringifyValues) and test infrastructure updates (useChannelAttach, modular exports) support the primary implementation.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch protocol-v5

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 and usage tips.

@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report December 15, 2025 16:46 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/features December 15, 2025 16:46 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/typedoc December 15, 2025 16:46 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ably.d.ts (1)

1451-1465: Public typings for publish/update/delete/append and new MESSAGE_APPEND action look consistent with implementation

  • Adding serials: (string | null)[] to BatchPublishSuccessResult and introducing PublishResult / UpdateDeleteResult matches the new protocol responses and REST/realtime implementations.
  • Updating Channel.publish and RealtimeChannel.publish overloads to return Promise<PublishResult> (and corresponding doc changes) aligns with RestChannel.publish and RealtimeChannel.publish now returning PublishResponse.
  • Exposing updateMessage / deleteMessage / appendMessage as returning Promise<UpdateDeleteResult> fits the new REST and realtime flows, which always return an object with a version field.
  • Adding MessageActions.MESSAGE_APPEND and extending MessageAction with MESSAGE_APPEND dovetails with the new 'message.append' operation used in both REST and realtime paths.

One consideration: the public types narrow serials from optional (in internal PublishResponse) to required (in public PublishResult). The realtime implementation defensively resolves publishResponse || {} when the callback fires, and update/delete operations access serials with optional chaining serials?.[0]. This assumes protocol v5+ always populates serials for successful publish operations—tests confirm it currently does, but if backwards compatibility with older protocol versions or edge cases becomes relevant, the required-type assumption may need revisiting.

🧹 Nitpick comments (3)
src/common/lib/client/restchannelmixin.ts (1)

11-12: Update/delete/append over REST are wired cleanly

The refactor to build a fresh requestMessage, encode it, and PATCH /messages/{serial} while returning a decoded UpdateDeleteResponse (or { version: null }) is sound and keeps the caller’s Message immutable. Only minor nit: the error string “cannot be updated” now also covers delete/append – consider slightly more generic wording if you touch this again.

Also applies to: 93-139

src/common/lib/client/realtimechannel.ts (2)

2-6: Realtime publish now returns protocol publish metadata as intended

Changing publish(...args) and sendMessage to return Promise<PublishResponse> cleanly exposes per-message serials from the transport layer. The publishResponse || {} fallback is safe given that PublishResponse.serials is optional, and keeps callers from having to handle undefined.

If you want stricter guarantees in the future (to better align with the public PublishResult.serials always being present), you could default to { serials: [] } rather than {} when no response is provided.

-        } else {
-          resolve(publishResponse || {});
-        }
+        } else {
+          resolve(publishResponse || { serials: [] });
+        }

Also applies to: 244-287, 493-503


1030-1088: sendUpdate flow is solid but could mirror REST’s request shape more closely

The realtime updateMessage/deleteMessage/appendMessage helpers correctly:

  • Require message.serial.
  • Check channel/connection are publishable.
  • Encode a single MESSAGE with the appropriate action.
  • Return UpdateDeleteResponse using publishResponse.serials?.[0] ?? null.

One small improvement would be to align the constructed Message with the REST path and avoid spreading the entire original message, to reduce risk of sending unintended fields:

-    const updateDeleteMsg = Message.fromValues({
-      ...message,
-      action: action,
-    });
-    if (operation) {
-      updateDeleteMsg.version = operation;
-    }
+    const updateDeleteMsg = Message.fromValues({
+      action,
+      serial: message.serial,
+      name: message.name,
+      data: message.data,
+      extras: message.extras,
+    });
+    if (operation) {
+      updateDeleteMsg.version = operation;
+    }

This would match RestChannelMixin.updateDeleteMessage and keep the wire payload minimal.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 194aed7 and 6600755.

📒 Files selected for processing (15)
  • ably.d.ts (8 hunks)
  • src/common/lib/client/realtimeannotations.ts (1 hunks)
  • src/common/lib/client/realtimechannel.ts (5 hunks)
  • src/common/lib/client/restchannel.ts (4 hunks)
  • src/common/lib/client/restchannelmixin.ts (3 hunks)
  • src/common/lib/transport/connectionmanager.ts (3 hunks)
  • src/common/lib/transport/messagequeue.ts (3 hunks)
  • src/common/lib/transport/protocol.ts (2 hunks)
  • src/common/lib/transport/transport.ts (1 hunks)
  • src/common/lib/types/message.ts (1 hunks)
  • src/common/lib/types/protocolmessage.ts (2 hunks)
  • src/common/lib/util/defaults.ts (1 hunks)
  • test/realtime/message.test.js (0 hunks)
  • test/realtime/updates-deletes.test.js (1 hunks)
  • test/rest/updates-deletes.test.js (8 hunks)
💤 Files with no reviewable changes (1)
  • test/realtime/message.test.js
🧰 Additional context used
🧬 Code graph analysis (10)
test/rest/updates-deletes.test.js (2)
src/common/lib/client/realtimechannel.ts (2)
  • updateMessage (1030-1037)
  • appendMessage (1048-1055)
src/common/lib/client/restchannel.ts (2)
  • updateMessage (172-179)
  • appendMessage (190-197)
src/common/lib/types/message.ts (2)
src/common/lib/types/protocolmessagecommon.ts (1)
  • actions (5-28)
ably.d.ts (1)
  • MessageAction (3572-3578)
src/common/lib/transport/messagequeue.ts (1)
src/common/lib/types/protocolmessage.ts (1)
  • PublishResponse (152-154)
src/common/lib/transport/protocol.ts (1)
src/common/lib/types/protocolmessage.ts (1)
  • PublishResponse (152-154)
src/common/lib/client/realtimeannotations.ts (2)
ably.d.ts (2)
  • Message (3311-3369)
  • Annotation (3374-3429)
src/common/lib/util/utils.ts (1)
  • Properties (479-479)
test/realtime/updates-deletes.test.js (2)
src/common/lib/client/realtimechannel.ts (2)
  • updateMessage (1030-1037)
  • appendMessage (1048-1055)
src/common/lib/client/restchannel.ts (2)
  • updateMessage (172-179)
  • appendMessage (190-197)
src/common/lib/transport/connectionmanager.ts (1)
src/common/lib/transport/protocol.ts (2)
  • PublishCallback (11-11)
  • PendingMessage (13-30)
src/common/lib/client/restchannel.ts (2)
src/common/lib/types/protocolmessage.ts (2)
  • PublishResponse (152-154)
  • UpdateDeleteResponse (156-158)
src/common/types/http.ts (1)
  • RequestBody (26-29)
src/common/lib/client/restchannelmixin.ts (2)
ably.d.ts (2)
  • Message (3311-3369)
  • MessageOperation (3494-3507)
src/common/lib/types/protocolmessage.ts (1)
  • UpdateDeleteResponse (156-158)
src/common/lib/client/realtimechannel.ts (1)
src/common/lib/types/protocolmessage.ts (2)
  • PublishResponse (152-154)
  • UpdateDeleteResponse (156-158)
🪛 GitHub Actions: Lint
src/common/lib/client/realtimechannel.ts

[error] 1033-1033: ESLint: '_params' is defined but never used. (@typescript-eslint/no-unused-vars)

🪛 GitHub Check: lint
src/common/lib/transport/connectionmanager.ts

[failure] 5-5:
'PublishResponse' is defined but never used. Allowed unused vars must match /^_/u


[failure] 1807-1807:
'lastQueued' is assigned a value but never used. Allowed unused vars must match /^_/u

src/common/lib/client/realtimechannel.ts

[failure] 1051-1051:
'_params' is defined but never used


[failure] 1042-1042:
'_params' is defined but never used


[failure] 1033-1033:
'_params' is defined but never used

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-npm-package
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-browser (webkit)
🔇 Additional comments (21)
src/common/lib/util/defaults.ts (1)

94-94: Protocol version upgrade to v5 is correctly implemented.

The change properly updates the protocol version across all layers: REST headers via defaultGetHeaders/defaultPostHeaders, realtime connections via connectionmanager.ts, and connection parameters. No hardcoded v4 references remain in the codebase. The implementation allows version override via the request() method parameter, enabling flexibility for testing.

src/common/lib/types/message.ts (3)

23-30: LGTM! Actions array correctly updated.

The addition of 'message.append' to the actions array aligns with the protocol v5 upgrade and the MessageAction type definition in the public API.


32-34: LGTM! Improved type safety.

The updated return type API.MessageAction provides better type safety while remaining backward compatible.


36-38: LGTM! Useful encoding helper.

The encodeAction function provides the inverse operation to stringifyAction, enabling conversion from MessageAction strings to their numeric indices. The default to 'message.create' is appropriate.

src/common/lib/transport/transport.ts (1)

164-164: LGTM! ACK handler correctly propagates response data.

The addition of message.res as a third argument to the 'ack' event emission enables the transport layer to propagate PublishResponse data, supporting the protocol v5 upgrade for publish/update return values.

src/common/lib/client/realtimeannotations.ts (1)

48-48: LGTM! Explicit await improves error propagation.

The change from return to await for async operations ensures errors are caught and propagated in the current execution context, improving error handling consistency across publish/update/delete flows.

Also applies to: 53-53

src/common/lib/types/protocolmessage.ts (2)

152-158: LGTM! New response types support protocol v5.

The PublishResponse and UpdateDeleteResponse types provide the structure for returning publish/update/delete operation results, enabling per-message serials and version metadata.


184-184: LGTM! ProtocolMessage extended with response field.

The addition of res?: PublishResponse[] enables the protocol message to carry response data for publish operations, supporting the new return value semantics in protocol v5.

test/rest/updates-deletes.test.js (4)

23-55: LGTM! Comprehensive publish serial tests.

The tests correctly validate that publish operations return serials for both single and batch messages, confirming the protocol v5 enhancement.


62-93: LGTM! getMessage tests validate serial-based retrieval.

The tests properly verify getMessage functionality with both string serials and Message objects, aligning with the new serial-based retrieval pattern.


99-181: LGTM! Update and delete operations thoroughly tested.

The tests validate operation metadata (clientId, description, metadata) and version tracking for both update and delete operations. The use of waitFor appropriately handles eventual consistency.


286-341: LGTM! Append operation test validates message concatenation.

The test properly verifies that appendMessage appends data, maintains operation metadata, and that the action is correctly set to 'message.update' after the append completes. The serial-based verification approach is consistent with other tests.

test/realtime/updates-deletes.test.js (3)

23-67: LGTM! Realtime publish serial tests.

The tests correctly validate that publish operations over realtime return serials for both single and batch messages, with proper resource cleanup using realtime.close().


72-171: LGTM! Realtime update and delete operations well-tested.

The tests properly use channel subscriptions to capture update and delete events, validating all aspects of the operation including version metadata, clientId, description, and metadata fields. The promise-based approach correctly handles asynchronous event delivery.


212-280: LGTM! Append test includes reattach verification.

The test thoroughly validates the append operation by first capturing the append event, then reattaching with rewind to verify the full concatenated message. This confirms both the immediate append event and the eventual consistency of the message state.

src/common/lib/transport/messagequeue.ts (1)

43-65: LGTM! Message completion updated to propagate PublishResponse.

The completeMessages method now correctly propagates PublishResponse data to per-message callbacks. The iteration properly maps each response to its corresponding message using the array index.

Note: This changes the callback signature from (err) to (err, publishResponse). Existing callbacks that only expect an error parameter will continue to work due to JavaScript's flexible argument handling.

src/common/lib/transport/connectionmanager.ts (1)

1751-1751: LGTM! Callback signature updated for PublishResponse support.

The send method signature correctly updated to use PublishCallback instead of ErrCallback, enabling the propagation of PublishResponse data through the messaging pipeline.

src/common/lib/transport/protocol.ts (2)

2-3: Publish callback type change is coherent with new response shape

Using PublishCallback = StandardCallback<PublishResponse> and updating PendingMessage.callback keeps the callback contract clear while enabling response metadata. ackRequired gating remains unchanged and appropriate.

Also applies to: 9-12, 15-29


36-42: Confirmed: ack emitter signature properly wired across all implementations

Transport base class correctly emits ack events with (message.msgSerial, message.count, message.res). Protocol listener receives this as (serial, count, res?: PublishResponse[]) and passes res to MessageQueue.completeMessages(), which has the signature (serial, count, err?, res?) and properly uses it at the callback site. Both WebSocketTransport and CometTransport inherit this behavior without override. The nack path correctly omits res since NACK messages don't carry publish responses.

src/common/lib/client/restchannel.ts (1)

19-26: REST publish and update/delete/append result plumbing looks correct

Normalizing the POST response into RestPublishResponse and then returning a bare PublishResponse from _publish is tidy, and the new updateMessage / deleteMessage / appendMessage wrappers cleanly delegate to channelMixin.updateDeleteMessage with the appropriate action and return UpdateDeleteResponse. No issues spotted.

Also applies to: 81-161, 172-197

src/common/lib/client/realtimechannel.ts (1)

505-512: sendPresence/sendState use sendMessage consistently

Presence and objects state now go through sendMessage and await its completion, so they’ll correctly propagate errors from the connection manager while ignoring the publish metadata they don’t need. This keeps all messaging paths uniform.

Also applies to: 514-521

@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report December 15, 2025 16:58 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/features December 15, 2025 16:58 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/typedoc December 15, 2025 16:58 Inactive
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions github-actions bot temporarily deployed to staging/pull/2127/typedoc December 15, 2025 17:30 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report December 15, 2025 17:30 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/common/lib/client/restchannelmixin.ts (2)

93-99: Consider renaming method to reflect append capability.

The method name updateDeleteMessage no longer accurately describes its functionality now that it also handles 'message.append'. Consider renaming to something like updateDeleteAppendMessage or a more generic modifyMessage for clarity.

This is a minor inconsistency and can be deferred if backwards compatibility is a concern.


126-127: Simplify: remove unnecessary variable assignment.

method is assigned but never conditionally changed. Call Resource.patch directly.

-    let method = Resource.patch;
-    const { body, unpacked } = await method<UpdateDeleteResponse>(
+    const { body, unpacked } = await Resource.patch<UpdateDeleteResponse>(
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b28b6c0 and ff35286.

📒 Files selected for processing (8)
  • src/common/lib/client/realtimechannel.ts (5 hunks)
  • src/common/lib/client/restchannel.ts (4 hunks)
  • src/common/lib/client/restchannelmixin.ts (3 hunks)
  • src/common/lib/transport/connectionmanager.ts (3 hunks)
  • src/common/lib/types/protocolmessage.ts (2 hunks)
  • src/platform/react-hooks/src/hooks/useChannelAttach.ts (1 hunks)
  • test/realtime/message.test.js (0 hunks)
  • test/realtime/updates-deletes.test.js (1 hunks)
💤 Files with no reviewable changes (1)
  • test/realtime/message.test.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/realtime/updates-deletes.test.js
🔇 Additional comments (13)
src/platform/react-hooks/src/hooks/useChannelAttach.ts (1)

42-42: LGTM! Proper React hooks compliance.

Adding ably to the dependency array is correct since it's referenced inside the effect (line 39). This ensures the effect re-runs when the Ably instance changes, triggering a re-attachment with the new instance.

src/common/lib/types/protocolmessage.ts (2)

152-158: LGTM! Protocol v5 response types are well-defined.

The new PublishResponse and UpdateDeleteResponse types clearly define the response shapes for publish/update/delete/append operations. The nullable types (string | null and (string | null)[]) appropriately handle cases where values may be absent in protocol responses.


184-184: LGTM! PublishResponse array field appropriately added.

The optional res field correctly allows a ProtocolMessage to carry multiple publish responses, consistent with the protocol v5 upgrade.

src/common/lib/transport/connectionmanager.ts (3)

7-7: LGTM! Callback type import updated for protocol v5.

The import change from ErrCallback to PublishCallback aligns with the protocol v5 upgrade that introduces publish/update/delete return values.


1747-1747: LGTM! Send method signature updated correctly.

The send method callback parameter type correctly changed to PublishCallback, consistent with the protocol v5 changes that enable returning publish responses to callers.


1801-1803: LGTM! Queue method updated and past issue resolved.

The queue method callback parameter correctly changed to PublishCallback. The previously flagged unused variables (lastQueued and maxSize) have been removed, leaving a clean implementation that simply logs and queues the message.

src/common/lib/client/restchannelmixin.ts (2)

11-11: LGTM!

Import aligns with the new return type for updateDeleteMessage.


113-121: Good practice: constructing new Message to avoid mutation.

Creating a new Message via fromValues() instead of mutating the input is the right approach. This prevents unexpected side effects for callers.

src/common/lib/client/restchannel.ts (2)

157-160: Delete operations on potentially missing keys are safe but worth noting.

The code handles both unpacked and non-unpacked responses correctly, defaulting to an empty object. The delete operations on channel and messageId are safe even if these keys don't exist, which is appropriate here since these fields are being intentionally stripped from the RestPublishResponse before returning a PublishResponse.


172-197: LGTM! Clean delegation pattern for update/delete/append operations.

All three methods (updateMessage, deleteMessage, appendMessage) follow a consistent delegation pattern to updateDeleteMessage, appropriately differentiating operations via the action parameter. The return type changes to Promise<UpdateDeleteResponse> align with the PR objectives.

src/common/lib/client/realtimechannel.ts (3)

493-521: LGTM! sendMessage propagates PublishResponse correctly.

The sendMessage method now correctly returns Promise<PublishResponse> (line 493) and defaults to an empty object if the response is undefined (line 499). The sendPresence and sendState methods appropriately consume the response via await without exposing it to their callers, maintaining their Promise<void> return types.


1030-1052: LGTM! Lint issue resolved and clean delegation pattern.

The previously reported @typescript-eslint/no-unused-vars issue with the _params parameter has been resolved by removing it from the signatures. All three methods (updateMessage, deleteMessage, appendMessage) now follow a consistent pattern, delegating to the new sendUpdate helper with appropriate action strings.


1054-1085: Implementation is sound.

Verified that PublishResponse.serials is correctly defined as an optional array of (string | null)[] in the codebase (src/common/lib/types/protocolmessage.ts:153). The code correctly:

  • Uses optional chaining ?. to handle undefined serials
  • Accesses [0] appropriately for the single message being sent
  • Uses nullish coalescing ?? null to handle null elements (which occur when messages are discarded due to conflation rules)

The implementation matches the documented behavior that serials is an array with 1:1 correspondence to published messages.

@github-actions github-actions bot temporarily deployed to staging/pull/2127/features December 15, 2025 18:03 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report December 15, 2025 18:03 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/typedoc December 15, 2025 18:03 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/common/lib/client/restchannelmixin.ts (1)

121-121: Use const instead of let for method.

The method variable is never reassigned, so it should be declared with const for better code clarity and to prevent accidental reassignment.

Apply this diff:

-    let method = Resource.patch;
+    const method = Resource.patch;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ff35286 and 704a9ad.

📒 Files selected for processing (1)
  • src/common/lib/client/restchannelmixin.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/common/lib/client/restchannelmixin.ts (3)
ably.d.ts (1)
  • Message (3311-3369)
src/common/lib/types/protocolmessage.ts (1)
  • UpdateDeleteResponse (156-158)
src/common/lib/types/basemessage.ts (1)
  • CipherOptions (13-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-npm-package
🔇 Additional comments (4)
src/common/lib/client/restchannelmixin.ts (4)

11-11: LGTM!

The import of UpdateDeleteResponse correctly supports the new return type for the updateDeleteMessage method.


113-119: LGTM!

Creating a new Message via Message.fromValues instead of mutating the input message is good practice. The encoding flow is clean and consistent.


132-134: LGTM!

The response handling with a fallback to { version: null } is appropriate defensive coding, ensuring the method always returns a valid UpdateDeleteResponse even if the server response body is empty or null.


93-99: No type compatibility issues exist. The method signature change improves clarity by using explicit action parameter and explicit return type. The action parameter correctly maps to API.MessageAction, and the MessageOperation argument is structurally compatible with the Message.version property of type API.MessageVersion.

@SimonWoolf SimonWoolf requested review from ttypic and removed request for lawrence-forooghian December 18, 2025 09:43
@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report December 18, 2025 11:04 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/features December 18, 2025 11:04 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/typedoc December 18, 2025 11:04 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
src/common/lib/client/restchannelmixin.ts (1)

120-121: Consider inlining the method variable.

The method variable is assigned but only used once immediately after. Since all update/delete/append operations now use PATCH, this could be simplified.

🔎 Suggested simplification:
-    let method = Resource.patch;
-    const { body, unpacked } = await method<API.UpdateDeleteResult>(
+    const { body, unpacked } = await Resource.patch<API.UpdateDeleteResult>(
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 704a9ad and 1067c51.

📒 Files selected for processing (8)
  • ably.d.ts (8 hunks)
  • src/common/lib/client/realtimechannel.ts (5 hunks)
  • src/common/lib/client/restchannel.ts (4 hunks)
  • src/common/lib/client/restchannelmixin.ts (2 hunks)
  • src/common/lib/transport/messagequeue.ts (3 hunks)
  • src/common/lib/transport/protocol.ts (2 hunks)
  • src/common/lib/types/protocolmessage.ts (1 hunks)
  • test/realtime/updates-deletes.test.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/realtime/updates-deletes.test.js
  • src/common/lib/types/protocolmessage.ts
🧰 Additional context used
🧬 Code graph analysis (3)
src/common/lib/transport/messagequeue.ts (1)
ably.d.ts (1)
  • PublishResult (3512-3518)
src/common/lib/client/restchannelmixin.ts (1)
src/common/lib/types/basemessage.ts (1)
  • CipherOptions (13-24)
src/common/lib/client/restchannel.ts (2)
ably.d.ts (3)
  • PublishResult (3512-3518)
  • Message (3311-3369)
  • MessageOperation (3494-3507)
src/common/types/http.ts (1)
  • RequestBody (26-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-npm-package
🔇 Additional comments (19)
src/common/lib/transport/messagequeue.ts (1)

43-65: LGTM! The indexed iteration correctly correlates publish responses with messages.

The refactored loop properly passes publishResponse (if available) from the res array to each message's callback by index. This aligns with the PublishCallback type signature in protocol.ts.

One consideration: the code assumes res array (when present) has at least as many elements as completeMessages. If the server ever returns fewer elements, res?.[i] would be undefined, which is safely handled by the optional chaining. This appears intentional given the PublishResult.serials can contain null entries per the type definition.

src/common/lib/transport/protocol.ts (2)

9-12: LGTM! Clean type definition for PublishCallback.

The PublishCallback type alias correctly wraps API.PublishResult in StandardCallback, providing proper typing for the callback chain. The import of both StandardCallback and ErrCallback is appropriate since ErrCallback is still used in onceIdle.


41-51: LGTM! Consistent propagation of publish results through the ack flow.

The changes correctly thread the res parameter from the transport's ack event through onAck to completeMessages. The optional typing (res?: API.PublishResult[]) appropriately handles cases where the response may not be present.

src/common/lib/client/restchannelmixin.ts (2)

112-118: LGTM! Clean message construction avoiding mutation of user's input.

Creating a new Message via fromValues and then assigning action and version properties correctly avoids mutating the original message object passed by the user. This is a good practice for API design.


131-132: LGTM! Safe fallback for missing response body.

The fallback { versionSerial: null } correctly handles cases where the response body might be empty or undefined, matching the UpdateDeleteResult interface contract where versionSerial can be null when a message was superseded.

src/common/lib/client/restchannel.ts (3)

24-25: LGTM! Clean type extension for internal response handling.

The RestPublishResponse type correctly extends API.PublishResult with optional internal fields (channel, messageId) that are stripped before returning to the caller.


140-161: LGTM! Response handling correctly decodes and sanitizes the publish result.

The implementation properly:

  1. Decodes the response body based on format (binary/JSON)
  2. Falls back to an empty object if body is undefined
  3. Strips internal-only fields (channel, messageId) before returning

The delete operations mutate the decoded object, but since this is a freshly decoded object (not user-provided), this is acceptable.


191-198: LGTM! New appendMessage method follows the established pattern.

The appendMessage implementation correctly delegates to updateDeleteMessage with the 'message.append' action, maintaining consistency with updateMessage and deleteMessage.

src/common/lib/client/realtimechannel.ts (4)

240-283: LGTM! Publish flow correctly returns PublishResult.

The updated publish method properly:

  1. Returns Promise<API.PublishResult>
  2. Propagates the response from sendMessage
  3. Provides a safe fallback { serials: [] } when response is undefined

490-500: LGTM! sendMessage correctly typed for optional PublishResult.

The return type Promise<API.PublishResult | undefined> correctly reflects that not all protocol messages (e.g., ATTACH, DETACH) return a publish result. The callback parameter publishResponse is properly propagated.


1027-1040: LGTM! Update/delete/append methods correctly delegate to sendUpdate.

The refactored methods are cleaner than the previous implementation:

  • Removed the unused _params parameter (addressing the past lint issue)
  • All three methods consistently delegate to sendUpdate with the appropriate action
  • Return type Promise<API.UpdateDeleteResult> matches the API contract

1042-1073: LGTM! sendUpdate implements realtime update/delete/append correctly.

The private sendUpdate method properly:

  1. Validates message serial presence with a clear error message
  2. Checks publishable state before proceeding
  3. Constructs a new Message to avoid mutating the input
  4. Encodes and wraps in a protocol message
  5. Extracts versionSerial from the first serial in the response

Minor observation: Line 1072 uses optional chaining (publishResponse?.serials?.[0]) which safely handles edge cases where the response or serials array might be missing.

ably.d.ts (7)

1460-1464: LGTM: Well-documented serials field addition

The addition of the serials array to BatchPublishSuccessResult is consistent with the new PublishResult interface and properly documents the 1:1 mapping to published messages, including the null case for conflated messages.


3509-3529: LGTM: Clean and well-documented result interfaces

The new PublishResult and UpdateDeleteResult interfaces provide clear return types for the protocol v5 publish and update/delete operations. The nullable fields are appropriately documented for edge cases (conflation and superseded updates).


3561-3578: LGTM: MESSAGE_APPEND properly integrated

The new MESSAGE_APPEND action type is properly added to both the MessageActions namespace and the MessageAction union type, following the existing pattern. The documentation clearly explains the append semantics.


2807-2826: Breaking change: Publish methods now return PublishResult

The publish method overloads now return Promise<PublishResult> instead of Promise<void>. This is a breaking change to the public API that allows callers to access the serials of published messages.

This appears intentional for the protocol v5 upgrade, but please confirm:

  1. Is this a documented breaking change for v5?
  2. Are there migration notes for users upgrading from v4?

2848-2878: LGTM: Well-designed message mutation API

The new updateMessage, deleteMessage, and appendMessage methods provide a consistent API for message mutations. The documentation clearly explains:

  • Patch semantics for update/delete (non-null fields replace)
  • Append semantics (data appended, other fields replace)
  • Return type includes versionSerial for tracking

The API design is clean and consistent across all three operations.


3079-3095: LGTM: Realtime publish methods consistent with REST

The RealtimeChannel.publish overloads are updated consistently with the Channel interface, all returning Promise<PublishResult>. This maintains consistency between REST and Realtime APIs.


3117-3147: LGTM: Realtime message mutations match REST interface

The updateMessage, deleteMessage, and appendMessage methods on RealtimeChannel are perfectly consistent with the Channel interface, maintaining API parity between REST and Realtime clients.

@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report December 29, 2025 14:41 Inactive
lawrence-forooghian added a commit to ably/specification that referenced this pull request Jan 6, 2026
Missed in abf2a71. Copied from [1] at faf7497.

[1] ably/ably-js#2127
Copy link
Collaborator

@ttypic ttypic left a comment

Choose a reason for hiding this comment

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

LGTM

requiring protocolv5 acks.
also add the corresponding publish response for publish().
Not very useful, not done by any other sdks, and would be annoying to
implement with the new ack protocol (handling res in the multicaster),
so just removing it.
psdr5: server always sends serials, it's up to us to extract the version
if it's an update
the code was intercepting it to return nothing, but expecting the
publish call to still work without error..?
@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report January 6, 2026 17:32 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/features January 6, 2026 17:32 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/typedoc January 6, 2026 17:32 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/bundle-report January 6, 2026 17:36 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/features January 6, 2026 17:36 Inactive
@github-actions github-actions bot temporarily deployed to staging/pull/2127/typedoc January 6, 2026 17:36 Inactive
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/common/lib/client/restchannel.ts (1)

140-162: Consider validation of empty response scenario.

The decoding logic properly handles both unpacked and packed responses and correctly removes extraneous channel and messageId fields from the final result.

However, the fallback to an empty object on line 158 could potentially mask server errors:

const decoded =
  (unpacked ? body : Utils.decodeBody<RestPublishResponse>(body, client._MsgPack, format)) ||
  ({} as RestPublishResponse);

If the server returns null or undefined, this would return {} which doesn't match the PublishResult interface (missing required serials property). While the tests would catch this, it might be clearer to throw an error or at least ensure a valid shape.

🔎 Alternative approach with validation
 const decoded =
   (unpacked ? body : Utils.decodeBody<RestPublishResponse>(body, client._MsgPack, format)) ||
-  ({} as RestPublishResponse);
+  ({ serials: [] } as RestPublishResponse);
 delete decoded['channel'];
 delete decoded['messageId'];
 return decoded;

Or throw an error if the response is unexpectedly empty:

 const decoded =
-  (unpacked ? body : Utils.decodeBody<RestPublishResponse>(body, client._MsgPack, format)) ||
-  ({} as RestPublishResponse);
+  unpacked ? body : Utils.decodeBody<RestPublishResponse>(body, client._MsgPack, format);
+if (!decoded) {
+  throw new ErrorInfo('Empty response from server', 50000, 500);
+}
 delete decoded['channel'];
 delete decoded['messageId'];
 return decoded;
src/common/lib/client/realtimechannel.ts (1)

1035-1060: Consider renaming parameter for consistency.

The implementation uses params as the parameter name, while the type definition in ably.d.ts uses options. For better documentation and code consistency, consider renaming the parameter to match:

Optional refactor for parameter naming
 async updateMessage(
   message: Message,
   operation?: API.MessageOperation,
-  params?: Record<string, any>,
+  options?: Record<string, any>,
 ): Promise<API.UpdateDeleteResult> {
   Logger.logAction(this.logger, Logger.LOG_MICRO, 'RealtimeChannel.updateMessage()', 'channel = ' + this.name);
-  return this.sendUpdate(message, 'message.update', operation, params);
+  return this.sendUpdate(message, 'message.update', operation, options);
 }

 async deleteMessage(
   message: Message,
   operation?: API.MessageOperation,
-  params?: Record<string, any>,
+  options?: Record<string, any>,
 ): Promise<API.UpdateDeleteResult> {
   Logger.logAction(this.logger, Logger.LOG_MICRO, 'RealtimeChannel.deleteMessage()', 'channel = ' + this.name);
-  return this.sendUpdate(message, 'message.delete', operation, params);
+  return this.sendUpdate(message, 'message.delete', operation, options);
 }

 async appendMessage(
   message: Message,
   operation?: API.MessageOperation,
-  params?: Record<string, any>,
+  options?: Record<string, any>,
 ): Promise<API.UpdateDeleteResult> {
   Logger.logAction(this.logger, Logger.LOG_MICRO, 'RealtimeChannel.appendMessage()', 'channel = ' + this.name);
-  return this.sendUpdate(message, 'message.append', operation, params);
+  return this.sendUpdate(message, 'message.append', operation, options);
 }

And update the sendUpdate signature accordingly.

Note: The Record<string, any> type in the implementation is more permissive than the PublishOptions type in the definition, but since line 1089 calls Utils.stringifyValues(), the actual behavior should be safe.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between faf7497 and 77e76e0.

📒 Files selected for processing (20)
  • ably.d.ts
  • src/common/lib/client/realtimeannotations.ts
  • src/common/lib/client/realtimechannel.ts
  • src/common/lib/client/restchannel.ts
  • src/common/lib/client/restchannelmixin.ts
  • src/common/lib/transport/connectionmanager.ts
  • src/common/lib/transport/messagequeue.ts
  • src/common/lib/transport/protocol.ts
  • src/common/lib/transport/transport.ts
  • src/common/lib/types/message.ts
  • src/common/lib/types/protocolmessage.ts
  • src/common/lib/util/defaults.ts
  • src/common/lib/util/utils.ts
  • src/platform/react-hooks/src/hooks/useChannelAttach.ts
  • test/browser/modular.test.js
  • test/realtime/init.test.js
  • test/realtime/message.test.js
  • test/realtime/updates-deletes.test.js
  • test/rest/http.test.js
  • test/rest/updates-deletes.test.js
💤 Files with no reviewable changes (1)
  • test/realtime/message.test.js
🚧 Files skipped from review as they are similar to previous changes (7)
  • src/common/lib/types/message.ts
  • test/browser/modular.test.js
  • test/realtime/init.test.js
  • src/common/lib/types/protocolmessage.ts
  • test/realtime/updates-deletes.test.js
  • src/common/lib/client/restchannelmixin.ts
  • src/common/lib/util/defaults.ts
🧰 Additional context used
🧬 Code graph analysis (7)
src/common/lib/util/utils.ts (1)
scripts/processPrivateApiData/dto.ts (1)
  • Record (53-53)
src/common/lib/transport/messagequeue.ts (1)
ably.d.ts (1)
  • PublishResult (3000-3006)
test/rest/updates-deletes.test.js (2)
src/common/lib/client/realtimechannel.ts (2)
  • updateMessage (1035-1042)
  • appendMessage (1053-1060)
src/common/lib/client/restchannel.ts (2)
  • updateMessage (173-180)
  • appendMessage (191-198)
src/common/lib/client/realtimeannotations.ts (2)
ably.d.ts (2)
  • Message (2799-2857)
  • Annotation (2862-2917)
src/common/lib/util/utils.ts (1)
  • Properties (483-483)
src/common/lib/client/restchannel.ts (2)
ably.d.ts (4)
  • PublishResult (3000-3006)
  • Message (2799-2857)
  • MessageOperation (2982-2995)
  • UpdateDeleteResult (3011-3017)
src/common/types/http.ts (1)
  • RequestBody (26-29)
src/common/lib/transport/protocol.ts (2)
ably.d.ts (1)
  • PublishResult (3000-3006)
src/common/lib/transport/messagequeue.ts (1)
  • count (15-17)
src/common/lib/transport/connectionmanager.ts (1)
src/common/lib/transport/protocol.ts (2)
  • PublishCallback (12-12)
  • PendingMessage (14-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-npm-package
🔇 Additional comments (23)
src/platform/react-hooks/src/hooks/useChannelAttach.ts (1)

42-42: LGTM! Correct dependency array fix.

Adding ably to the dependency array ensures the effect re-runs when the Ably client instance changes, preventing stale closures and ensuring correct attachment behavior.

src/common/lib/util/utils.ts (1)

248-250: LGTM! Clean utility function.

The stringifyValues function provides a straightforward way to convert all record values to strings. The implementation using Object.fromEntries and String(v) is clear and appropriate.

test/rest/http.test.js (1)

37-37: LGTM! Protocol version update.

The test correctly reflects the protocol version bump to v5, consistent with the broader PR changes.

src/common/lib/client/realtimeannotations.ts (2)

48-48: LGTM! Explicit await improves clarity.

Changing from return to await makes it explicit that the operation completes before the function returns. Since both publish and delete are async functions returning Promise<void>, this change has no functional impact but improves code readability.


53-53: LGTM! Consistent async/await pattern.

The change from return to await aligns with the pattern used in the publish method above, maintaining consistency across the annotation publishing flow.

src/common/lib/transport/transport.ts (1)

164-164: All 'ack' event listeners properly handle the new third parameter.

Verification found one 'ack' event listener at src/common/lib/transport/protocol.ts:41, which correctly accepts the new res parameter with proper typing res?: API.PublishResult[]. The corresponding onAck method at line 49 also properly handles and propagates this parameter to messageQueue.completeMessages().

test/rest/updates-deletes.test.js (3)

23-55: LGTM! Good test coverage for publish serials.

The tests properly validate that:

  • Single message publish returns a serials array with one element
  • Batch publish returns multiple serials corresponding to the number of messages
  • Each serial is a string

67-68: LGTM! Proper use of publish result serials.

All tests have been correctly updated to destructure and use the serials returned from publish operations. The pattern is consistent throughout the test suite.


286-341: LGTM! Comprehensive test coverage for appendMessage.

The tests properly validate:

  • appendMessage returns an UpdateDeleteResult with versionSerial
  • String data is concatenated correctly ('Hello' + ' World' = 'Hello World')
  • Operation metadata (clientId, description, metadata) is preserved in the version
  • Error handling when serial is missing (error code 40003)
src/common/lib/transport/messagequeue.ts (1)

48-88: LGTM! Proper threading of PublishResult to callbacks.

The implementation correctly:

  • Accepts an optional res parameter for backwards compatibility
  • Uses optional chaining res?.[i] to safely index into the results array
  • Passes the corresponding PublishResult to each message callback

The 1:1 correspondence between messages and results is maintained by the caller (Protocol.onAck), which is the correct design.

src/common/lib/transport/protocol.ts (1)

9-52: LGTM! Clean type updates for PublishResult support.

The changes properly:

  • Define PublishCallback as StandardCallback<API.PublishResult>
  • Update PendingMessage.callback type from ErrCallback to PublishCallback
  • Thread the res parameter through the ACK flow from transport to message queue
  • Maintain backwards compatibility with optional res parameter

The type consistency is maintained throughout the protocol layer.

src/common/lib/transport/connectionmanager.ts (1)

1747-1804: LGTM! Proper callback type updates and cleanup.

The changes correctly:

  • Update send and queue method signatures to use PublishCallback instead of ErrCallback
  • Align with the protocol layer's PublishResult support
  • Resolve the previously flagged unused variables issue (lastQueued and maxSize have been removed)

The simplified queue implementation is cleaner and correctly constructs a new PendingMessage with the callback.

src/common/lib/client/restchannel.ts (2)

173-198: LGTM! Consistent API for message operations.

The update, delete, and append methods follow a consistent pattern:

  • All return Promise<API.UpdateDeleteResult> with the versionSerial of the resulting operation
  • All delegate to client.rest.channelMixin.updateDeleteMessage with the appropriate operation type ('message.update', 'message.delete', 'message.append')
  • All include proper logging

The newly added appendMessage method follows the same design as the existing operations.


24-24: LGTM! Appropriate internal type for server response.

The RestPublishResponse type correctly models the server response format which includes PublishResult fields plus optional channel and messageId fields that need to be stripped before returning to the caller.

ably.d.ts (5)

1470-1474: LGTM! Well-documented new field.

The serials field addition is consistent with the new PublishResult interface and properly documents the 1:1 correspondence with published messages and the null case for conflated messages.


2325-2344: LGTM! Consistent protocol v5 upgrade.

The return type changes from Promise<void> to Promise<PublishResult> are correctly applied across all three publish() overloads with updated documentation.


2376-2384: LGTM! Clean addition of appendMessage with consistent API.

The new appendMessage() method follows the same pattern as updateMessage() and deleteMessage(), with clear documentation of the append semantics (data appended, other fields replaced).


2997-3017: LGTM! Well-designed result interfaces.

The new PublishResult and UpdateDeleteResult interfaces are cleanly designed with appropriate nullable types and clear documentation of when null values occur.


3049-3066: LGTM! MESSAGE_APPEND properly integrated.

The new MESSAGE_APPEND action is correctly added to both the MessageActions namespace and the MessageAction union type with clear documentation.

src/common/lib/client/realtimechannel.ts (4)

498-508: LGTM! Clean Promise wrapper.

The sendMessage() method correctly wraps the callback-based connectionManager.send() and propagates the publishResponse containing serials.


510-526: LGTM! Clean async/await conversion.

The sendPresence() and sendState() methods correctly use async/await and appropriately don't use the return value since presence and state messages don't need publish results.


1062-1093: LGTM! Well-structured update/delete/append implementation.

The private sendUpdate() helper cleanly implements the common logic for all three operations:

  • Validation: Properly checks for message serial and publishable state
  • Error message: Clear guidance about enabling the feature in dashboard (line 1070)
  • Type mapping: Correctly maps MessageOperation to MessageVersion field (line 1081)
  • Serial extraction: Safely extracts the version serial with null fallback (line 1092)

The implementation correctly handles the single-message nature of these operations by extracting the first serial from the response.


240-292: The implementation looks correct. Utils.isObject() is implemented using Object.prototype.toString.call(), which returns '[object Object]' for plain objects but '[object Array]' for arrays, ensuring that arrays are correctly excluded from the object check and properly handled by the Array.isArray() condition on line 252. The argument parsing logic correctly handles all three cases: string+data pairs, single message objects, and message arrays.

@lawrence-forooghian
Copy link
Collaborator

Haven't reviewed this myself but am happy with the changes made as a result of my comment — happy for you to merge if Evgenii is

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
test/rest/updates-deletes.test.js (2)

128-128: Consider extracting the timeout value to a constant.

The timeout value 10000 (10 seconds) is hardcoded in multiple waitFor calls throughout the test suite (lines 128, 171, 208, 313). Extracting this to a named constant would improve maintainability and make it easier to adjust if needed.

🔎 Suggested refactor

At the top of the describe block, add:

 describe('rest/message-operations', function () {
   this.timeout(60 * 1000);
+  const EVENTUAL_CONSISTENCY_TIMEOUT = 10000;

Then replace hardcoded values:

-      }, 10000);
+      }, EVENTUAL_CONSISTENCY_TIMEOUT);

322-322: Consider adding a clarifying comment about the append action.

Line 322 asserts that appendMessage results in an action of 'message.update' rather than 'message.append'. While this appears intentional (append is treated as a type of update), a brief comment explaining this behavior would improve test readability.

🔎 Suggested clarification
+      // Append operations result in 'message.update' action (append is a type of update)
       expect(latestMessage.action).to.equal('message.update');
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 77e76e0 and 1ce5277.

📒 Files selected for processing (1)
  • test/rest/updates-deletes.test.js
🧰 Additional context used
🧬 Code graph analysis (1)
test/rest/updates-deletes.test.js (2)
src/common/lib/client/realtimechannel.ts (2)
  • updateMessage (1035-1042)
  • appendMessage (1053-1060)
src/common/lib/client/restchannel.ts (2)
  • updateMessage (173-180)
  • appendMessage (191-198)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-node (18.x)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-browser (firefox)
🔇 Additional comments (4)
test/rest/updates-deletes.test.js (4)

14-14: Good defensive coding practice.

The explicit return after done(err) prevents the function from continuing and potentially calling done() twice, which would cause test failures.


20-55: LGTM!

Both publish serial tests are well-structured and provide comprehensive validation of the new serial-returning functionality for single and batch publishes.


62-92: LGTM!

Both getMessage tests properly validate the API's support for both string serials and Message objects with serial properties, with appropriate assertions on the retrieved messages.


217-279: LGTM!

Error handling tests are comprehensive and follow a consistent pattern, properly validating that operations fail with error code 40003 when the required serial is missing.

spec change: ably/specification#410

also tweaked the typings to be more consistent (use PublishOptions
instead of record<string, any>, and remove quickAck from PublishOptions,
publish params should be opaque to the SDK, the typings don't need to
have it as an example)
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ably.d.ts (1)

2321-2345: Add Channel.publish breaking change to release notes

The REST Channel.publish overloads now return Promise<PublishResult> instead of Promise<void>, enabling access to message serials. This is a source-level breaking change for TypeScript consumers and must be documented in the release notes as a major version breaking change so SDK users understand the impact and can intentionally adopt the new result shape.

🧹 Nitpick comments (5)
ably.d.ts (3)

2359-2385: update/delete/append signatures and docs line up, but UpdateDeleteResult name is slightly narrow

The Channel.updateMessage, deleteMessage, and new appendMessage overloads all return Promise<UpdateDeleteResult> with options?: PublishOptions, matching how RealtimeChannel is typed and how sendUpdate is implemented. The only nit is naming/documentation: UpdateDeleteResult and its docstring mention “update or delete”, but the same type is now used for appends too. Consider broadening the wording (e.g. “update, delete, or append operation”) to avoid confusion.


2657-2661: PublishOptions doc is intentionally generic; consider hinting at known options

The index‑signature‑only PublishOptions with “Publish options are server‑defined” keeps the type future‑proof. You might still want to briefly mention currently‑known keys like quickAck in the doc comment (without typing them explicitly) so users have a discoverability hint while preserving the open‑ended type.


2997-3017: Result types are well‑shaped; clarify append usage

PublishResult.serials and UpdateDeleteResult.versionSerial match how the transport now returns serials (including null when conflated/superseded) and what the tests expect. As mentioned above, UpdateDeleteResult’s docstring could be broadened to mention append operations explicitly since it’s used for all three.

src/common/lib/client/realtimechannel.ts (2)

240-292: publish(...args) correctly wires params and serials, but silently falls back when responses are absent

The new publish correctly normalizes the three overload shapes into Message[], applies max‑size checking, allows transient publishing via throwIfUnpublishableState, and threads params through to ProtocolMessage.params (stringified). Returning the PublishResult from sendMessage is the right shape; the res || { serials: [] } fallback is a bit lossy though—if the server/transport ever fails to supply serials for MESSAGE publishes, callers see a successful publish with an empty serials array and no indication that results were unavailable. Consider either:

  • Making absence of publishResponse for MESSAGE actions an error, or
  • At least documenting that empty serials can mean “no results available” as well as “no messages”.

1035-1093: sendUpdate helper unifies update/delete/append, but consider using the public PublishOptions type

The new updateMessage, deleteMessage, and appendMessage methods logging then delegating to sendUpdate are straightforward. sendUpdate:

  • Enforces presence of message.serial with a clear 40003 ErrorInfo, matching the tests.
  • Reuses throwIfUnpublishableState() so realtime operations follow the same gating as publish.
  • Builds the outbound Message with the provided action and uses version to carry MessageOperation metadata, then encodes under the current channel cipher.
  • Sends as a single‑message actions.MESSAGE protocol message with optional params, and returns { versionSerial: publishResponse?.serials?.[0] ?? null }, which aligns with UpdateDeleteResult.

Two small polish points:

  • The params argument could be typed as API.PublishOptions instead of Record<string, any> to stay in lockstep with the public surface.
  • If you ever extend this to support batched updates, be explicit about how you map multiple returned serials into a result shape.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1ce5277 and db5f3a2.

📒 Files selected for processing (4)
  • ably.d.ts
  • src/common/lib/client/realtimechannel.ts
  • src/common/lib/util/utils.ts
  • test/rest/updates-deletes.test.js
🧰 Additional context used
🧬 Code graph analysis (3)
test/rest/updates-deletes.test.js (2)
src/common/lib/client/realtimechannel.ts (2)
  • updateMessage (1035-1042)
  • appendMessage (1053-1060)
src/common/lib/client/restchannel.ts (2)
  • updateMessage (173-180)
  • appendMessage (191-198)
src/common/lib/util/utils.ts (1)
scripts/processPrivateApiData/dto.ts (1)
  • Record (53-53)
src/common/lib/client/realtimechannel.ts (4)
ably.d.ts (2)
  • PublishResult (3000-3006)
  • Message (2799-2857)
src/common/lib/types/errorinfo.ts (1)
  • ErrorInfo (35-66)
src/common/lib/types/protocolmessagecommon.ts (1)
  • actions (5-28)
src/common/lib/types/basemessage.ts (1)
  • CipherOptions (13-24)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: test-npm-package
  • GitHub Check: test-browser (webkit)
  • GitHub Check: test-node (20.x)
  • GitHub Check: test-browser (chromium)
  • GitHub Check: test-browser (firefox)
  • GitHub Check: test-node (16.x)
  • GitHub Check: test-node (18.x)
🔇 Additional comments (9)
src/common/lib/util/utils.ts (1)

248-250: Function usage is safe; edge cases are handled by callers.

The implementation is correct and straightforward. Both call sites guard the function with params ?, preventing null/undefined inputs from causing runtime errors. The function is used exclusively for simple value conversion in protocol message parameters, where complex type stringification is not a concern.

Consider adding JSDoc documentation for clarity on intended usage and the string conversion behavior.

test/rest/updates-deletes.test.js (3)

9-55: Publish serial tests and setup look solid

The early return in the before hook avoids double-calling done, and the new tests asserting publish returns serials (single and batch) align with the new PublishResult shape. Channel- and data-agnostic expectations keep these tests robust as long as conflation is disabled on these channels.


67-215: Good coverage of serial‑driven get/update/delete/version flows

Using the serial returned from publish for getMessage, updateMessage, deleteMessage, and getMessageVersions matches the intended API and exercises version metadata (serial ordering, timestamps, clientId, description, metadata) well. The waitFor-based checks for “latest” state are appropriate here and keep the tests deterministic against eventual consistency.


281-341: Test assertion is correct—no changes needed

The test correctly expects action === 'message.update' when retrieving the persisted message via getMessage(). This is intentional behavior: while message.append is the wire action (visible in realtime subscriptions when the operation is transmitted), the persisted latest message state has action === 'message.update' because the append semantically represents an update to the original message. This is consistent with how updateMessage() works and aligns with RSL15 spec requirements for message versioning. The distinction between operation action and persisted message action is sound, though SDK documentation could benefit from clarifying this for consumers reading the MessageAction type.

ably.d.ts (4)

1461-1475: Batch publish serials field matches new publish semantics

Adding serials: (string | null)[] to BatchPublishSuccessResult is consistent with PublishResult.serials and the conflation story (1:1 with messages, allowing nulls when discarded). This gives batch callers parity with per‑channel publish while preserving the existing messageId prefix behaviour.


2579-2600: RealtimeChannel.publish overloads align with the new realtime implementation

The realtime publish overloads (name+data, Message[], Message) returning Promise<PublishResult> and taking PublishOptions match the new RealtimeChannel.publish(...args) implementation and sendMessage’s propagation of publish responses. The docs correctly distinguish query‑string options (REST) from protocol message options (realtime) while sharing the same PublishOptions type.


2615-2640: Realtime update/delete/append API surface matches sendUpdate helper

The realtime updateMessage, deleteMessage, and appendMessage signatures (returning UpdateDeleteResult, accepting MessageOperation and PublishOptions) are consistent with the new sendUpdate helper and the REST API surface. This symmetry is good for users switching between REST and Realtime channels.


3049-3066: No action needed—the tests correctly reflect the intended design.

The REST test asserting message.update and the realtime test showing both message.append (on arrival) and message.update (on rewind) are correct. The design intentionally treats 'message.append' as a transient operation/wire action that surfaces in real-time, while the final persisted state of an appended message is represented as 'message.update'. The type definitions correctly expose MESSAGE_APPEND, and the runtime behavior is as intended.

Likely an incorrect or invalid review comment.

src/common/lib/client/realtimechannel.ts (1)

498-526: sendMessage now returning PublishResult is a clean abstraction

Having sendMessage wrap ConnectionManager.send and surface the (possibly undefined) PublishResult keeps publish/update/delete/append call sites simple while letting attach/detach/presence/state just ignore the result. The change to have sendPresence/sendState await sendMessage rather than duplicating send logic is a nice consolidation.

@SimonWoolf SimonWoolf merged commit ed0f0de into main Jan 6, 2026
12 of 14 checks passed
@SimonWoolf SimonWoolf deleted the protocol-v5 branch January 6, 2026 19:43
@coderabbitai coderabbitai bot mentioned this pull request Jan 6, 2026
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.

4 participants