| title | Objects Features | |||
|---|---|---|---|---|
| section | client-lib-development-guide | |||
| index | 65 | |||
| jump_to |
|
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 spec.
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.
(RTO1)Objects#getRootfunction:(RTO1a)Requires theOBJECT_SUBSCRIBEchannel mode to be granted per RTO2(RTO1b)If the channel is in theDETACHEDorFAILEDstate, the library should throw anErrorInfoerror withstatusCode400 andcode90001(RTO1c)Waits for the objects sync sequence to complete and for RTO5c to finish(RTO1d)Returns the object with idrootfrom the internalObjectsPoolas aLiveMap
(RTO2)Certain 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:(RTO2a)If the channel is in theATTACHEDstate, the presence of the required channel mode is checked against the set of channel modes granted by the server per RTL4m :(RTO2a1)If the channel mode is in the set, the operation is allowed(RTO2a2)If the channel mode is missing, unless otherwise specified by the operation, the library should throw anErrorInfoerror withstatusCode400 andcode40024, indicating that the operation cannot be performed without the required channel mode
(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 :(RTO2b1)If the channel mode is in the set, the operation is allowed(RTO2b2)If the channel mode is missing, unless otherwise specified by the operation, the library should throw anErrorInfoerror withstatusCode400 andcode40024, indicating that the operation cannot be performed without the required channel mode
(RTO3)An internalObjectsPoolshould be used to maintain the list of objects present on a channel(RTO4)When a channelATTACHEDProtocolMessageis received, theProtocolMessagemay contain aHAS_OBJECTSbit flag indicating that it will perform an objects sync, see TR3 . Note that this does not imply that objects are definitely present on the channel, only that there may be; theOBJECT_SYNCmessage may be empty(RTO4a)If theHAS_OBJECTSflag is 1, the server will shortly perform anOBJECT_SYNCsequence as described in RTO5(RTO4b)If theHAS_OBJECTSflag is 0 or there is noflagsfield, the sync sequence must be considered complete immediately, and the client library must perform the following actions in order:(RTO4b1)All objects except the one with idrootmust be removed from the internalObjectsPool(RTO4b2)The data for theLiveMapwith idrootmust be cleared by setting it to a zero-value per RTLM4. Note that the client SDK must not create a newLiveMapinstance with idroot; it must only clear the internal data of the existingLiveMapwith idroot(RTO4b3)TheSyncObjectsPoolmust be cleared(RTO4b4)Perform the actions for objects sync completion as described in RTO5c
(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(RTO5d)If anOBJECT_SYNCProtocolMessageis received andObjectMessage.objectis null or omitted, the client library should skip processing thatProtocolMessage(RTO5a)When anOBJECT_SYNCProtocolMessageis received with achannelattribute matching the channel name, the client library must parse thechannelSerialattribute:(RTO5a1)ThechannelSerialis used as the sync cursor and is a two-part identifier:<sequence id>:<cursor value>(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:(RTO5a2a)The currentSyncObjectsPoollist must be cleared
(RTO5a3)If the sequence id matches the previously received sequence id, the client library should continue the sync process(RTO5a4)The objects sync sequence for that sequence identifier is considered complete once the cursor is empty; that is when thechannelSeriallooks like<sequence id>:(RTO5a5)AnOBJECT_SYNCmay also be sent with nochannelSerialattribute. In this case, the sync data is entirely contained within theProtocolMessage
(RTO5b)During the sync sequence, theObjectMessage.objectvalues from incomingOBJECT_SYNC@ProtocolMessage@s must be temporarily stored in the internalSyncObjectsPoollist(RTO5c)When the objects sync has completed, the client library must perform the following actions in order:(RTO5c1)For eachObjectStatein theSyncObjectsPoollist:(RTO5c1a)If an object withObjectState.objectIdexists in the internalObjectsPool:(RTO5c1b)If an object withObjectState.objectIddoes not exist in the internalObjectsPool:(RTO5c1b1)Create a newLiveObjectusing the data fromObjectStateand add it to the internalObjectsPool:(RTO5c1b1a)IfObjectState.counteris present, create a zero-valueLiveCounter(per RTLC4), set its privateobjectIdequal toObjectState.objectIdand replace its internal data using the currentObjectStateper RTLC6(RTO5c1b1b)IfObjectState.mapis present, create a zero-valueLiveMap(per RTLM4), set its privateobjectIdequal toObjectState.objectId, set its privatesemanticsequal toObjectState.map.semanticsand replace its internal data using the currentObjectStateper RTLM6(RTO5c1b1c)Otherwise, log a warning that an unsupported object state message has been received, and discard the currentObjectStatewithout taking any action
(RTO5c2)Remove any objects from the internalObjectsPoolfor which @objectId@s were not received during the sync sequence(RTO5c2a)The object with IDrootmust not be removed fromObjectsPool, as per RTO3b
(RTO5c3)Clear any stored sync sequence identifiers and cursor values(RTO5c4)TheSyncObjectsPoolmust be cleared
(RTO6)Certain object operations may require creating a zero-value object if one does not already exist in the internalObjectsPoolfor the givenobjectId. This can be done as follows:(RTO6a)If an object withobjectIdexists inObjectsPool, do not create a new object(RTO6b)The expected type of the object can be inferred from the providedobjectId:(RTO6b1)Split theobjectId(formatted astype:hash@timestamp) on the separator:and parse the first part as the type string(RTO6b2)If the parsed type ismap, create a zero-valueLiveMapper RTLM4 in theObjectsPool(RTO6b3)If the parsed type iscounter, create a zero-valueLiveCounterper RTLC4 in theObjectsPool
(RTLO1)TheLiveObjectrepresents the common interface and includes shared functionality for concrete object types
(RTLC1)TheLiveCounterextendsLiveObject(RTLC2)Represents the counter object type for Object IDs of typecounter(RTLC3)Holds a 64-bit floating-point number as a privatedata(RTLC4)The zero-valueLiveCounteris aLiveCounterwithdataset to 0(RTLC5)LiveCounter#valuefunction:(RTLC5a)Requires theOBJECT_SUBSCRIBEchannel mode to be granted per RTO2(RTLC5b)If the channel is in theDETACHEDorFAILEDstate, the library should throw anErrorInfoerror withstatusCode400 andcode90001(RTLC5c)Returns the currentdatavalue
(RTLC6)@LiveCounter@’s internaldatacan be replaced with the providedObjectStatein the following way:(RTLC6a)Replace the privatesiteTimeserialsof theLiveCounterwith the value fromObjectState.siteTimeserials(RTLC6b)Set the private flagcreateOperationIsMergedtofalse(RTLC6c)Setdatato the value ofObjectState.counter.count, or to 0 if it does not exist(RTLC6d)IfObjectState.createOpis present:(RTLC6d1)AddObjectState.createOp.counter.counttodata, if it exists(RTLC6d2)Set the private flagcreateOperationIsMergedtotrue
(RTLM1)TheLiveMapextendsLiveObject(RTLM2)Represents the map object type for Object IDs of typemap(RTLM3)Holds aDict<String, ObjectsMapEntry>as a privatedatamap(RTLM4)The zero-valueLiveMapis aLiveMapwithdataset to an empty map(RTLM5)LiveMap#getfunction:(RTLM5a)Accepts a key of type String(RTLM5b)Requires theOBJECT_SUBSCRIBEchannel mode to be granted per RTO2(RTLM5c)If the channel is in theDETACHEDorFAILEDstate, the library should throw anErrorInfoerror withstatusCode400 andcode90001(RTLM5d)Returns the value from the currentdataat the specified key, as follows:(RTLM5d1)If noObjectsMapEntryexists at the key, return undefined/null(RTLM5d2)If anObjectsMapEntryexists at the key:(RTLM5d2a)IfObjectsMapEntry.tombstoneistrue, return undefined/null(RTLM5d2b)IfObjectsMapEntry.data.booleanexists, return it(RTLM5d2c)IfObjectsMapEntry.data.bytesexists, return it(RTLM5d2d)IfObjectsMapEntry.data.numberexists, return it(RTLM5d2e)IfObjectsMapEntry.data.stringexists, return it(RTLM5d2f)IfObjectsMapEntry.data.objectIdexists, get the object stored at thatobjectIdfrom the internalObjectsPool:(RTLM5d2f1)If an object with idobjectIddoes not exist, return undefined/null(RTLM5d2f2)If an object with idobjectIdexists, return it
(RTLM5d2g)Otherwise, return undefined/null
(RTLM10)LiveMap#size:(RTLM10a)A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a property similar toMap.sizefor the nativeMapclass(RTLM10b)Requires theOBJECT_SUBSCRIBEchannel mode to be granted per RTO2(RTLM10c)If the channel is in theDETACHEDorFAILEDstate, the library should throw anErrorInfoerror withstatusCode400 andcode90001(RTLM10d)Returns the number of non-tombstoned entries (per RTLM14) in the internaldatamap
(RTLM11)LiveMap#entries:(RTLM11a)A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a method similar toMap.entries()for the nativeMapclass(RTLM11b)Requires theOBJECT_SUBSCRIBEchannel mode to be granted per RTO2(RTLM11c)If the channel is in theDETACHEDorFAILEDstate, the library should throw anErrorInfoerror withstatusCode400 andcode90001(RTLM11d)Returns key-value pairs from the internaldatamap:(RTLM11d1)Pairs with tombstoned entries (per RTLM14) are not returned(RTLM11d3)ObjectsMapEntryvalues are mapped to user-facing values following the same procedure as in RTLM5d2(RTLM11d3a)Note that if RTLM5d2 results in anObjectsMapEntrybeing mapped to an undefined/null value, the corresponding key-value pair is still returned by thisLiveMap#entriescall
(RTLM11d2)The return type is idiomatic for the platform’s analogous Map/Dictionary interface operation. For example, in JavaScript, it returns a map iterator object like the one returned byMap.entries()method for the nativeMapclass
(RTLM12)LiveMap#keys:(RTLM12a)A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a method similar toMap.keys()for the nativeMapclass(RTLM12b)The implementation is identical toLiveMap#entries, except that it returns only the keys from the internaldatamap
(RTLM13)LiveMap#values:(RTLM13a)A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a method similar toMap.values()for the nativeMapclass(RTLM13b)The implementation is identical toLiveMap#entries, except that it returns only the values from the internaldatamap
(RTLM14)AnObjectsMapEntryin the internaldatamap can be checked for being tombstoned using the convenience method:(RTLM14a)The method returns true ifObjectsMapEntry.tombstoneis true(RTLM14b)Otherwise, it returns false
(RTLM6)LiveMapinternaldatacan be replaced with the providedObjectStatein the following way:(RTLM6a)Replace the privatesiteTimeserialsof theLiveMapwith the value fromObjectState.siteTimeserials(RTLM6b)Set the private flagcreateOperationIsMergedtofalse(RTLM6c)SetdatatoObjectState.map.entries, or to an empty map if it does not exist(RTLM6d)IfObjectState.createOpis present:(RTLM6d1)For each key–@ObjectsMapEntry@ pair inObjectState.createOp.map.entries:(RTLM6d1a)IfObjectsMapEntry.tombstoneisfalseor omitted, apply theMAP_SEToperation to the current key as described in RTLM7, passing inObjectsMapEntry.dataand the current key asObjectsMapOp, andObjectsMapEntry.timeserialasserial(RTLM6d1b)IfObjectsMapEntry.tombstoneistrue, apply theMAP_REMOVEoperation to the current key as described in RTLM8, passing in the current key asObjectsMapOp, andObjectsMapEntry.timeserialasserial
(RTLM6d2)Set the private flagcreateOperationIsMergedtotrue
(RTLM7)MAP_SEToperation for a key can be applied to aLiveMapin the following way:(RTLM7d)Expects the following arguments:(RTLM7d1)ObjectsMapOp(RTLM7d2)serialstring – operation’s serial value
(RTLM7a)If anObjectsMapEntryexists in the privatedatafor the specified key:(RTLM7a1)If the operation cannot be applied to the existing entry as per RTLM9, discard the operation without taking any action(RTLM7a2)Otherwise, apply the operation to the existing entry:(RTLM7a2a)SetObjectsMapEntry.datato theObjectDatafrom the operation(RTLM7a2b)SetObjectsMapEntry.timeserialto the providedserial(RTLM7a2c)SetObjectsMapEntry.tombstonetofalse
(RTLM7b)If an entry does not exist in the privatedatafor the specified key:(RTLM7b1)Create a newObjectsMapEntryindatafor the specified key, withObjectsMapEntry.dataset to the providedObjectDataandObjectsMapEntry.timeserialset toserial(RTLM7b2)SetObjectsMapEntry.tombstonefor the new entry tofalse
(RTLM7c)If the operation has a non-emptyObjectData.objectIdattribute:(RTLM7c1)Create a zero-valueLiveObjectfor thisObjectData.objectIdin the internalObjectsPoolper RTO6
(RTLM8)MAP_REMOVEoperation for a key can be applied to aLiveMapin the following way:(RTLM8c)Expects the following arguments:(RTLM8c1)ObjectsMapOp(RTLM8c2)serialstring – operation’s serial value
(RTLM8a)If anObjectsMapEntryexists in the privatedatafor the specified key:(RTLM8a1)If the operation cannot be applied to the existing entry as per RTLM9, discard the operation without taking any action(RTLM8a2)Otherwise, apply the operation to the existing entry:(RTLM8a2a)SetObjectsMapEntry.datato undefined/null(RTLM8a2b)SetObjectsMapEntry.timeserialto the providedserial(RTLM8a2c)SetObjectsMapEntry.tombstonetotrue
(RTLM8b)If an entry does not exist in the privatedatafor the specified key:(RTLM8b1)Create a newObjectsMapEntryindatafor the specified key, withObjectsMapEntry.dataset to undefined/null andObjectsMapEntry.timeserialset to the providedserial(RTLM8b2)SetObjectsMapEntry.tombstonefor the new entry totrue
(RTLM9)Whether a map operation can be applied to a map entry is determined as follows:(RTLM9a)For aLiveMapwithsemanticsset toObjectsMapSemantics.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(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(RTLM9c)If only the entry serial exists and is not an empty string, the missing operation serial is considered lower than the existing entry serial, so the operation must not be applied(RTLM9d)If only the operation serial exists and is not an empty string, it is considered greater than the missing entry serial, so the operation can be applied(RTLM9e)If both serials exist and are not empty strings, compare them lexicographically and allow operation to be applied only if the operation’s serial is greater than the entry’s serial