Skip to content

Commit fc46aed

Browse files
committed
Add separate page for Objects spec, RealtimeChannel.objects and OBJECT_SYNC spec
1 parent 2266a50 commit fc46aed

3 files changed

Lines changed: 150 additions & 5 deletions

File tree

scripts/find-duplicate-spec-items

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# Script to detect duplicate spec IDs in the client library spec
44
# This tends to happen when concurrent spec PRs are merged
55

6-
SPEC_FILES = ["features", "chat-features"]
6+
SPEC_FILES = ["features", "chat-features", "objects-features"]
77

88
has_errors = false
99

textile/features.textile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,7 @@ h3(#plugins). Plugins
386386
* @(PC2)@ No generic plugin interface is specified, and therefore there is no common API exposed by all plugins. However, for type-safety, the opaque interface @Plugin@ should be used in strongly-typed languages as the type of the @ClientOptions.plugins@ collection as per "TO3o":#TO3o.
387387
* @(PC3)@ A plugin provided with the @PluginType@ enum key value of @vcdiff@ should be capable of decoding "vcdiff"-encoded messages. It must implement the @VCDiffDecoder@ interface and the client library must be able to use it by casting it to this interface.
388388
** @(PC3a)@ The base argument of the @VCDiffDecoder.decode@ method should receive the stored base payload of the last message on a channel as specified by "RTL19":#RTL19. If the base payload is a string it should be encoded to binary using UTF-8 before being passed as base argument of the @VCDiffDecoder.decode@ method.
389-
* @(PC5)@ A plugin provided with the @PluginType@ enum key value of @Objects@ should provide the "RealtimeObjects":#RTO1 feature functionality for realtime channels ("RTL27":#RTL27). The plugin object itself is not expected to provide a public API. The type of the plugin object, and how it enables the Objects feature for a realtime channel, are left for individual implementations to decide.
389+
* @(PC5)@ A plugin provided with the @PluginType@ enum key value of @Objects@ should provide the "RealtimeObjects":../objects-features#RTO1 feature functionality for realtime channels ("RTL27":#RTL27). The plugin object itself is not expected to provide a public API. The type of the plugin object, and how it enables the Objects feature for a realtime channel, are left for individual implementations to decide.
390390
* @(PC4)@ A client library is allowed to accept plugins other than those specified in this specification, through the use of additional @ClientOptions.plugins@ keys defined by that library. The library is responsible for defining the interface of these plugins, and for making sure that these keys do not clash with the keys defined in this specification.
391391

392392
h3(#plugin-type). PluginType
@@ -680,7 +680,7 @@ h3(#realtime-channels). Channels
680680
h3(#realtime-channel). RealtimeChannel
681681

682682
* @(RTL23)@ @RealtimeChannel#name@ attribute is a string containing the channel’s name
683-
* @(RTL1)@ As soon as a @RealtimeChannel@ becomes attached, all incoming messages and presence messages (where 'incoming' is defined as 'received from Ably over the realtime transport') are processed and emitted where applicable. @PRESENCE@ and @SYNC@ messages are passed to the @RealtimePresence@ object ensuring it maintains a map of current members on a channel in realtime
683+
* @(RTL1)@ As soon as a @RealtimeChannel@ becomes attached, all incoming messages, presence messages and object messages (where 'incoming' is defined as 'received from Ably over the realtime transport') are processed and emitted where applicable. @PRESENCE@ and @SYNC@ messages are passed to the @RealtimePresence@ object ensuring it maintains a map of current members on a channel in realtime. @OBJECT@ and @OBJECT_SYNC@ messages are passed to the @RealtimeObjects@ object ensuring it maintains an up-to-date representation of objects on a channel in realtime
684684
* @(RTL2)@ The @RealtimeChannel@ implements @EventEmitter@ and emits @ChannelEvent@ events, where a @ChannelEvent@ is either a @ChannelState@ or @UPDATE@, and a @ChannelState@ is either @INITIALIZED@, @ATTACHING@, @ATTACHED@, @DETACHING@, @DETACHED@, @SUSPENDED@ and @FAILED@
685685
** @(RTL2a)@ It emits a @ChannelState@ @ChannelEvent@ for every channel state change
686686
** @(RTL2g)@ It emits an @UPDATE@ @ChannelEvent@ for changes to channel conditions for which the @ChannelState@ (e.g. @ATTACHED@) does not change, unless explicitly prevented by a more specific condition (see "RTL12":#RTL12). (The library must never emit a @ChannelState@ @ChannelEvent@ for a state equal to the previous state)
@@ -779,7 +779,7 @@ h3(#realtime-channel). RealtimeChannel
779779
* @(RTL9)@ @RealtimeChannel#presence@ attribute:
780780
** @(RTL9a)@ Returns the @RealtimePresence@ object for this channel
781781
* @(RTL27)@ @RealtimeChannel#objects@ attribute:
782-
** @(RTL27a)@ Returns the @RealtimeObjects@ object for this channel "RTO1":#RTO1
782+
** @(RTL27a)@ Returns the @RealtimeObjects@ object for this channel "RTO1":../objects-features#RTO1
783783
** @(RTL27b)@ It is a programmer error to access this property without first providing the @Objects@ plugin ("PC5":#PC5) in the client options. This programmer error should be handled in an idiomatic fashion; if this means accessing the property should throw an error, then the error should be an @ErrorInfo@ with @statusCode@ 400 and @code@ 40019.
784784
* @(RTL10)@ @RealtimeChannel#history@ function:
785785
** @(RTL10a)@ Supports all the same params as @RestChannel#history@
@@ -938,7 +938,7 @@ then the @enter@ request results in an error immediately.
938938

939939
h3(#realtime-objects). RealtimeObjects
940940

941-
Reserved for @RealtimeObjects@ feature specification. Reserved spec points: @RTO@, @RTLO@, @RTLC@, @RTLM@
941+
Reserved for @RealtimeObjects@ feature specification, see "objects-features":../objects-features. Reserved spec points: @RTO@, @RTLO@, @RTLC@, @RTLM@
942942

943943
h3(#realtime-annotations). RealtimeAnnotations
944944

textile/objects-features.textile

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
---
2+
title: Objects Features
3+
section: client-lib-development-guide
4+
index: 65
5+
jump_to:
6+
Help with:
7+
- Objects Features Overview#overview
8+
---
9+
10+
h2(#overview). Overview
11+
12+
This document outlines the feature specification for the Objects feature of the Realtime system. It is currently under development and stored separately from the main specification to simplify the initial implementation of the feature in other SDKs. Once completed, it will be moved to the main "features":../features spec.
13+
14+
Objects feature enables clients to store shared data as "objects" on a channel. When an object is updated, changes are automatically propagated to all subscribed clients in realtime, ensuring each client always sees the latest state.
15+
16+
h3(#realtime-objects). RealtimeObjects
17+
18+
* @(RTO1)@ @Objects#getRoot@ function:
19+
** @(RTO1a)@ Requires the @OBJECT_SUBSCRIBE@ channel mode to be granted per "RTO2":#RTO2
20+
** @(RTO1b)@ If the channel is in the @DETACHED@ or @FAILED@ state, the library should indicate an error with code 90001
21+
** @(RTO1c)@ Waits for the objects sync sequence to complete and for "RTO5c":#RTO5c to finish
22+
** @(RTO1d)@ Returns the object with id @root@ from the internal @ObjectsPool@ as a @LiveMap@
23+
* @(RTO2)@ Various object operations may require a specific channel mode to be set on a channel in order to be performed. If a specific channel mode is required by an operation, then:
24+
** @(RTO2a)@ If the channel is in the @ATTACHED@ state, the presence of the required channel mode is checked against the set of channel modes granted by the server per "RTL4m":../features#RTL4m :
25+
*** @(RTO2a1)@ If the channel mode is in the set, the operation is allowed
26+
*** @(RTO2a2)@ If the channel mode is missing, unless otherwise specified by the operation, the library should indicate an error with code 40024 stating that the operation cannot be performed without the required channel mode
27+
** @(RTO2b)@ Otherwise, a best-effort attempt is made, and the channel mode is checked against the set of channel modes requested by the user per "TB2d":../features#TB2d :
28+
*** @(RTO2b1)@ If the channel mode is in the set, the operation is allowed
29+
*** @(RTO2b2)@ If the channel mode is missing, unless otherwise specified by the operation, the library should indicate an error with code 40024 stating that the operation cannot be performed without the required channel mode
30+
* @(RTO3)@ An internal @ObjectsPool@ should be used to maintain the list of objects present on a channel
31+
** @(RTO3a)@ @ObjectsPool@ is a @Dict<String, LiveObject>@ - a map of @LiveObject@s keyed by "@objectId@":../features#OST2a string
32+
** @(RTO3b)@ It must always contain a @LiveMap@ object with id @root@
33+
* @(RTO4)@ When a channel @ATTACHED@ @ProtocolMessage@ is received, the @ProtocolMessage@ may contain a @HAS_OBJECTS@ bit flag indicating that it will perform an objects sync, see "TR3":../features#TR3 . Note that this does not imply that objects are definitely present on the channel, only that there may be; the @OBJECT_SYNC@ message may be empty
34+
** @(RTO4a)@ If the @HAS_OBJECTS@ flag is 1, the server will shortly perform an @OBJECT_SYNC@ sequence as described in "RTO5":#RTO5
35+
** @(RTO4b)@ If the @HAS_OBJECTS@ flag is 0 or there is no @flags@ field, the sync sequence must be considered complete immediately, and the client library must perform the following actions in order:
36+
*** @(RTO4b1)@ All objects except the one with id @root@ must be removed from the internal @ObjectsPool@
37+
*** @(RTO4b2)@ The data for the @LiveMap@ with id @root@ must be cleared by setting it to a zero-value per "RTLM4":#RTLM4
38+
*** @(RTO4b3)@ The @SyncObjectsPool@ must be cleared
39+
*** @(RTO4b4)@ Perform the actions for objects sync completion as described in "RTO5c":#RTO5c
40+
* @(RTO5)@ The realtime system reserves the right to initiate an objects sync of the objects on a channel at any point once a channel is attached. A server initiated objects sync provides Ably with a means to send a complete list of objects present on the channel at any point
41+
** @(RTO5a)@ When an @OBJECT_SYNC@ @ProtocolMessage@ is received with a @channel@ attribute matching the channel name, the client library must parse the @channelSerial@ attribute:
42+
*** @(RTO5a1)@ The @channelSerial@ is used as the sync cursor and is a two-part identifier: @<sequence id>:<cursor value>@
43+
*** @(RTO5a2)@ If a new sequence id is sent from Ably, the client library must treat it as the start of a new objects sync sequence, and any previous in-flight sync must be discarded:
44+
**** @(RTO5a2a)@ The current @SyncObjectsPool@ list must be cleared
45+
*** @(RTO5a3)@ If the sequence id matches the previously received sequence id, the client library should continue the sync process
46+
*** @(RTO5a4)@ The objects sync sequence for that sequence identifier is considered complete once the cursor is empty; that is when the @channelSerial@ looks like @<sequence id>:@
47+
*** @(RTO5a5)@ An @OBJECT_SYNC@ may also be sent with no @channelSerial@ attribute. In this case, the sync data is entirely contained within the @ProtocolMessage@
48+
** @(RTO5b)@ During the sync sequence, the @ObjectMessage.object@ values from incoming @OBJECT_SYNC@ @ProtocolMessage@s must be temporarily stored in the internal @SyncObjectsPool@ list
49+
** @(RTO5c)@ When the objects sync has completed, the client library must perform the following actions in order:
50+
*** @(RTO5c1)@ For each @ObjectState@ member in the @SyncObjectsPool@ list:
51+
**** @(RTO5c1a)@ If an object with @ObjectState.objectId@ exists in the internal @ObjectsPool@:
52+
***** @(RTO5c1a1)@ Override the internal data for the object as per "RTLC6":#RTLC6, "RTLM6":#RTLM6
53+
**** @(RTO5c1b)@ If an object with @ObjectState.objectId@ does not exist in the internal @ObjectsPool@:
54+
***** @(RTO5c1b1)@ Create a new @LiveObject@ using the data from @ObjectState@ and add it to the internal @ObjectsPool@:
55+
****** @(RTO5c1b1a)@ If @ObjectState.counter@ is present, create a zero-value @LiveCounter@ (per "RTLC4":#RTLC4), set its private @objectId@ equal to @ObjectState.objectId@ and override its internal data using the current @ObjectState@ per "RTLC6":#RTLC6
56+
****** @(RTO5c1b1b)@ If @ObjectState.map@ is present, create a zero-value @LiveMap@ (per "RTLM4":#RTLM4), set its private @objectId@ equal to @ObjectState.objectId@, set its private @semantics@ equal to @ObjectState.map.semantics@ and override its internal data using the current @ObjectState@ per "RTLM6":#RTLM6
57+
****** @(RTO5c1b1c)@ Otherwise, log a warning that an unsupported object state message has been received, and discard the current @ObjectState@ without taking any action
58+
*** @(RTO5c2)@ Remove any objects from the internal @ObjectsPool@ for which @objectId@s were not received during the sync sequence
59+
**** @(RTO5c2a)@ The object with ID @root@ must not be removed from @ObjectsPool@, as per "RTO3b":#RTO3b
60+
*** @(RTO5c3)@ Clear any stored sync sequence identifiers and cursor values
61+
*** @(RTO5c4)@ The @SyncObjectsPool@ must be cleared
62+
* @(RTO6)@ When needed, a zero-value object can be created if it does not exist in the internal @ObjectsPool@ for an @objectId@, in the following way:
63+
** @(RTO6a)@ If an object with @objectId@ exists in @ObjectsPool@, do not create a new object
64+
** @(RTO6b)@ The expected type of the object can be inferred from the provided @objectId@:
65+
*** @(RTO6b1)@ Split the @objectId@ (formatted as @type:hash&#64;timestamp@) on the separator @:@ and parse the first part as the type string
66+
*** @(RTO6b2)@ If the parsed type is @map@, create a zero-value @LiveMap@ per "RTLM4":#RTLM4 in the @ObjectsPool@
67+
*** @(RTO6b3)@ If the parsed type is @counter@, create a zero-value @LiveCounter@ per "RTLC4":#RTLC4 in the @ObjectsPool@
68+
69+
h3(#livecounter). LiveCounter
70+
71+
* @(RTLC1)@ The @LiveCounter@ extends @LiveObject@
72+
* @(RTLC2)@ Represents the counter object type for Object IDs of type @counter@
73+
* @(RTLC3)@ Holds a 64-bit floating-point number as a private @data@
74+
* @(RTLC4)@ The zero-value @LiveCounter@ is a @LiveCounter@ with @data@ set to 0
75+
* @(RTLC5)@ @LiveCounter#value@ function:
76+
** @(RTLC5a)@ Requires the @OBJECT_SUBSCRIBE@ channel mode to be granted per "RTO2":#RTO2
77+
** @(RTLC5b)@ If the channel is in the @DETACHED@ or @FAILED@ state, the library should indicate an error with code 90001
78+
** @(RTLC5c)@ Returns the current @data@ value
79+
* @(RTLC6)@ @LiveCounter@'s internal @data@ can be overridden with the provided @ObjectState@ in the following way:
80+
** @(RTLC6a)@ Replace the private @siteTimeserials@ of the @LiveCounter@ with the value from @ObjectState.siteTimeserials@
81+
** @(RTLC6b)@ Set the private flag @createOperationIsMerged@ to @false@
82+
** @(RTLC6c)@ Set @data@ to the value of @ObjectState.counter.count@, or to 0 if it does not exist
83+
** @(RTLC6d)@ If @ObjectState.createOp@ is present:
84+
*** @(RTLC6d1)@ Add @ObjectState.createOp.counter.count@ to @data@, if it exists
85+
*** @(RTLC6d2)@ Set the private flag @createOperationIsMerged@ to @true@
86+
87+
h3(#livemap). LiveMap
88+
89+
* @(RTLM1)@ The @LiveMap@ extends @LiveObject@
90+
* @(RTLM2)@ Represents the map object type for Object IDs of type @map@
91+
* @(RTLM3)@ Holds a @Dict<String, ObjectsMapEntry>@ as a private @data@ map
92+
* @(RTLM4)@ The zero-value @LiveMap@ is a @LiveMap@ with @data@ set to an empty map
93+
* @(RTLM5)@ @LiveMap#get@ function:
94+
** @(RTLM5a)@ Accepts a key of type String
95+
** @(RTLM5b)@ Requires the @OBJECT_SUBSCRIBE@ channel mode to be granted per "RTO2":#RTO2
96+
** @(RTLM5c)@ If the channel is in the @DETACHED@ or @FAILED@ state, the library should indicate an error with code 90001
97+
** @(RTLM5d)@ Returns the value from the current @data@ at the specified key, as follows:
98+
*** @(RTLM5d1)@ If no @ObjectsMapEntry@ exists at the key, return undefined/null
99+
*** @(RTLM5d2)@ If an @ObjectsMapEntry@ exists at the key:
100+
**** @(RTLM5d2a)@ If @ObjectsMapEntry.tombstone@ is @true@, return undefined/null
101+
**** @(RTLM5d2b)@ If @ObjectsMapEntry.data.boolean@ exists, return it
102+
**** @(RTLM5d2c)@ If @ObjectsMapEntry.data.bytes@ exists, return it
103+
**** @(RTLM5d2d)@ If @ObjectsMapEntry.data.number@ exists, return it
104+
**** @(RTLM5d2e)@ If @ObjectsMapEntry.data.string@ exists, return it
105+
**** @(RTLM5d2f)@ If @ObjectsMapEntry.data.objectId@ exists, get the object stored at that @objectId@ from the internal @ObjectsPool@:
106+
***** @(RTLM5d2f1)@ If an object with id @objectId@ does not exist, return undefined/null
107+
***** @(RTLM5d2f2)@ If an object with id @objectId@ exists, return it
108+
**** @(RTLM5d2g)@ Otherwise, return undefined/null
109+
* @(RTLM6)@ @LiveMap@ internal @data@ can be overridden with the provided @ObjectState@ in the following way:
110+
** @(RTLM6a)@ Replace the private @siteTimeserials@ of the @LiveMap@ with the value from @ObjectState.siteTimeserials@
111+
** @(RTLM6b)@ Set the private flag @createOperationIsMerged@ to @false@
112+
** @(RTLM6c)@ Set @data@ to @ObjectState.map.entries@, or to an empty map if it does not exist
113+
** @(RTLM6d)@ If @ObjectState.createOp@ is present:
114+
*** @(RTLM6d1)@ For each key–@ObjectsMapEntry@ pair in @ObjectState.createOp.map.entries@:
115+
**** @(RTLM6d1a)@ If @ObjectsMapEntry.tombstone@ is @false@, apply the @MAP_SET@ operation to the specified key using @ObjectsMapEntry.timeserial@ and @ObjectsMapEntry.data@ per "RTLM7":#RTLM7
116+
**** @(RTLM6d1b)@ If @ObjectsMapEntry.tombstone@ is @true@, apply the @MAP_REMOVE@ operation to the specified key using @ObjectsMapEntry.timeserial@ per "RTLM8":#RTLM8
117+
*** @(RTLM6d2)@ Set the private flag @createOperationIsMerged@ to @true@
118+
* @(RTLM7)@ @MAP_SET@ operation for a key can be applied to a @LiveMap@ in the following way:
119+
** @(RTLM7a)@ If an entry exists in the private @data@ for the specified key:
120+
*** @(RTLM7a1)@ If the operation cannot be applied as per "RTLM9":#RTLM9, discard the operation without taking any action
121+
*** @(RTLM7a2)@ Otherwise, apply the operation:
122+
**** @(RTLM7a2a)@ Set @ObjectsMapEntry.data@ to the @ObjectData@ from the operation
123+
**** @(RTLM7a2b)@ Set @ObjectsMapEntry.timeserial@ to the operation's serial
124+
**** @(RTLM7a2c)@ Set @ObjectsMapEntry.tombstone@ to @false@
125+
** @(RTLM7b)@ If an entry does not exist in the private @data@ for the specified key:
126+
*** @(RTLM7b1)@ Create a new entry in @data@ for the specified key with the provided @ObjectData@ and the operation's serial
127+
*** @(RTLM7b2)@ Set @ObjectsMapEntry.tombstone@ for the new entry to @false@
128+
** @(RTLM7c)@ If the operation has a non-empty @ObjectData.objectId@ attribute:
129+
*** @(RTLM7c1)@ Create a zero-value @LiveObject@ in the internal @ObjectsPool@ per "RTO6":#RTO6
130+
* @(RTLM8)@ @MAP_REMOVE@ operation for a key can be applied to a @LiveMap@ in the following way:
131+
** @(RTLM8a)@ If an entry exists in the private @data@ for the specified key:
132+
*** @(RTLM8a1)@ If the operation cannot be applied as per "RTLM9":#RTLM9, discard the operation without taking any action
133+
*** @(RTLM8a2)@ Otherwise, apply the operation:
134+
**** @(RTLM8a2a)@ Set @ObjectsMapEntry.data@ to undefined/null
135+
**** @(RTLM8a2b)@ Set @ObjectsMapEntry.timeserial@ to the operation's serial
136+
**** @(RTLM8a2c)@ Set @ObjectsMapEntry.tombstone@ to @true@
137+
** @(RTLM8b)@ If an entry does not exist in the private @data@ for the specified key:
138+
*** @(RTLM8b1)@ Create a new entry in @data@ for the specified key, with @ObjectsMapEntry.data@ set to undefined/null and the operation's serial
139+
*** @(RTLM8b2)@ Set @ObjectsMapEntry.tombstone@ for the new entry to @true@
140+
* @(RTLM9)@ Whether a map operation can be applied to a map entry is determined as follows:
141+
** @(RTLM9a)@ For a @LiveMap@ using @LWW@ (Last-Write-Wins) CRDT semantics, the operation must only be applied if its serial is strictly greater ("after") than the entry's serial when compared lexicographically
142+
** @(RTLM9b)@ If both the entry serial and the operation serial are null or empty strings, they are treated as the "earliest possible" serials and considered "equal", so the operation must not be applied
143+
** @(RTLM9c)@ If only the entry serial exists, the missing operation serial is considered lower than the existing entry serial, so the operation must not be applied
144+
** @(RTLM9d)@ If only the operation serial exists, it is considered greater than the missing entry serial, so the operation can be applied
145+
** @(RTLM9e)@ If both serials exist, compare them lexicographically and allow operation to be applied only if the operation's serial is greater than the entry's serial

0 commit comments

Comments
 (0)