From a0d904baf58378cf3e56604b142e92e19855e9ab Mon Sep 17 00:00:00 2001 From: Mike Christensen Date: Wed, 17 Dec 2025 17:12:32 +0000 Subject: [PATCH 1/3] components: add public preview type for Aside Adds support for data-type="public-preview" to the Aside component to support labelling with the new Public Preview release stage. SEE: PDR-086 --- src/components/Layout/mdx/Admonition.tsx | 2 +- src/components/blocks/dividers/Aside.tsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Layout/mdx/Admonition.tsx b/src/components/Layout/mdx/Admonition.tsx index 7c16ea1110..5f93647ee8 100644 --- a/src/components/Layout/mdx/Admonition.tsx +++ b/src/components/Layout/mdx/Admonition.tsx @@ -3,7 +3,7 @@ import cn from '@ably/ui/core/utils/cn'; import Aside from 'src/components/blocks/dividers/Aside'; import { HtmlComponentPropsData } from 'src/components/html-component-props'; -const LEGACY_ADMONITION_TYPES = ['new', 'updated', 'experimental']; +const LEGACY_ADMONITION_TYPES = ['new', 'updated', 'experimental', 'public-preview']; type AdmonitionVariant = 'neutral' | 'note' | 'further-reading' | 'important' | 'warning'; diff --git a/src/components/blocks/dividers/Aside.tsx b/src/components/blocks/dividers/Aside.tsx index 8bacaeb0aa..edf5ce7ad2 100644 --- a/src/components/blocks/dividers/Aside.tsx +++ b/src/components/blocks/dividers/Aside.tsx @@ -19,6 +19,7 @@ const versioningColors: { [key: string]: { bg: string; text: string } } = { new: { bg: '#FFF0BA', text: '#AC8600' }, updated: { bg: '#FFB8F1', text: '#9C007E' }, experimental: { bg: '#D8BCFB', text: '#460894' }, + 'public-preview': { bg: '#B8E6FF', text: '#005A8C' }, }; const Aside = ({ data, attribs }: HtmlComponentProps<'div'>) => { From 0b72282807349676fc125252984d41a5ddacf828 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Wed, 17 Dec 2025 22:22:59 +0000 Subject: [PATCH 2/3] Add append docs and modify update/delete/publish for protocol v5 --- src/pages/docs/api/realtime-sdk/channels.mdx | 41 +++- src/pages/docs/api/realtime-sdk/types.mdx | 27 ++- src/pages/docs/api/rest-api.mdx | 125 ++-------- src/pages/docs/api/rest-sdk/channels.mdx | 44 +++- src/pages/docs/api/rest-sdk/types.mdx | 2 +- src/pages/docs/channels/options/index.mdx | 16 ++ src/pages/docs/messages/updates-deletes.mdx | 232 +++++++++++++------ 7 files changed, 284 insertions(+), 203 deletions(-) diff --git a/src/pages/docs/api/realtime-sdk/channels.mdx b/src/pages/docs/api/realtime-sdk/channels.mdx index 2160157d91..2b22eac6a3 100644 --- a/src/pages/docs/api/realtime-sdk/channels.mdx +++ b/src/pages/docs/api/realtime-sdk/channels.mdx @@ -824,6 +824,8 @@ Failure to retrieve the message history will trigger the `errback` callbacks of Retrieves the latest version of a specific message by its serial identifier. Requires the **history** [capability](/docs/auth/capabilities). +See [updating and deleting messages: retrieving the latest version](/docs/messages/updates-deletes#get) for more information. + ##### Parameters | Parameter | Description | Type | @@ -836,11 +838,11 @@ Returns a promise which, upon success, will be fulfilled with a [`Message`](/doc #### updateMessage -`updateMessage(message: Message, operation?: MessageOperation, params?: Record): Promise` +`updateMessage(message: Message, operation?: MessageOperation): Promise` -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. Requires the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities). +Publishes an update to an existing message with shallow mixin 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. Requires the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities). -See [updating and deleting messages](/docs/messages/updates-deletes) for more information. +See [updating and deleting messages: updates](/docs/messages/updates-deletes#update) for more information. ##### Parameters @@ -848,19 +850,18 @@ See [updating and deleting messages](/docs/messages/updates-deletes) for more in |-----------|-------------|------| | message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field and the fields to update | [`Message`](/docs/api/realtime-sdk/messages) | | operation | An optional `MessageOperation` object containing metadata about the update operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) | -| params | Optional parameters sent as part of the query string | `Record` (optional) | ##### Returns -Returns a promise which, upon success, will be fulfilled. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. +Returns a promise which, upon success, will be fulfilled with an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result) object containing the new version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. #### deleteMessage -`deleteMessage(message: Message, operation?: MessageOperation, params?: Record): Promise` +`deleteMessage(message: Message, operation?: MessageOperation): Promise` -Marks a message as deleted by publishing an update with an action of `MESSAGE_DELETE`. This does not remove the message from the server, and the full message history remains accessible. Uses 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. Requires the **message-delete-own** or **message-delete-any** [capability](/docs/auth/capabilities). +Marks a message as deleted by publishing an update with an action of `MESSAGE_DELETE`. This does not remove the message from the server, and the full message history remains accessible. Uses shallow mixin 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. Requires the **message-delete-own** or **message-delete-any** [capability](/docs/auth/capabilities). -See [updating and deleting messages](/docs/messages/updates-deletes) for more information. +See [updating and deleting messages: deletes](/docs/messages/updates-deletes#delete) for more information. ##### Parameters @@ -868,11 +869,29 @@ See [updating and deleting messages](/docs/messages/updates-deletes) for more in |-----------|-------------|------| | message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field | [`Message`](/docs/api/realtime-sdk/messages) | | operation | An optional `MessageOperation` object containing metadata about the delete operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) | -| params | Optional parameters sent as part of the query string | `Record` (optional) | ##### Returns -Returns a promise which, upon success, will be fulfilled. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. +Returns a promise which, upon success, will be fulfilled with an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result) object containing the new version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. + +#### appendMessage + +`appendMessage(message: Message, operation?: MessageOperation): Promise` + +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 the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities). + +See [updating and deleting messages: appends](/docs/messages/updates-deletes#append) for more information. + +##### Parameters + +| Parameter | Description | Type | +|-----------|-------------|------| +| message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field and the data to append | [`Message`](/docs/api/realtime-sdk/messages) | +| operation | An optional `MessageOperation` object containing metadata about the append operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) | + +##### Returns + +Returns a promise which, upon success, will be fulfilled with an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result) object containing the new version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. #### getMessageVersions @@ -880,7 +899,7 @@ Returns a promise which, upon success, will be fulfilled. Upon failure, the prom Retrieves all historical versions of a specific message, ordered by version. This includes the original message and all subsequent updates or delete operations. Requires the **history** [capability](/docs/auth/capabilities). -See [updating and deleting messages](/docs/messages/updates-deletes) for more information. +See [updating and deleting messages: message versions](/docs/messages/updates-deletes#versions) for more information. ##### Parameters diff --git a/src/pages/docs/api/realtime-sdk/types.mdx b/src/pages/docs/api/realtime-sdk/types.mdx index d588159d4e..384572f7ad 100644 --- a/src/pages/docs/api/realtime-sdk/types.mdx +++ b/src/pages/docs/api/realtime-sdk/types.mdx @@ -432,7 +432,7 @@ This will typically be empty as all messages received from Ably are automaticall The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values. -_Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_ +_Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY, MESSAGE_APPEND }`_ ### serial @@ -498,6 +498,7 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects 'message.delete', 'meta', 'message.summary' + 'message.append', ] ``` @@ -513,6 +514,7 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects 'message.delete', 'meta', 'message.summary' + 'message.append', ] ``` @@ -530,6 +532,7 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects MESSAGE_DELETE, // 2 META, // 3 MESSAGE_SUMMARY // 4 + MESSAGE_APPEND, // 5 } ``` @@ -549,6 +552,7 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects ARTMessageActionDelete, ARTMessageActionMeta, ARTMessageActionMessageSummary + ARTMessageActionAppend, }; ``` @@ -564,6 +568,7 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects case Delete case Meta case Summary + case Append } ``` @@ -580,6 +585,26 @@ An `Array` of [`Message`](/docs/api/realtime-sdk/types#message) objects |----------|-------------|------| | summary | An object whose keys are annotation types, and the values are aggregated summaries for that annotation type | `Record` | +### PublishResult + +Contains the result of a publish operation. + +#### PropertiesMembersAttributes + +| Property | Description | Type | +|----------|-------------|------| +| serialsSerials | An array of message serials corresponding 1:1 to the messages that were published. A serial may be null if the message was discarded due to a configured conflation rule. | `String[]` | + +### UpdateDeleteResult + +Contains the result of an update, delete, or append message operation. + +#### PropertiesMembersAttributes + +| Property | Description | Type | +|----------|-------------|------| +| versionSerialVersionSerial | The serial of the version of the updated, deleted, or appended message. Will be null if the message was superseded by a subsequent update before it could be published. | `String` | + ### PresenceMessageio.ably.lib.types.PresenceMessageAbly::Models::PresenceMessageARTPresenceMessageIO.Ably.PresenceMessage A `PresenceMessage` represents an individual presence update that is sent to or received from Ably. diff --git a/src/pages/docs/api/rest-api.mdx b/src/pages/docs/api/rest-api.mdx index 455091a272..e5d1edcfc9 100644 --- a/src/pages/docs/api/rest-api.mdx +++ b/src/pages/docs/api/rest-api.mdx @@ -682,24 +682,27 @@ See [MessageAction](/docs/api/realtime-sdk/types#message-action) for the possibl An unsuccessful request returns an error. A 404 error is returned if a message with that serial does not exist. -### Update a message +### Update, delete, or append to a message #### PATCH main.realtime.ably.net/channels/\/messages/\ -Update an existing message on a channel, the message with the specified serial. This endpoint requires that the channel is configured with the "Message annotations, updates, and deletes" channel rule. +Update, delete, or append to an existing message on a channel, identified by its serial. This endpoint requires that the channel is configured with the "Message annotations, updates, and deletes" channel rule. -See [Updating and deleting messages](/docs/messages/updates-deletes#update) for more information about message updates and field semantics. +See [Updating and deleting messages](/docs/messages/updates-deletes) for more information about this feature. -The request body contains the message fields to update, along with optional operation metadata. Any fields not specified will be left as their original values. +The request body contains a Message object with an `action` field specifying the operation, the fields to update, and optional operation metadata in `version`. Any fields not specified will be left as their original values (shallow mixin semantics). For appends, the `data` field is concatenated to the existing message's data rather than replacing it. + +The `action` should be set to a [MessageAction](/docs/api/realtime-sdk/types#message-action) int enum: `MESSAGE_UPDATE` (1), `MESSAGE_DELETE` (2), or `MESSAGE_APPEND` (5). ```json { + action: , name: , data: , encoding: , extras: , - operation: { + version: { clientId: , description: , metadata: @@ -717,8 +720,9 @@ curl -X PATCH https://main.realtime.ably.net/channels/rest-example/messages/0182 -H "Content-Type: application/json" \ --data \ '{ + "action": 1, "data": "updated message content", - "operation": { + "version": { "description": "Fixed typo" } }' @@ -735,116 +739,19 @@ curl -X PATCH https://main.realtime.ably.net/channels/rest-example/messages/0182 ##### Capabilities -- `message-update-own` := Can update your own messages (messages where the original publisher's `clientId` matches the updater's `clientId`, where both are [identified](/docs/auth/identified-clients)) -- `message-update-any` := Can update any message on the channel +- `message-update-own` := Can update or append to your own messages (messages where the original publisher's `clientId` matches the updater's `clientId`, where both are [identified](/docs/auth/identified-clients)) +- `message-update-any` := Can update or append to any message on the channel +- `message-delete-own` := Can delete your own messages +- `message-delete-any` := Can delete any message on the channel ##### Returns -A successful request returns the updated @Message@ object with the new version information. - -See [MessageAction](/docs/api/realtime-sdk/types#message-action) for the possible values of the `action` enum. +Returns an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result), an object with a single `versionSerial` field: the serial of the version of the updated, deleted, or appended message, or `null` if the message was superseded by a subsequent update before it could be published. ```json { - serial: , - name: , - data: , - timestamp: , - clientId: , - action: , - version: { - serial: , - clientId: , - timestamp: , - description: , - metadata: - } -} -``` - - -An unsuccessful request returns an error. - -### Delete a message - -#### POST main.realtime.ably.net/channels/\/messages/\/delete - -Delete a message on a channel, the message with the specified serial. This is a 'soft' delete that publishes a new version of the message with an action of `message.delete`. The full message history remains accessible through the [message versions](message-versions) endpoint. This endpoint requires that the channel is configured with the "Message annotations, updates, and deletes" channel rule. - -See [Updating and deleting messages](/docs/messages/updates-deletes#update) for more information about message updates and field semantics. - -The request body contains the message fields to update, along with optional operation metadata. Any fields not specified will be left as their original values. - - -```json -{ - name: , - data: , - encoding: , - extras: , - operation: { - clientId: , - description: , - metadata: - } -} -``` - - -Example request: - - -```shell -curl -X POST https://main.realtime.ably.net/channels/rest-example/messages/01826232498871-001@abcdefghij:001/delete \ - -u "{{API_KEY}}" \ - -H "Content-Type: application/json" \ - --data \ - '{ - "operation": { - "description": "Content violation" - } - }' -``` - - -##### Options - -| Property | Value | -|----------|-------| -| Content-Type | `application/json`, `application/x-msgpack` or `application/x-www-form-urlencoded` | -| Accept | `application/json` by default, or `application/x-msgpack`, `text/html` | -| Auth required | yes ([basic](basic-authentication) or [token](token-authentication) with `message-delete-own` or `message-delete-any` capability) | - -##### Capabilities - -| Capability | Description | -|------------|-------------| -| `message-delete-own` | Can delete your own messages (messages where the original publisher's `clientId` matches the deleter's `clientId`, where both are [identified](/docs/auth/identified-clients)) | -| `message-delete-any` | Can delete any message on the channel | - -##### Returns - -A successful request returns the updated `Message` object with `action` set to `message.delete`. - -See [MessageAction](/docs/api/realtime-sdk/types#message-action) for the possible values of the `action` enum. - - -```shell -{ - serial: , - name: , - data: , - timestamp: , - clientId: , - action: , - version: { - serial: , - clientId: , - timestamp: , - description: , - metadata: - } + "versionSerial": "01826232512345-002@abcdefghij:002" } ``` diff --git a/src/pages/docs/api/rest-sdk/channels.mdx b/src/pages/docs/api/rest-sdk/channels.mdx index f57747d395..022178675c 100644 --- a/src/pages/docs/api/rest-sdk/channels.mdx +++ b/src/pages/docs/api/rest-sdk/channels.mdx @@ -473,6 +473,8 @@ On failure to retrieve message history, the `error` contains an [`ErrorInfo`](#e Retrieves the latest version of a specific message by its serial identifier. Requires the **history** [capability](/docs/auth/capabilities). +See [updating and deleting messages: retrieving the latest version](/docs/messages/updates-deletes#get) for more information. + ##### Parameters | Parameter | Description | Type | @@ -485,11 +487,11 @@ Returns a promise which, upon success, will be fulfilled with a [`Message`](/doc #### updateMessage -`updateMessage(message: Message, operation?: MessageOperation, params?: Record): Promise` +`updateMessage(message: Message, operation?: MessageOperation, params?: Record): Promise` -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. Requires the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities). +Publishes an update to an existing message with shallow mixin 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. Requires the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities). -See [updating and deleting messages](/docs/messages/updates-deletes) for more information. +See [updating and deleting messages: updates](/docs/messages/updates-deletes#update) for more information. ##### Parameters @@ -501,15 +503,15 @@ See [updating and deleting messages](/docs/messages/updates-deletes) for more in ##### Returns -Returns a promise which, upon success, will be fulfilled. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. +Returns a promise which, upon success, will be fulfilled with an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result) object containing the new version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. #### deleteMessage -`deleteMessage(message: Message, operation?: MessageOperation, params?: Record): Promise` +`deleteMessage(message: Message, operation?: MessageOperation, params?: Record): Promise` -Marks a message as deleted by publishing an update with an action of `MESSAGE_DELETE`. This does not remove the message from the server, and the full message history remains accessible. Uses 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. Requires the **message-delete-own** or **message-delete-any** [capability](/docs/auth/capabilities). +Marks a message as deleted by publishing an update with an action of `MESSAGE_DELETE`. This does not remove the message from the server, and the full message history remains accessible. Uses shallow mixin 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. Requires the **message-delete-own** or **message-delete-any** [capability](/docs/auth/capabilities). -See [updating and deleting messages](/docs/messages/updates-deletes) for more information. +See [updating and deleting messages: deletes](/docs/messages/updates-deletes#delete) for more information. ##### Parameters @@ -521,7 +523,29 @@ See [updating and deleting messages](/docs/messages/updates-deletes) for more in ##### Returns -Returns a promise which, upon success, will be fulfilled. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. +Returns a promise which, upon success, will be fulfilled with an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result) object containing the new version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. + +#### appendMessage + +`appendMessage(message: Message, operation?: MessageOperation, params?: Record): Promise` + +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 the **message-update-own** or **message-update-any** [capability](/docs/auth/capabilities). + +For publishing a high rate of appends, you typically want to use a realtime client, not a REST client, in order to have message order preservation. See [append ordering](/docs/messages/updates-deletes#append-ordering). + +See [updating and deleting messages: appends](/docs/messages/updates-deletes#append) for more information. + +##### Parameters + +| Parameter | Description | Type | +|-----------|-------------|------| +| message | A [`Message`](/docs/api/realtime-sdk/messages) object containing a populated `serial` field and the data to append | [`Message`](/docs/api/realtime-sdk/messages) | +| operation | An optional `MessageOperation` object containing metadata about the append operation. Can include `clientId`, `description`, and `metadata` fields | `MessageOperation` (optional) | +| params | Optional parameters sent as part of the query string | `Record` (optional) | + +##### Returns + +Returns a promise which, upon success, will be fulfilled with an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result) object containing the new version of the message. Upon failure, the promise will be rejected with an [`ErrorInfo`](/docs/api/realtime-sdk/types#error-info) object which explains the error. #### getMessageVersions @@ -529,7 +553,7 @@ Returns a promise which, upon success, will be fulfilled. Upon failure, the prom Retrieves all historical versions of a specific message, ordered by version. This includes the original message and all subsequent updates or delete operations. Requires the **history** [capability](/docs/auth/capabilities). -See [updating and deleting messages](/docs/messages/updates-deletes) for more information. +See [updating and deleting messages: versions](/docs/messages/updates-deletes#versions) for more information. ##### Parameters @@ -590,7 +614,7 @@ This will typically be empty as all messages received from Ably are automaticall ### action -The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values.
_Type: `enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_ +The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values.
_Type: `enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, MESSAGE_APPEND, META, MESSAGE_SUMMARY }`_ ### serial
diff --git a/src/pages/docs/api/rest-sdk/types.mdx b/src/pages/docs/api/rest-sdk/types.mdx index 0f7a7b0e5d..646ef6c8f3 100644 --- a/src/pages/docs/api/rest-sdk/types.mdx +++ b/src/pages/docs/api/rest-sdk/types.mdx @@ -411,7 +411,7 @@ This will typically be empty as all messages received from Ably are automaticall #### action -The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values.
_Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY }`_ +The action type of the message, one of the [`MessageAction`](/docs/api/realtime-sdk/types#message-action) enum values.
_Type: `int enum { MESSAGE_CREATE, MESSAGE_UPDATE, MESSAGE_DELETE, META, MESSAGE_SUMMARY, MESSAGE_APPEND }`_ #### serial
diff --git a/src/pages/docs/channels/options/index.mdx b/src/pages/docs/channels/options/index.mdx index 7f08fe1c02..9af33062d7 100644 --- a/src/pages/docs/channels/options/index.mdx +++ b/src/pages/docs/channels/options/index.mdx @@ -236,6 +236,22 @@ The [`rewind`](/docs/channels/options/rewind) feature enables clients to replay The [`delta`](/docs/channels/options/deltas) feature enables clients to subscribe to a channel so that message payloads only contain the difference, or delta, between the current and previous message. +### Append mode + +When using [message appends](/docs/messages/updates-deletes#append), subscribers receive incremental append payloads by default. Set `appendMode` to `full` to receive `update` messages, with the full message data so far in each message, instead of just the incremental `append`: + + +```realtime_javascript +const channelOpts = { params: { appendMode: 'full' } }; +const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts); +``` + +```realtime_nodejs +const channelOpts = { params: { appendMode: 'full' } }; +const channel = realtime.channels.get('{{RANDOM_CHANNEL_NAME}}', channelOpts); +``` + + ### Occupancy [Occupancy](/docs/presence-occupancy/occupancy) provides metrics about the clients attached to a channel, such as the number of connections and the number of clients subscribed to the channel. `occupancy` can be specified in the `params` property in order to subscribe a client to occupancy metrics for the channel. The metrics will be received by a client as events on the channel. diff --git a/src/pages/docs/messages/updates-deletes.mdx b/src/pages/docs/messages/updates-deletes.mdx index 9aed8f97e5..180cb48b69 100644 --- a/src/pages/docs/messages/updates-deletes.mdx +++ b/src/pages/docs/messages/updates-deletes.mdx @@ -41,7 +41,7 @@ When message updates and deletes are enabled, messages are [persisted](/docs/sto To update an existing message, use the `updateMessage()` method on a REST or realtime channel. The published update will have an `action` of `message.update`. -The message is identified by its `serial`, which is populated by Ably. So to publish an update to a message, you have to have received it, as a subscriber or by querying history. Once you have that message, you can either take it, make your desired changes, and use that; or make a new Message object and just set its `serial` to that of the original message, as appropriate in your usecase. +The message is identified by its `serial`, which is populated by Ably. To update a message, you need its serial - you can get this either from the return value of `publish()`, from a received message via subscription, or by querying history. ```javascript @@ -49,29 +49,22 @@ const realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); // This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes const channel = realtime.channels.get('updates:example'); -// First subscribe to messages -await channel.subscribe(async (msg) => { - if (msg.data === 'original-data') { - // Publish an update - - // First way: mutate the received Message - msg.data = 'new-data-1'; - await channel.updateMessage(msg, { description: 'reason for first update' }); - - // Second way: publish a new Message using the serial - const msg2 = { - name: 'message-name', - serial: msg.serial, - }; - await channel.updateMessage(msg2, { description: 'reason for second update' }); - } -}); - -// Publish the original message -await channel.publish({ +// Publish the original message and get its serial from the result +const publishResult = await channel.publish({ name: 'message-name', data: 'original-data', }); +const serial = publishResult.serials[0]; + +// Publish an update using the serial +await channel.updateMessage( + { + serial, + data: 'updated-data' + }, + { description: 'reason for update' } +); + ``` ```nodejs @@ -79,31 +72,28 @@ const realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); // This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes const channel = realtime.channels.get('updates:example'); -// First subscribe to messages -await channel.subscribe(async (msg) => { - if (msg.data === 'original-data') { - // Publish an update - // First way: mutate the received Message - msg.data = 'new-data-1'; - await channel.updateMessage(msg, { description: 'reason for first update' }); - - // Second way: publish a new Message using the serial - const msg2 = { - name: 'message-name', - serial: msg.serial, - }; - await channel.updateMessage(msg2, { description: 'reason for second update' }); - } -}); - -// Publish the original message -await channel.publish({ +// Publish the original message and get its serial from the result +const publishResult = await channel.publish({ name: 'message-name', data: 'original-data', }); +const serial = publishResult.serials[0]; + +// Publish an update using the serial +await channel.updateMessage( + { + serial, + data: 'updated-data' + }, + { description: 'reason for update' } +); ``` +#### Returns + +Returns an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result), an object with a single `versionSerial` field: the serial of the version of the updated message, or `null` if the message was superseded by a subsequent update before it could be published. + #### Mixin semantics When updating a message, any `data`, `name`, and `extras` you specify in the update will replace the corresponding fields in the existing message. Any you leave out remain as they were, so you get a shallow mixin. For example, if a message has `{ name: "greeting", data: "hello" }`, and you update it with `{ data: "hi" }`, the result will be `{ name: "greeting", data: "hi" }`. @@ -113,6 +103,10 @@ The fields that can be updated are: - `name` - `extras` +#### Conflation + +Ably may opportunistically discard out of date updates to a given message, for example, during a serverside batching step, or within a [rewind](/docs/channels/options/rewind) backlog. This means subscribers are not guaranteed to receive every intermediate update if multiple updates occur in quick succession, but it is guaranteed that the last update that they receive will represent the most recent version of the message (matching the version that will be eventually retrievable by a history or [getMessage()](http://localhost:8000/docs/messages/updates-deletes#get) request). + #### Capabilities To update messages, clients need one of the following [capabilities](/docs/auth/capabilities): @@ -140,7 +134,7 @@ To delete a message, use the `deleteMessage()` method on a REST or realtime chan The latest version of each message will be accessible from history, including if that latest version happens to be a delete. -As with updating, the message is identified by its `serial`, which is populated by Ably. So to delete a message, you have to have received it, as a subscriber or by querying history. Once you have that message, you can either take it, make your desired changes, and use that; or make a new Message object and just set its `serial` to that of the original message, as appropriate in your usecase. +The message is identified by its `serial`, which is populated by Ably. To delete a message, you need its serial - you can get this either from the return value of `publish()`, from a received message via subscription, or by querying history. Deleting a message marks it as deleted without removing it from the server. The full message history remains accessible through the [message versions](#versions) API. @@ -150,24 +144,21 @@ const realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); // This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes const channel = realtime.channels.get('updates:example'); -// First subscribe to messages -await channel.subscribe(async (msg) => { - if (msg.data === 'original-data') { - // Publish a delete - // First way: just use the received Message - await channel.deleteMessage(msg, { description: 'reason for first delete' }); - - // Second way: publish a new Message using the serial - const msg2 = { serial: msg.serial }; - await channel.deleteMessage(msg2, { description: 'reason for second delete' }); - } -}); - -// Publish the original message -await channel.publish({ +// Publish the original message and get its serial from the result +const publishResult = await channel.publish({ name: 'message-name', data: 'original-data', }); +const serial = publishResult.serials[0]; + +// Delete the message using the serial +await channel.deleteMessage( + { + serial, + data: '' // clear the previous data + }, + { description: 'reason for delete' } +); ``` ```nodejs @@ -175,33 +166,38 @@ const realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); // This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes const channel = realtime.channels.get('updates:example'); -// First subscribe to messages -await channel.subscribe(async (msg) => { - if (msg.data === 'original-data') { - // Publish a delete - // First way: just use the received Message - await channel.deleteMessage(msg, { description: 'reason for first delete' }); - - // Second way: publish a new Message using the serial - const msg2 = { serial: msg.serial }; - await channel.deleteMessage(msg2, { description: 'reason for second delete' }); - } -}); - -// Publish the original message -await channel.publish({ +// Publish the original message and get its serial from the result +const publishResult = await channel.publish({ name: 'message-name', data: 'original-data', }); +const serial = publishResult.serials[0]; + +// Delete the message using the serial +await channel.deleteMessage( + { + serial, + data: '' // clear the previous data + }, + { description: 'reason for delete' } +); ```
+#### Returns + +Returns an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result), an object with a single `versionSerial` field: the serial of the version of the deleted message, or `null` if the message was superseded by a subsequent update before it could be published. + #### Mixin semantics Deleting has the same semantics as updating, so only the message fields (out of `data`, `name`, and `extras`) you specify in the update will replace the corresponding fields in the existing message, in a shallow mixin. That means that if you e.g. want the deleted message to have empty `data` (to prevent users looking at raw history results from the API from seeing what the data used to be), you must explicitly set to e.g. an empty object when publishing the delete. (And even then, all previous versions are accessible through the version history API). +#### Conflation + +Since deletes are just updates with a different action, [as with updates](#update-conflation), Ably may opportunistically discard out of date versions of a given message, for example, during a serverside batching step, or within a [rewind](/docs/channels/options/rewind) backlog. This means subscribers are not guaranteed to receive every intermediate update if multiple updates/deletes occur in quick succession, but it is guaranteed that the last update/delete that they receive will represent the most recent version of the message (matching the version that will be eventually retrievable by a history or [getMessage()](http://localhost:8000/docs/messages/updates-deletes#get) request). + #### Capabilities To delete messages, clients need one of the following [capabilities](/docs/auth/capabilities): @@ -223,6 +219,99 @@ When deleting a message, you can optionally provide metadata: This metadata will end up in the message's `version` property. See [Message version structure](/docs/messages/updates-deletes#version-structure) for what this looks like. +## Append to a message + +To append data to an existing message, use the `appendMessage()` method on a REST or realtime channel. The published append will have an `action` of `message.append`. + +Unlike `updateMessage()` which replaces the data field, `appendMessage()` concatenates the new data to the existing message's (string or binary) data. This is useful for building up message content incrementally, for example in streaming or gradual message building scenarios. Other fields (`name`, `extras`) will replace the previous values if provided, similar to update mixin semantics. + +The message is identified by its `serial`, which is populated by Ably. To append to a message, you need its serial - you can get this either from the return value of `publish()`, from a received message via subscription, or by querying history. + +When Ably receives an append, we will concatenate the provided data with the current latest version and calculate the updated full (non-incremental) version of the message. That full version of the message is what will be stored in [History](/docs/storage-history/history) as the most recent version, with an action of `message.update`, so someone paging through the history of a channel will never need to do any concatenation themselves, they will just get the full message. Similarly, when attaching to a channel using [rewind](/docs/channels/options/rewind) to get recent messages you will receive only one version of each message, the latest version with the full concatenated data, which means you can specify e.g. `rewind=10` to get the most recent 10 full, distinct messages. Only realtime subscribers receiving live messages will receive the incremental appends (and they can pass a [channel param](/docs/channels/options#append-mode) to request full versions if they want). + +Unlike updates, appends are not stored in version history, since they're designed for a usecase of a frequent rate of updates from a single publisher. + + +```javascript +const realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); +// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes +const channel = realtime.channels.get('updates:example'); + +// Publish the original message and get its serial from the result +const publishResult = await channel.publish({ + name: 'message-name', + data: 'Hello', +}); +const serial = publishResult.serials[0]; + +// Append to the message a few times (without needing to await each to finish +// before doing the next); the data will be concatenated +channel.appendMessage({ serial, data: ', ' }); +channel.appendMessage({ serial, data: 'World' }); +channel.appendMessage({ serial, data: '!' }); + +// the message in history now has data: "Hello, World!" +``` + +```nodejs +const realtime = new Ably.Realtime({ key: '{{API_KEY}}' }); +// This assumes there is an 'updates' namespace with a channel rule enabling updates and deletes +const channel = realtime.channels.get('updates:example'); + +// Publish the original message and get its serial from the result +const publishResult = await channel.publish({ + name: 'message-name', + data: 'Hello', +}); +const serial = publishResult.serials[0]; + +// Append to the message a few times (without needing to await each to finish +// before doing the next); the data will be concatenated +channel.appendMessage({ serial, data: ', ' }); +channel.appendMessage({ serial, data: 'World' }); +channel.appendMessage({ serial, data: '!' }); + +// the message in history now has data: "Hello, World!" +``` + + +#### Returns + +Returns an [`UpdateDeleteResult`](/docs/api/realtime-sdk/types#update-delete-result), an object with a single `versionSerial` field: the serial of the version of the appended message, or `null` if the message was superseded by a subsequent update before it could be published. + +#### Ordering + +You don't need to wait for one append publish before doing the next: they can be pipelined, and Ably will construct the full payload optimistically in the pipeline. (Which means there is a possibility of an append being rejected e.g. due to a channel rate limit, but its data still being optimistically incorporated into a subsequent append). + +If you want to publish a high rate of appends to a single message, you should be publishing with [a realtime client](https://faqs.ably.com/should-i-use-the-rest-or-realtime-library), since then [Ably message order preservation](/docs/platform/architecture/message-ordering) will guarantee that the appends will be applied in the same order they were published. If using a REST client, while in practice message order is often still preserved (especially in sdks that use http/2), this cannot be guaranteed. + +#### Conflation + +Ably may opportunistically conflate multiple appends to the same message together (concatenating their data payloads), so subscribers may receive a single append containing the combined data of multiple append operations rather than each append individually. The operation metadata of this will be from the most recent of the appends. + +Ably may also at any point deliver an append to subscribers as a `message.update` containing the complete payload so far (instead of an incremental `message.append`). + +#### Capabilities + +To append to messages, clients need one of the following [capabilities](/docs/auth/capabilities): + +| Capability | Description | +| ---------- | ----------- | +| **message-update-own** | Can append to your own messages (more precisely, messages where the original publisher's `clientId` matches your `clientId`, where both are [identified](/docs/auth/identified-clients)). | +| **message-update-any** | Can append to any message on the channel. | + +#### Operation metadata + +When appending to a message, you can optionally provide metadata: + +| Property | Description | Type | +| -------- | ----------- | ---- | +| clientId | The client identifier of the user performing the append (automatically populated if done by an identified client). | String | +| description | A description of why the append was made. | String | +| metadata | Additional metadata about the append operation. | Object | + +This metadata will end up in the message's `version` property. See [Message version structure](/docs/messages/updates-deletes#version-structure) for what this looks like. + ## Get the latest version of a message To retrieve the most recent version of a specific message, use the `getMessage()` method on a REST channel. You can pass either the message's serial identifier as a string, or a message object with a `serial` property. @@ -331,6 +420,7 @@ The `action` property on a message indicates the type of operation: | message.delete | A deletion of a message | | meta | A message originating from ably rather than being published by a user, such as [inband occupancy events](/docs/channels/options#occupancy) | | message.summary | A message containing the [latest rolled-up summary of annotations](/docs/messages/annotations#annotation-summaries) | +| message.append | An append to an existing message's data | ## Version ordering From 156d41c87e754ce3273dd5ca8760ff5edf92d647 Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Thu, 18 Dec 2025 14:49:26 +0000 Subject: [PATCH 3/3] Annotations and updates: move from experimental to public preview --- src/pages/docs/channels/index.mdx | 2 +- src/pages/docs/messages/annotations.mdx | 4 ++-- src/pages/docs/messages/updates-deletes.mdx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pages/docs/channels/index.mdx b/src/pages/docs/channels/index.mdx index 908607717f..b0b6f0695d 100644 --- a/src/pages/docs/channels/index.mdx +++ b/src/pages/docs/channels/index.mdx @@ -200,7 +200,7 @@ The channel rules related to enabling features are: | Push notifications enabled | If checked, publishing messages with a push payload in the `extras` field is permitted. This triggers the delivery of a [Push Notification](/docs/push) to devices registered for push on the channel. | | Server-side batching | If enabled, messages are grouped into batches before being sent to subscribers. [Server-side batching](/docs/messages/batch#server-side) reduces the overall message count, lowers costs, and mitigates the risk of hitting rate limits during high-throughput scenarios. | | Message conflation | If enabled, messages are aggregated over a set period of time and evaluated against a conflation key. All but the latest message for each conflation key value will be discarded, and the resulting message, or messages, will be delivered to subscribers as a single batch once the period of time elapses. [Message conflation](/docs/messages#conflation) reduces costs in high-throughput scenarios by removing redundant and outdated messages. | -| Message annotations, updates, and deletes | If enabled, allows message "annotations":/docs/messages/annotations to be used, as well as updates and deletes to be published to messages. Note that these features are currently Experimental, still in development, and subject to change. When this feature is enabled, messages will be "persisted":/docs/storage-history/storage#all-message-persistence (necessary in order from them later be annotated or updated), and "continuous history":/docs/storage-history/history#continuous-history features will not work. +| Message annotations, updates, and deletes | If enabled, allows message "annotations":/docs/messages/annotations to be used, as well as updates and deletes to be published to messages. Note that these features are currently in public preview. When this feature is enabled, messages will be "persisted":/docs/storage-history/storage#all-message-persistence (necessary in order from them later be annotated or updated), and "continuous history":/docs/storage-history/history#continuous-history features will not work. To set a channel rule in the Ably dashboard: diff --git a/src/pages/docs/messages/annotations.mdx b/src/pages/docs/messages/annotations.mdx index ef24b54124..4eafc6dd87 100644 --- a/src/pages/docs/messages/annotations.mdx +++ b/src/pages/docs/messages/annotations.mdx @@ -3,8 +3,8 @@ title: Message annotations meta_description: "Annotate messages on a channel with additional metadata." --- -