From fef00bf3481e40b29de93d96dce79a7a384aa3ec Mon Sep 17 00:00:00 2001 From: Simon Woolf Date: Mon, 15 Dec 2025 17:22:25 +0000 Subject: [PATCH] Implement spec for protocol v5 --- meta.yaml | 4 +- textile/api-docstrings.md | 60 ++++++++++++++++++++----- textile/features.textile | 94 ++++++++++++++++++++++++++------------- textile/protocol.textile | 24 ++++++++-- 4 files changed, 134 insertions(+), 48 deletions(-) diff --git a/meta.yaml b/meta.yaml index 8bc536e9..3d838c44 100644 --- a/meta.yaml +++ b/meta.yaml @@ -1,8 +1,8 @@ versions: # Must conform to Semantic Versioning (https://semver.org/). # Should never need to use a pre-release suffix. - specification: 4.0.0 + specification: 5.0.0 # Must be an Integer value. # Previously, prior to version 2 (i.e. versions 0.8, 1.0, 1.1 and 1.2) it was a Decimal value. - protocol: 4 + protocol: 5 diff --git a/textile/api-docstrings.md b/textile/api-docstrings.md index 89702da6..5ac9d397 100644 --- a/textile/api-docstrings.md +++ b/textile/api-docstrings.md @@ -276,28 +276,38 @@ Enables messages to be published and historic messages to be retrieved for a cha ||| `PaginatedResult` || A [`PaginatedResult`]{@link PaginatedResult} object containing an array of [`Message`]{@link Message} objects. | | status() => ChannelDetails ||| RSL8 | Retrieves a [`ChannelDetails`]{@link ChannelDetails} object for the channel, which includes status and occupancy metrics. | ||| `ChannelDetails` || A [`ChannelDetails`]{@link ChannelDetails} object. | -| publish(Message, params?: `Dict`) => io ||| RSL1 | Publishes a message to the channel. A callback may optionally be passed in to this call to be notified of success or failure of the operation. | +| publish(Message, params?: `Dict`) => io PublishResult ||| RSL1 | Publishes a message to the channel. | || `Message` ||| A [`Message`]{@link Message} object. | || `params` ||| Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. | -| publish([Message], params?: `Dict` ||| RSL1 | Publishes an array of messages to the channel. A callback may optionally be passed in to this call to be notified of success or failure of the operation. | +||| `PublishResult` || A [`PublishResult`]{@link PublishResult} object containing the serials of the published messages. | +| publish([Message], params?: `Dict`) => io PublishResult ||| RSL1 | Publishes an array of messages to the channel. | || [`Message`] ||| An array of [`Message`]{@link Message} objects. | || `params` ||| Optional parameters, such as [`quickAck`](https://faqs.ably.com/why-are-some-rest-publishes-on-a-channel-slow-and-then-typically-faster-on-subsequent-publishes) sent as part of the query string. | -| publish(name: String?, data: Data?) => io ||| RSL1 | Publishes a single message to the channel with the given event name and payload. A callback may optionally be passed in to this call to be notified of success or failure of the operation. | +||| `PublishResult` || A [`PublishResult`]{@link PublishResult} object containing the serials of the published messages. | +| publish(name: String?, data: Data?) => io PublishResult ||| RSL1 | Publishes a single message to the channel with the given event name and payload. | || `name` ||| The name of the message. | || `data` ||| The payload of the message. | +||| `PublishResult` || A [`PublishResult`]{@link PublishResult} object containing the serial of the published message. | | setOptions(options: ChannelOptions) => io ||| RSL7 | Sets the [`ChannelOptions`]{@link ChannelOptions} for the channel. | || `options` ||| A [`ChannelOptions`]{@link ChannelOptions} object. | | getMessage(serialOrMsg: String \| Message) => io Message ||| RSL11 | Retrieves the latest version of a specific message by its serial identifier or by providing a [`Message`]{@link Message} object containing the serial (which will not be mutated). | || `serialOrMsg` ||| Either the serial identifier string of the message to retrieve, or a [`Message`]{@link Message} object containing a populated `serial` field. | ||| `Message` || A [`Message`]{@link Message}, the latest version of the message | -| updateMessage(Message, operation?: MessageOperation, params?: `Dict`) => io ||| RSL12 | Publishes an update to 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. Note that this publishes an update, it does not mutate the original message if passed in. | +| updateMessage(Message, operation?: MessageOperation, params?: `Dict`) => io UpdateDeleteResult ||| RSL12 | Publishes an update to 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. Note that this publishes an update, it does not mutate the original message if passed in. | || `Message` ||| A [`Message`]{@link Message} object containing a populated `serial` field and the fields to update. | || `operation` ||| An optional [`MessageOperation`]{@link MessageOperation} object containing metadata about the update operation. | || `params` ||| Optional parameters sent as part of the query string. | -| deleteMessage(Message, operation?: MessageOperation, params?: `Dict`) => io ||| RSL13 | 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 (meaning that if you for example want the `MESSAGE_DELETE` to have an empty data, you should explicitly set the `data` to an empty object). | +||| `UpdateDeleteResult` || An [`UpdateDeleteResult`]{@link UpdateDeleteResult} object containing the serial of the new version of the message. | +| deleteMessage(Message, operation?: MessageOperation, params?: `Dict`) => io UpdateDeleteResult ||| RSL13 | 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 (meaning that if you for example want the `MESSAGE_DELETE` to have an empty data, you should explicitly set the `data` to an empty object). | || `Message` ||| A [`Message`]{@link Message} object containing a populated `serial` field. | || `operation` ||| An optional [`MessageOperation`]{@link MessageOperation} object containing metadata about the delete operation. | || `params` ||| Optional parameters sent as part of the query string. | +||| `UpdateDeleteResult` || An [`UpdateDeleteResult`]{@link UpdateDeleteResult} object containing the serial of the new version of the message. | +| appendMessage(Message, operation?: MessageOperation, params?: `Dict`) => io UpdateDeleteResult ||| RSL15 | 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. | +|| `Message` ||| A [`Message`]{@link Message} object containing a populated `serial` field and the data to append. | +|| `operation` ||| An optional [`MessageOperation`]{@link MessageOperation} object containing metadata about the append operation. | +|| `params` ||| Optional parameters sent as part of the query string. | +||| `UpdateDeleteResult` || An [`UpdateDeleteResult`]{@link UpdateDeleteResult} object containing the serial of the new version of the message. | | getMessageVersions(serialOrMsg: String \| Message, params?: `Dict`) => io `PaginatedResult` ||| RSL14 | Retrieves all historical versions of a specific message, ordered by version. This includes the original message and all subsequent updates or delete operations. | || `serialOrMsg` ||| Either the serial identifier string of the message whose versions are to be retrieved, or a [`Message`]{@link Message} object containing a populated `serial` field. | || `params` ||| Optional parameters sent as part of the query string. | @@ -331,13 +341,16 @@ Enables messages to be published and subscribed to. Also enables historic messag || `limit` || RTL10a | An upper limit on the number of messages returned. The default is 100, and the maximum is 1000. | || `untilAttach` || RTL10b | When `true`, ensures message history is up until the point of the channel being attached. See [continuous history](https://ably.com/docs/realtime/history#continuous-history) for more info. Requires the `direction` to be `backwards`. If the channel is not attached, or if `direction` is set to `forwards`, this option results in an error. | ||| `PaginatedResult` || A [`PaginatedResult`]{@link PaginatedResult} object containing an array of [`Message`]{@link Message} objects. | -| publish(Message) => io ||| RTL6i | Publish a message to the channel. A callback may optionally be passed in to this call to be notified of success or failure of the operation. When publish is called with this client library, it won't attempt to implicitly attach to the channel. | +| publish(Message) => io PublishResult ||| RTL6i | Publish a message to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. | || `Message` ||| A [`Message`]{@link Message} object. | -| publish([Message]) => io ||| RTL6i | Publishes an array of messages to the channel. A callback may optionally be passed in to this call to be notified of success or failure of the operation. When publish is called with this client library, it won't attempt to implicitly attach to the channel. | +||| `PublishResult` || A [`PublishResult`]{@link PublishResult} object containing the serial of the published message. | +| publish([Message]) => io PublishResult ||| RTL6i | Publishes an array of messages to the channel. When publish is called with this client library, it won't attempt to implicitly attach to the channel. | || [`Message`] ||| An array of [`Message`]{@link Message} objects. | -| publish(name: String?, data: Data?) => io ||| RTL6i | Publishes a single message to the channel with the given event name and payload. A callback may optionally be passed in to this call to be notified of success or failure of the operation. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. | +||| `PublishResult` || A [`PublishResult`]{@link PublishResult} object containing the serials of the published messages. | +| publish(name: String?, data: Data?) => io PublishResult ||| RTL6i | Publishes a single message to the channel with the given event name and payload. When publish is called with this client library, it won't attempt to implicitly attach to the channel, so long as [transient publishing](https://ably.com/docs/realtime/channels#transient-publish) is available in the library. Otherwise, the client will implicitly attach. | || `name` ||| The event name. | || `data` ||| The message payload. | +||| `PublishResult` || A [`PublishResult`]{@link PublishResult} object containing the serial of the published message. | | subscribe((Message) ->) => io ||| RTL7a | Registers a listener for messages on this channel. The caller supplies a listener function, which is called each time one or more messages arrives on the channel. A callback may optionally be passed in to this call to be notified of success or failure of the channel [`attach()`]{@link RealtimeChannel#attach} operation. It will not be called if the [`attachOnSubscribe`]{@link ChannelOptions#attachOnSubscribe} channel option is set to `false`. | || `(Message)` ||| An event listener function. | | subscribe(String, (Message) ->) => io ||| RTL7b | Registers a listener for messages with a given event name on this channel. The caller supplies a listener function, which is called each time one or more matching messages arrives on the channel. A callback may optionally be passed in to this call to be notified of success or failure of the channel [`attach()`]{@link RealtimeChannel#attach} operation. It will not be called if the [`attachOnSubscribe`]{@link ChannelOptions#attachOnSubscribe} channel option is set to `false`. | @@ -370,14 +383,21 @@ Enables messages to be published and subscribed to. Also enables historic messag | getMessage(serialOrMsg: String \| Message) => io Message ||| RTL28 | Retrieves the latest version of a specific message by its serial identifier or by providing a [`Message`]{@link Message} object containing the serial. | || `serialOrMsg` ||| Either the serial identifier string of the message to retrieve, or a [`Message`]{@link Message} object containing a populated `serial` field. | ||| `Message` || A [`Message`]{@link Message}, the latest version of the message | -| updateMessage(Message, operation?: MessageOperation, params?: `Dict`) => io ||| RTL29 | Updates 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. Note that this publishes an update, it does not mutate the original message if passed in. | +| updateMessage(Message, operation?: MessageOperation, params?: `Dict`) => io UpdateDeleteResult ||| RTL29 | Updates 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. Note that this publishes an update, it does not mutate the original message if passed in. | || `Message` ||| A [`Message`]{@link Message} object containing a populated `serial` field and the fields to update. | || `operation` ||| An optional [`MessageOperation`]{@link MessageOperation} object containing metadata about the update operation. | -|| `params` ||| Optional parameters sent as part of the query string. | -| deleteMessage(Message, operation?: MessageOperation, params?: `Dict`) => io ||| RTL30 | 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 (meaning that if you for example want the `MESSAGE_DELETE` to have an empty data, you should explicitly set the `data` to an empty object). | +|| `params` ||| Optional parameters (ignored for realtime). | +||| `UpdateDeleteResult` || An [`UpdateDeleteResult`]{@link UpdateDeleteResult} object containing the serial of the new version of the message. | +| deleteMessage(Message, operation?: MessageOperation, params?: `Dict`) => io UpdateDeleteResult ||| RTL30 | 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 (meaning that if you for example want the `MESSAGE_DELETE` to have an empty data, you should explicitly set the `data` to an empty object). | || `Message` ||| A [`Message`]{@link Message} object containing a populated `serial` field. | || `operation` ||| An optional [`MessageOperation`]{@link MessageOperation} object containing metadata about the delete operation. | -|| `params` ||| Optional parameters sent as part of the query string. | +|| `params` ||| Optional parameters (ignored for realtime). | +||| `UpdateDeleteResult` || An [`UpdateDeleteResult`]{@link UpdateDeleteResult} object containing the serial of the new version of the message. | +| appendMessage(Message, operation?: MessageOperation, params?: `Dict`) => io UpdateDeleteResult ||| RTL32 | 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. | +|| `Message` ||| A [`Message`]{@link Message} object containing a populated `serial` field and the data to append. | +|| `operation` ||| An optional [`MessageOperation`]{@link MessageOperation} object containing metadata about the append operation. | +|| `params` ||| Optional parameters (ignored for realtime). | +||| `UpdateDeleteResult` || An [`UpdateDeleteResult`]{@link UpdateDeleteResult} object containing the serial of the new version of the message. | | getMessageVersions(serialOrMsg: String \| Message, params?: `Dict`) => io `PaginatedResult` ||| RTL31 | Retrieves all historical versions of a specific message, ordered by version. This includes the original message and all subsequent updates or delete operations. | || `serialOrMsg` ||| Either the serial identifier string of the message whose versions are to be retrieved, or a [`Message`]{@link Message} object containing a populated `serial` field. | || `params` ||| Optional parameters sent as part of the query string. | @@ -405,6 +425,22 @@ Contains metadata about a message update or delete operation. | description?: String ||| MOP2b | Optional human-readable description of the operation. | | metadata?: `Dict` ||| MOP2c | Optional dictionary of key-value pairs containing additional metadata about the operation. | +## class PublishResult + +Contains the result of a publish operation. + +| Method / Property | Parameter | Returns | Spec | Description | +|---|---|---|---|---| +| serials: [String?] ||| PBR2a | 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. | + +## class UpdateDeleteResult + +Contains the result of an update or delete message operation. + +| Method / Property | Parameter | Returns | Spec | Description | +|---|---|---|---|---| +| versionSerial: String? ||| UDR2a | The serial of the version of the updated or deleted message. Will be null if the message was superseded by a subsequent update before it could be published. | + ## class ChannelProperties Describes the properties of the channel state. diff --git a/textile/features.textile b/textile/features.textile index 88c2caff..8809c3d8 100644 --- a/textile/features.textile +++ b/textile/features.textile @@ -330,6 +330,8 @@ h3(#rest-channel). RestChannel ** @(RSL1b)@ When @name@ and @data@ (or a @Message@) is provided, a single message is published to Ably ** @(RSL1c)@ When an array of @Message@ objects is provided, a single request is made to Ably ** @(RSL1d)@ Indicates an error if the message was not successfully published to Ably +** @(RSL1n)@ On success, returns a @PublishResult@ object containing the serials of the published messages. The API response body will contain a superset of the fields of a @PublishResult@. +*** @(RSL1n1)@ In SDKs for which adding a response value would be a breaking API change and where such changes are idiomatically rare may implement any appropriate alternative that would allow a user who wishes to get a serials array on publish to do so. ** @(RSL1e)@ Allows @name@ and/or @data@ to be @null@. If any of the values are @null@, that property is not sent to Ably, e.g. a payload with a @null@ value for @data@ would be sent as @{"name":"click"}@ ** @(RSL1m)@ The @Message.clientId@ must be left alone (that is, it will be there if it was set in the @Message@ object passed to @publish()@, else left unset). The client library must not try and set it from the client-library-wide @clientId@; that is achieved by @RSA7e@ for basic auth or implicit in the credentials for token auth. The following tests can be used to check for correct clientId handling: *** @(RSL1m1)@ Publishing a @Message@ with no clientId when the clientId is set to some value in the client options should result in a message received with the @clientId@ property set to that value @@ -386,33 +388,21 @@ h3(#rest-channel). RestChannel *** @(RSL11a1)@ In languages where method overloading is supported, the first argument should also accept a @Message@ object (which must contain a populated @serial@ field) ** @(RSL11b)@ The SDK must send a GET request to the endpoint @/channels/{channelName}/messages/{serial}@ ** @(RSL11c)@ Returns the decoded @Message@ object for the specified message serial -* @(RSL12)@ @RestChannel#updateMessage@ function: -** @(RSL12a)@ Takes a first argument of a @Message@ object (which must contain a populated @serial@ field), an optional second argument of a @MessageOperation@ object, and an optional third argument of @Dict@ publish params -** @(RSL12b)@ The SDK must send a PATCH to @/channels/{channelName}/messages/{serial}@, with any @params@ sent in the querystring. The request body consists of an object with the following fields: -*** @(RSL12b1)@ @serial@ - the serial identifier of the message being updated -*** @(RSL12b2)@ @operation@ - the @MessageOperation@ object if provided -*** @(RSL12b3)@ @name@ - the message name from the @Message@ object if provided -*** @(RSL12b4)@ @data@ - the message data from the @Message@ object if provided, encoded per @RSL4@ -*** @(RSL12b5)@ @encoding@ - the encoding field set according to @RSL4@ if @data@ was encoded -*** @(RSL12b6)@ @extras@ - the extras field from the @Message@ object if provided -** @(RSL12c)@ The body must be encoded to the appropriate format per @RSC8@ -** @(RSL12d)@ The function signals success or failure according to the http response per our normal REST standards. It does not return a value on success -* @(RSL13)@ @RestChannel#deleteMessage@ function: -** @(RSL13a)@ Takes a first argument of a @Message@ object (which must contain a populated @serial@ field), an optional second argument of a @MessageOperation@ object, and an optional third argument of @Dict@ publish params -** @(RSL13b)@ The SDK must send a POST to @/channels/{channelName}/messages/{serial}/delete@, with any @params@ sent in the querystring. The request body consists of an object with the following fields: -*** @(RSL13b1)@ @serial@ - the serial identifier of the message being deleted -*** @(RSL13b2)@ @operation@ - the @MessageOperation@ object if provided -*** @(RSL13b3)@ @name@ - the message name from the @Message@ object if provided -*** @(RSL13b4)@ @data@ - the message data from the @Message@ object if provided, encoded per @RSL4@ -*** @(RSL13b5)@ @encoding@ - the encoding field set according to @RSL4@ if @data@ was encoded -*** @(RSL13b6)@ @extras@ - the extras field from the @Message@ object if provided -** @(RSL13c)@ The body must be encoded to the appropriate format per @RSC8@ -** @(RSL13d)@ The function signals success or failure according to the http response per our normal REST standards. It does not return a value on success +* @(RSL12)@ This clause has been replaced by "@RSL15@":#RSL15. It was valid up to and including specification version 5.0.0. +* @(RSL13)@ This clause has been replaced by "@RSL15@":#RSL15. It was valid up to and including specification version 5.0.0. * @(RSL14)@ @RestChannel#getMessageVersions@ function: ** @(RSL14a)@ Takes a first argument a @serial@ string of the message whose versions are to be retrieved, and an optional second argument of @Dict@ params *** @(RSL14a1)@ In languages where method overloading is supported, the first argument should also accept a @Message@ object (which must contain a populated @serial@ field) ** @(RSL14b)@ The SDK must send a GET request to the endpoint @/channels/{channelName}/messages/{serial}/versions@ ** @(RSL14c)@ Returns a @PaginatedResult@ +* @(RSL15)@ @RestChannel#updateMessage@, @RestChannel#deleteMessage@, and @RestChannel#appendMessage@ functions: +** @(RSL15a)@ Take a first argument of a @Message@ object (which must contain a populated @serial@ field), an optional second argument of a @MessageOperation@ object, and an optional third argument of @Dict@ publish params +** @(RSL15b)@ The SDK must send a PATCH to @/channels/{channelName}/messages/{serial}@, with any @params@ sent in the querystring. The request body is a @Message@ object (encoded per "RSL4":#RSL4) with whatever fields were in the user-supplied @Message@, in addition to: +*** @(RSL15b1)@ @action@ - set to @MESSAGE_UPDATE@ for @updateMessage()@, @MESSAGE_DELETE@ for @deleteMessage()@, and @MESSAGE_APPEND@ for @appendMessage()@. +*** @(RSL15b7)@ @version@ - set to the @MessageOperation@ object if provided. +** @(RSL15c)@ The SDK must not mutate the user-supplied @Message@ object. +** @(RSL15d)@ The request body must be encoded to the appropriate format per @RSC8@. +** @(RSL15e)@ On success, returns an @UpdateDeleteResult@ object. The API response body will contain a superset of the fields of a @UpdateDeleteResult@. Indicates an error if the operation was not successful. h3(#plugins). Plugins * @(PC1)@ Specific client library features that are not commonly used may be supplied as independent libraries, as plugins, in order to avoid excessively bloating the client library. Although such plugins are packaged as independent libraries, they are still considered logically to be part of the client library code and, as such, may be tightly coupled with the client library implementation. The client library can be assumed to be aware of the plugin specific type and capabilities, and such plugins may by design be coupled to a specific version of the client library. @@ -764,6 +754,8 @@ h3(#realtime-channel). RealtimeChannel ** @(RTL6a)@ Messages are encoded in the same way as the @RestChannel#publish@ method, and "RSL1i":#RSL1i (size limit) applies similarly *** @(RTL6a1)@ "RSL1k":#RSL1k (@idempotentRestPublishing@ option), "RSL1j1":#RSL1j1 (idempotent publishing test), and "RSL1l":#RSL1l (@publish(Message, params)@ form) do not apply to realtime publishes ** @(RTL6b)@ An optional callback can be provided to the @#publish@ method that is called when the message is successfully delivered or upon failure with the appropriate @ErrorInfo@ error. A test should exist to publish lots of messages on a few connections to ensure all message success callbacks are called for all messages published +** @(RTL6j)@ On success, returns a @PublishResult@ object containing the serials of the published messages. The serials are obtained from the @ACK@ @ProtocolMessage@ response (see "TR4s":#TR4s). +*** @(RTL6j1)@ In SDKs for which adding a response value would be a breaking API change and where such changes are idiomatically rare may implement any appropriate alternative that would allow a user who wishes to get a serials array on publish to do so. ** @(RTL6i)@ Expects either a @Message@ object, an array of @Message@ objects, or a @name@ string and @data@ payload: *** @(RTL6i1)@ When @name@ and @data@ (or a @Message@) is provided, a single @ProtocolMessage@ containing one @Message@ is published to Ably *** @(RTL6i2)@ When an array of @Message@ objects is provided, a single @ProtocolMessage@ is used to publish all @Message@ objects in the array. @@ -852,9 +844,16 @@ h3(#realtime-channel). RealtimeChannel ** @(RTL25b)@ Else, calls @#once@ with the given state and listener. * @(RTL26)@ @RealtimeChannel#annotations@ attribute contains the @RealtimeAnnotations@ object for this channel * @(RTL28)@ @RealtimeChannel#getMessage@ function: same as @RestChannel#getMessage@ -* @(RTL29)@ @RealtimeChannel#updateMessage@ function: same as @RestChannel#updateMessage@ -* @(RTL30)@ @RealtimeChannel#deleteMessage@ function: same as @RestChannel#deleteMessage@ +* @(RTL29)@ This clause has been replaced by "@RTL32@":#RTL32. It was valid up to and including specification version 5.0.0. +* @(RTL30)@ This clause has been replaced by "@RTL32@":#RTL32. It was valid up to and including specification version 5.0.0. * @(RTL31)@ @RealtimeChannel#getMessageVersions@ function: same as @RestChannel#getMessageVersions@ +* @(RTL32)@ @RealtimeChannel#updateMessage@, @RealtimeChannel#deleteMessage@, and @RealtimeChannel#appendMessage@ functions: +** @(RTL32a)@ Take a first argument of a @Message@ object (which must contain a populated @serial@ field), and an optional second argument of a @MessageOperation@ object +** @(RTL32b)@ The SDK must send a @MESSAGE@ @ProtocolMessage@ to Ably, containing a single @Message@ populated with whatever fields were in the user-supplied @Message@, in addition to: +*** @(RTL32b1)@ @action@ - set to @MESSAGE_UPDATE@ for @updateMessage()@, @MESSAGE_DELETE@ for @deleteMessage()@, and @MESSAGE_APPEND@ for @appendMessage()@ +*** @(RTL32b2)@ @version@ - set to the @MessageOperation@ object if provided. +** @(RTL32c)@ The SDK must not mutate the user-supplied @Message@ object. +** @(RTL32d)@ On success, returns an @UpdateDeleteResult@ object containing the version serial of the published update, obtained from the first element of the @serials@ array of the "TR4s":#TR4s @res@ field of the @ACK@. Indicates an error if the operation was not successful. h3(#realtime-presence). RealtimePresence @@ -1466,7 +1465,7 @@ h3(#types). Data types h4. Message * @(TM1)@ A @Message@ represents an individual message to be sent or received via the Ably Realtime service. -* @(TM5)@ @Message@ @Action@ enum has the following values in order from zero: @MESSAGE_CREATE@, @MESSAGE_UPDATE@, @MESSAGE_DELETE@, @META@, @MESSAGE_SUMMARY@ +* @(TM5)@ @Message@ @Action@ enum has the following values in order from zero: @MESSAGE_CREATE@, @MESSAGE_UPDATE@, @MESSAGE_DELETE@, @META@, @MESSAGE_SUMMARY@, @MESSAGE_APPEND@ * @(TM2)@ The attributes available in a @Message@ are: ** @(TM2a)@ @id@ string - unique ID for this message. This attribute is always populated for messages received over REST. For messages received over Realtime, if the message does not contain an @id@, it should be set to @protocolMsgId:index@, where @protocolMsgId@ is the id of the @ProtocolMessage@ encapsulating it, and @index@ is the index of the message inside the @messages@ array of the @ProtocolMessage@ ** @(TM2b)@ @clientId@ string @@ -1780,6 +1779,7 @@ h4. ProtocolMessage ** @(TR4m)@ @timestamp@ time in milliseconds since epoch ** @(TR4q)@ @params@ @Dict@ key-value pairs ** @(TR4r)@ @state@ Array of @ObjectMessage@ objects +** @(TR4s)@ @res@ Array of @PublishResult@ objects - present in @ACK@ @ProtocolMessages@, contains one @PublishResult@ per acknowledged @ProtocolMessage@ in order, each containing the @serials@ of the messages that were published h4. PaginatedResult @@ -1971,6 +1971,7 @@ h4. BatchPublishSuccessResult * @(BPR2)@ The attributes of @BatchPublishSuccessResult@ consist of: ** @(BPR2a)@ @channel@ string - the name of the channel ** @(BPR2b)@ @messageId@ string - a string containing the @messageId@ prefix for the published message(s) +** @(BPR2c)@ @serials@ array of @String?@ - 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. h4. BatchPublishFailureResult @@ -1993,6 +1994,18 @@ h4. BatchPresenceFailureResult ** @(BGF2a)@ @channel@ string - the name of the channel ** @(BGF2b)@ @error@ @ErrorInfo@ - @ErrorInfo@ indicating the reason the presence request failed for the given channel +h4. PublishResult + +* @(PBR1)@ Contains the result of a publish operation +* @(PBR2)@ The attributes of @PublishResult@ consist of: +** @(PBR2a)@ @serials@ array of @String?@ - 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. + +h4. UpdateDeleteResult + +* @(UDR1)@ Contains the result of an update or delete message operation +* @(UDR2)@ The attributes of @UpdateDeleteResult@ consist of: +** @(UDR2a)@ @versionSerial@ @String?@ - the new version serial string of the updated or deleted message. Will be null if the message was superseded by a subsequent update before it could be published. + h4. TokenRevocationTargetSpecifier * @(TRT1)@ Describes which tokens should be affected by a token revocation request @@ -2388,11 +2401,16 @@ class RestChannel: // RSL* limit: int api-default 100 // RSL2b3 ) => io PaginatedResult // RSL2 status() => ChannelDetails // RSL8 - publish(Message, params?: Dict) => io // RSL1 - publish([Message], params?: Dict) => io // RSL1 - publish(name: String?, data: Data?) => io // RSL1 + publish(Message, params?: Dict) => io PublishResult // RSL1 + publish([Message], params?: Dict) => io PublishResult // RSL1 + publish(name: String?, data: Data?) => io PublishResult // RSL1 setOptions(options: ChannelOptions) => io // RSL7 - note asynchronous return value for // compatibility with RealtimeChannel#setOptions; not required for REST-only libraries + getMessage(serial: String) => io Message // RSL11 + updateMessage(Message, operation?: MessageOperation, params?: Dict) => io UpdateDeleteResult // RSL12 + deleteMessage(Message, operation?: MessageOperation, params?: Dict) => io UpdateDeleteResult // RSL13 + getMessageVersions(serial: String, params?: Dict) => io PaginatedResult // RSL14 + appendMessage(Message, operation?: MessageOperation, params?: Dict) => io UpdateDeleteResult // RSL15 // Only on platforms that support receiving push notifications: push: PushChannel // RSH7 @@ -2419,9 +2437,9 @@ class RealtimeChannel: // RTL* limit: int api-default 100, // RTL10a untilAttach: Bool default false // RTL10b ) => io PaginatedResult // RTL10 - publish(Message) => io // RTL6, RTL6i - publish([Message]) => io // RTL6, RTL6i - publish(name: String?, data: Data?) => io // RTL6, RTL6i + publish(Message) => io PublishResult // RTL6, RTL6i + publish([Message]) => io PublishResult // RTL6, RTL6i + publish(name: String?, data: Data?) => io PublishResult // RTL6, RTL6i subscribe((Message) ->) => io ChannelStateChange? // RTL7, RTL7a subscribe(String, (Message) ->) => io ChannelStateChange? // RTL7, RTL7b subscribe(MessageFilter, (Message) ->) io ChannelStateChange? // RTL22 @@ -2430,6 +2448,11 @@ class RealtimeChannel: // RTL* unsubscribe(String, (Message) ->) // RTL8, RTL8b unsubscribe(MessageFilter, (Message) ->) // RTL22 setOptions(options: ChannelOptions) => io // RTL16 + getMessage(serial: String) => io Message // RTL28 + updateMessage(Message, operation?: MessageOperation) => io UpdateDeleteResult // RTL29 + deleteMessage(Message, operation?: MessageOperation) => io UpdateDeleteResult // RTL30 + getMessageVersions(serial: String, params?: Dict) => io PaginatedResult // RTL31 + appendMessage(Message, operation?: MessageOperation) => io UpdateDeleteResult // RTL32 class MessageFilter: // MFI* isRef: bool // MFI2a @@ -2604,6 +2627,7 @@ enum MessageAction: // TM5 MESSAGE_DELETE // TM5 META // TM5 MESSAGE_SUMMARY // TM5 + MESSAGE_APPEND // TM5 enum ObjectOperationAction: // OOP2, internal MAP_CREATE // OOP2 @@ -2760,6 +2784,7 @@ class ProtocolMessage: // internal timestamp: Time? // TR4m params: Dict? // TR4q, RTL4k state: [ObjectMessage]? // TR4r + res: [PublishResult]? // TR4s enum ProtocolMessageAction: // internal HEARTBEAT // TR2 @@ -2978,6 +3003,7 @@ class BatchPublishSpec: class BatchPublishSuccessResult: channel: string // BPR2a messageId: string // BPR2b + serials: [String?] // BPR2c class BatchPublishFailureResult: channel: string // BPF2a @@ -2991,6 +3017,12 @@ class BatchPresenceFailureResult channel: string // BGF2a error: ErrorInfo // BGF2b +class PublishResult: + serials: [String?] // PBR2a + +class UpdateDeleteResult: + versionSerial: String? // UDR2a + class TokenRevocationTargetSpecifier: type: string // TRT2a value: string // TRT2b diff --git a/textile/protocol.textile b/textile/protocol.textile index 96a8be4f..25f694c1 100644 --- a/textile/protocol.textile +++ b/textile/protocol.textile @@ -38,7 +38,7 @@ Each Protocol Message has an @action@ that indicates the nature of the message. Heartbeat messages are not exposed to the client app, and are silently consumed in the transport layer of the client library. However, client library unit tests will typically wish to check for the presence of a heartbeat to confirm that a connection is intact, and therefore client libraries should expose some means for tests to observe the occurrence of heartbeats.

No other message fields are populated in a heartbeat message. -- ACK (1) := An acknowledgement message, sent from the service to a client, to confirm receipt of one or more messages published by the client. Further details of the acknowledgement protocol is given below. +- ACK (1) := An acknowledgement message, sent from the service to a client, to confirm receipt of one or more messages published by the client. The @ACK@ message contains a @res@ field with an array of @PublishResult@ objects, one per acknowledged @ProtocolMessage@, each containing the @serials@ of the messages that were published. Further details of the acknowledgement protocol is given below. - NACK (2) := An negative acknowledgement message, sent from the service to a client, that indicates failure of one or more messages published by the client. Further details of the acknowledgement protocol is given below. @@ -128,6 +128,8 @@ ProtocolMessages are populated with one or more of the following fields. - i64 @timestamp@ := An optional timestamp, applied by the service in messages sent to the client, to indicate the system time at which the message was sent. Note that this differs from the timestamp field of a @Message@ or @PresenceMessage@ which is an indication of the timestamp of receipt of that message by the system.

Currently there are no requirements for the client library to process or populate the timestamp. +- list @res@ := Present in @ACK@ Protocol Messages. Contains one @PublishResult@ per acknowledged @ProtocolMessage@ in order. Each @PublishResult@ contains a @serials@ array with the 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. + h2(#other-message-structs). Other message object The protocol relies on a number of structs embedded in Protocol Messages. @@ -213,6 +215,20 @@ Messages sent from the service to the client will contain a clientId if one is a - string connectionId := optional public connection ID of the message publisher. The field is expected to be empty in messages sent from a client to the service. +- i32 action := The message action, indicating the type of message operation. The following values are defined: @MESSAGE_CREATE@ (0) for new messages, @MESSAGE_UPDATE@ (1) for updates to existing messages, @MESSAGE_DELETE@ (2) for message deletions, @META@ (3) for metadata messages, @MESSAGE_SUMMARY@ (4) for summary messages, and @MESSAGE_APPEND@ (5) for appending data to existing messages. When publishing an update, delete, or append, the @serial@ field must be populated to identify the target message. + +- string serial := An opaque string that uniquely identifies the message. Used when updating, deleting, or appending to an existing message. + +- object version := Contains information about the latest version of a message, including version serial, timestamp, and optional operation metadata (clientId, description, metadata) for update/delete/append operations. + +h3. PublishResult + +Contains the result of a publish operation, returned in @ACK@ Protocol Messages. + +The members are as follows. + +- list @serials@ := An array of message serials corresponding 1:1 to the messages that were published in the acknowledged @ProtocolMessage@. A serial may be null if the message was discarded due to a configured conflation rule. + h3. Presence Message This is an individual channel presence update, as defined in the Thrift @TPresence@ struct. @@ -232,7 +248,7 @@ If the connectionId of an already-entered member changes (eg in the situation th h2(#message-acknowledgement). Message acknowledgement protocol -The Ably client API allows a caller to provide a success callback when publishing messages, or presence updates, to the Ably service. The callback is called, either with success or a failure code, once the Ably service has indicated whether or not it has processed the message successfully. The callback is not simply an indication that the message sent without error; it is confirmation that the service has processed the message sufficiently that its onward delivery to relevant attached clients is now guaranteed. +The Ably client API allows a caller to await the result of publishing messages, or presence updates, to the Ably service. The result indicates success (with a @PublishResult@ containing the serials of published messages) or failure (with an error code), once the Ably service has indicated whether or not it has processed the message successfully. A successful result is not simply an indication that the message sent without error; it is confirmation that the service has processed the message sufficiently that its onward delivery to relevant attached clients is now guaranteed. In the Comet transport, success or failure is indicated on a per-call basis with an @ACK@ or @NACK@ Message body in the HTTP response to the @send@ API call. @@ -246,7 +262,9 @@ An @ACK@ message contains a @msgSerial@ and @count@ value. Receipt of this messa have been processed successfully. -Similarly, a @NACK@ message contains a @msgSerial@ and @count@ value and usually also an @error@ value. Receipt of this message signifies that the messages whose serial numbers are: +The @ACK@ message also contains a @res@ field, which is an array of @PublishResult@ objects corresponding 1:1 to the acknowledged @ProtocolMessages@ in @msgSerial@ order. Each @PublishResult@ contains a @serials@ array with the message serials for each @Message@ in the corresponding @ProtocolMessage@. This allows the client library to return a @PublishResult@ from publish operations, providing callers with the serials of their published messages. A serial in the array may be null if the message was discarded due to a configured conflation rule. + +A @NACK@ message contains a @msgSerial@ and @count@ value and usually also an @error@ value. Receipt of this message signifies that the messages whose serial numbers are:
 { msgSerial ... msgSerial + count - 1 }