| title | Objects Features |
|---|
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)This clause has been replaced by RTO23.(RTO23)RealtimeObject#getfunction:(RTO23a)Requires theOBJECT_SUBSCRIBEchannel mode to be granted per RTO2(RTO23b)This clause has been replaced by RTO23e(RTO23e)Perform the ensure-active-channel procedure (RTL33) on the underlyingRealtimeChannel. If the procedure fails, thegetfunction must reject with the sameErrorInfothat caused the procedure to fail(RTO23c)If the RTO17 sync state is notSYNCED, waits for the sync state to transition toSYNCED(RTO23d)Returns a newPathObject(RTPO1) withpath(RTPO2a) set to an empty list androot(RTPO2b) set to theInternalLiveMapwith idrootfrom the internalObjectsPool
(RTO11)This clause has been replaced by RTLMV3.(RTO11a)This clause has been replaced by RTLMV3.(RTO11a1)This clause has been replaced by RTLMV3.
(RTO11b)This clause has been replaced by RTLMV3.(RTO11c)This clause has been replaced by RTLMV3.(RTO11d)This clause has been replaced by RTLMV3.(RTO11e)This clause has been replaced by RTLMV3.(RTO11f)This clause has been replaced by RTLMV3.(RTO11f1)This clause has been replaced by RTLMV3.(RTO11f2)This clause has been replaced by RTLMV3.(RTO11f3)This clause has been replaced by RTLMV3.(RTO11f4)This clause has been replaced by RTO11f14 as of specification version 6.0.0.(RTO11f4a)This clause has been replaced by RTO11f14a as of specification version 6.0.0.(RTO11f4b)This clause has been replaced by RTO11f14b as of specification version 6.0.0.(RTO11f4c)This clause has been replaced by RTO11f14c as of specification version 6.0.0.(RTO11f4c1)This clause has been replaced by RTO11f14c1 as of specification version 6.0.0.(RTO11f4c1a)This clause has been replaced by RTO11f14c1a as of specification version 6.0.0.(RTO11f4c1b)This clause has been replaced by RTO11f14c1b as of specification version 6.0.0.(RTO11f4c1c)This clause has been replaced by RTO11f14c1c as of specification version 6.0.0.(RTO11f4c1d)This clause has been replaced by RTO11f14c1d as of specification version 6.0.0.(RTO11f4c1e)This clause has been replaced by RTO11f14c1e as of specification version 6.0.0.(RTO11f4c1f)This clause has been replaced by RTO11f14c1f as of specification version 6.0.0.
(RTO11f4c2)This clause has been replaced by RTO11f14c2 as of specification version 6.0.0.
(RTO11f14)This clause has been replaced by RTLMV3.(RTO11f14a)This clause has been replaced by RTLMV3.(RTO11f14b)This clause has been replaced by RTLMV3.(RTO11f14c)This clause has been replaced by RTLMV3.(RTO11f14c1)This clause has been replaced by RTLMV3.(RTO11f14c1a)This clause has been replaced by RTLMV3.(RTO11f14c1b)This clause has been replaced by RTLMV3.(RTO11f14c1c)This clause has been replaced by RTLMV3.(RTO11f14c1d)This clause has been replaced by RTLMV3.(RTO11f14c1e)This clause has been replaced by RTLMV3.(RTO11f14c1f)This clause has been replaced by RTLMV3.
(RTO11f14c2)This clause has been replaced by RTLMV3.
(RTO11f5)This clause has been replaced by RTO11f15 as of specification version 6.0.0.(RTO11f15)This clause has been replaced by RTLMV3.(RTO11f6)This clause has been replaced by RTLMV3.(RTO11f7)This clause has been replaced by RTLMV3.(RTO11f8)This clause has been replaced by RTLMV3.(RTO11f9)This clause has been replaced by RTLMV3.(RTO11f10)This clause has been replaced by RTLMV3.(RTO11f11)This clause has been replaced by RTO11f16 as of specification version 6.0.0.(RTO11f12)This clause has been replaced by RTO11f17 as of specification version 6.0.0.(RTO11f13)This clause has been deleted as of specification version 6.0.0.(RTO11f16)This clause has been replaced by RTLMV3.(RTO11f17)This clause has been replaced by RTLMV3.(RTO11f18)This clause has been replaced by RTLMV4j5.
(RTO11g)This clause has been replaced by RTO11i(RTO11i)This clause has been replaced by RTLMV3.(RTO11i1)This clause has been replaced by RTLMV3.
(RTO11h)This clause has been replaced by RTLMV3.(RTO11h1)This clause has been deleted.(RTO11h2)This clause has been replaced by RTLMV3.(RTO11h3)This clause has been replaced by RTLMV3.(RTO11h3a)This clause has been deleted.(RTO11h3b)This clause has been deleted.(RTO11h3c)This clause has been deleted.(RTO11h3d)This clause has been replaced by RTLMV3.
(RTO12)This clause has been replaced by RTLCV3.(RTO12a)This clause has been replaced by RTLCV3.(RTO12a1)This clause has been replaced by RTLCV3.
(RTO12b)This clause has been replaced by RTLCV3.(RTO12c)This clause has been replaced by RTLCV3.(RTO12d)This clause has been replaced by RTLCV3.(RTO12e)This clause has been replaced by RTLCV3.(RTO12f)This clause has been replaced by RTLCV3.(RTO12f1)This clause has been replaced by RTLCV3.(RTO12f2)This clause has been replaced by RTO12f12 as of specification version 6.0.0.(RTO12f12)This clause has been replaced by RTLCV3.(RTO12f3)This clause has been replaced by RTO12f13 as of specification version 6.0.0.(RTO12f13)This clause has been replaced by RTLCV3.(RTO12f4)This clause has been replaced by RTLCV3.(RTO12f5)This clause has been replaced by RTLCV3.(RTO12f6)This clause has been replaced by RTLCV3.(RTO12f7)This clause has been replaced by RTLCV3.(RTO12f8)This clause has been replaced by RTLCV3.(RTO12f9)This clause has been replaced by RTO12f14 as of specification version 6.0.0.(RTO12f10)This clause has been replaced by RTO12f15 as of specification version 6.0.0.(RTO12f11)This clause has been deleted as of specification version 6.0.0.(RTO12f14)This clause has been replaced by RTLCV3.(RTO12f15)This clause has been replaced by RTLCV3.(RTO12f16)This clause has been replaced by RTLCV4g5.
(RTO12g)This clause has been replaced by RTO12i(RTO12i)This clause has been replaced by RTLCV3.(RTO12i1)This clause has been replaced by RTLCV3.
(RTO12h)This clause has been replaced by RTLCV3.(RTO12h1)This clause has been deleted.(RTO12h2)This clause has been replaced by RTLCV3.(RTO12h3)This clause has been replaced by RTLCV3.(RTO12h3a)This clause has been deleted.(RTO12h3b)This clause has been deleted.(RTO12h3c)This clause has been deleted.(RTO12h3d)This clause has been replaced by RTLCV3.
(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
(RTO25)Certain object operations may require the access API preconditions to be satisfied in order to be performed. If the access API preconditions are required by an operation, then before doing anything else:(RTO25a)Require theOBJECT_SUBSCRIBEchannel mode to be granted per RTO2(RTO25b)If the channel is in theDETACHEDorFAILEDstate, throw anErrorInfoerror withstatusCode400 andcode90001
(RTO26)Certain object operations may require the write API preconditions to be satisfied in order to be performed. If the write API preconditions are required by an operation, then before doing anything else:(RTO26a)Require theOBJECT_PUBLISHchannel mode to be granted per RTO2(RTO26b)If the channel is in theDETACHED,FAILED, orSUSPENDEDstate, throw anErrorInfoerror withstatusCode400 andcode90001(RTO26c)IfechoMessagesclient option isfalse, throw anErrorInfoerror withstatusCode400 andcode40000, indicating thatechoMessagesmust be enabled for this operation
(RTO3)An internalObjectsPoolshould be used to maintain the list of objects present on a channel(RTO3a)ObjectsPoolis aDict<String, LiveObject>- a map ofLiveObjects keyed byobjectIdstring(RTO3b)It must always contain anInternalLiveMapobject with idroot(RTO3b1)Upon initialization of theObjectsPool, create a newInternalLiveMapper RTLM4 withobjectIdset torootand add it to theObjectsPool
(RTO4)When a channelATTACHEDProtocolMessageis received, the client library must perform the following actions in order. TheProtocolMessagemay contain aHAS_OBJECTSbit flag (see TR3); note that some of the following actions are conditional on this flag.(RTO4c)The RTO17 sync state must transition toSYNCINGif not alreadySYNCING(RTO4d)ThebufferedObjectOperationslist must be cleared without applying any buffered operations(RTO4a)If theHAS_OBJECTSflag is 1, the server will shortly perform anOBJECT_SYNCsequence as described in RTO5. Note that this does not imply that objects are definitely present on the channel, only that there may be; theOBJECT_SYNCmessage may be empty(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 theInternalLiveMapwith idrootmust be set to the value described in RTLM4c. Note that the client SDK must not create a newInternalLiveMapinstance with idroot; it must only clear the internal data of the existingInternalLiveMapwith idroot(RTO4b2a)Emit aLiveMapUpdateobject for theInternalLiveMapwith IDroot, withLiveMapUpdate.updateconsisting of entries for the keys that were removed, each set toremoved, and without populatingLiveMapUpdate.objectMessage
(RTO4b3)TheSyncObjectsPoolmust be cleared(RTO4b5)This clause has been replaced by RTO4d(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(RTO5e)When anOBJECT_SYNCProtocolMessageis received with achannelattribute matching the channel name, the RTO17 sync state must transition toSYNCINGif not alreadySYNCING. This must occur before performing any RTO5c sync completion actions.(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)TheSyncObjectsPoolmust be cleared(RTO5a2b)This clause has been replaced by RTO4d
(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)This clause has been replaced by RTO5f(RTO5f)During the sync sequence,ObjectMessagesfrom incomingOBJECT_SYNCProtocolMessagesmust be temporarily stored in the internalSyncObjectsPool, keyed byObjectMessage.object.objectId. TheSyncObjectsPoolstores oneObjectMessageperobjectId, which may represent merged state from multiple incoming messages. For eachObjectMessagein the incomingOBJECT_SYNCProtocolMessage, letObjectStatebeObjectMessage.object:(RTO5f3)If neitherObjectState.mapnorObjectState.counteris present on the incoming message, log a warning that a state message with an unsupported object type was received and skip the incoming message(RTO5f1)If an entry with the givenObjectState.objectIddoes not yet exist in theSyncObjectsPool, store theObjectMessage(RTO5f2)If an entry with the givenObjectState.objectIdalready exists in theSyncObjectsPool, this indicates a partial object state - the server has split a large object across multipleOBJECT_SYNCProtocolMessages. The client must merge the partial state into the existing entry based on the object type:(RTO5f2a)IfObjectState.mapis present on the incoming message, merge the map state:(RTO5f2a1)If the incomingObjectState.tombstoneistrue, replace the existing entry in theSyncObjectsPoolwith the incomingObjectMessageentirely(RTO5f2a2)Otherwise, mergeObjectState.map.entriesfrom the incoming message into the existingObjectState.map.entries. During partial sync, no two messages for the same map object contain the same map key, so no conflict resolution is needed
(RTO5f2b)IfObjectState.counteris present on the incoming message, log an error indicating that an unexpected partial object state for a counter was received, and skip the incoming message
(RTO5c)When the objects sync has completed, the client library must perform the following actions in order:(RTO5c1)For eachObjectMessagein theSyncObjectsPool, letObjectStatebeObjectMessage.object:(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 newInternalLiveCounterper RTLC4 by passing inObjectState.objectIdasobjectId, and then replace its internal data using the currentObjectMessageper RTLC6(RTO5c1b1b)IfObjectState.mapis present, create a newInternalLiveMapper RTLM4 by passing inObjectState.objectIdasobjectId,ObjectState.map.semanticsassemantics, and then replace its internal data using the currentObjectMessageper RTLM6(RTO5c1b1c)This clause has been deleted (redundant to RTO5f3).
(RTO5c2)Remove any objects from the internalObjectsPoolfor whichobjectIds were not received during the sync sequence(RTO5c2a)The object with IDrootmust not be removed fromObjectsPool, as per RTO3b
(RTO5c10)Rebuild everyparentReferencesmap (RTLO3f):(RTO5c10a)For eachLiveObjectin the internalObjectsPool, reset itsparentReferencesto the initial value defined in RTLO3f2(RTO5c10b)For eachInternalLiveMapin the internalObjectsPool, iterate itsInternalLiveMap#entries(RTLM11); for each entry whose value is aLiveObject, calladdParentReference(parent, key)on thatLiveObjectper RTLO4g, passing theInternalLiveMapasparentand the entry's key askey
(RTO5c7)For each previously existing object that was updated as a result of RTO5c1a, emit the corresponding storedLiveObjectUpdateobject from RTO5c1a2(RTO5c6)ObjectMessagesstored in thebufferedObjectOperationslist are applied as described in RTO9, passingsourceasCHANNEL(RTO5c3)Clear any stored sync sequence identifiers and cursor values(RTO5c4)TheSyncObjectsPoolmust be cleared(RTO5c5)ThebufferedObjectOperationslist must be cleared(RTO5c9)TheappliedOnAckSerialsset (RTO7b) must be cleared. A state sync causes the channel's LiveObjects data to be replaced, so after a state sync theappliedOnAckSerialsno longer accurately describes which operations have been applied to the channel's LiveObjects data(RTO5c8)The RTO17 sync state must transition toSYNCED
(RTO6)Certain object operations may require creating a new 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 as[type]:[hash]@[timestamp], see RTO14c) on the separator:and parse the first part as the type string(RTO6b2)If the parsed type ismap, create a newInternalLiveMapper RTLM4 by passing in theobjectId, and add it to theObjectsPool(RTO6b3)If the parsed type iscounter, create a newInternalLiveCounterper RTLC4 by passing in theobjectId, and add it to theObjectsPool
(RTO7)The client library may receiveOBJECTProtocolMessagesin realtime over the channel concurrently withOBJECT_SYNCProtocolMessagesduring the object sync sequence (RTO5). Some of the incomingOBJECTmessages may have already been applied to the objects described in the sync sequence, while others may not. Therefore, the client must bufferOBJECTmessages during the sync sequence so that it can determine which of them should be applied to the objects once the sync is complete. See RTO8(RTO7a)TheRealtimeObjectinstance has an internal attributebufferedObjectOperations, which is an array ofObjectMessageinstances. This is used to store the bufferedObjectMessages, as described in RTO8a.(RTO7a1)This array is empty uponRealtimeObjectinitialization
(RTO7b)TheRealtimeObjectinstance has an internal attributeappliedOnAckSerials, which is a set of strings. This is used to store the serial values of operations that have been applied upon receipt of anACKbut for which the echo has not yet been received.(RTO7b1)This set is empty uponRealtimeObjectinitialization
(RTO8)When the library receives aProtocolMessagewith an action ofOBJECT, each member of theProtocolMessage.statearray (decoded intoObjectMessageobjects) is passed to theRealtimeObjectinstance per RTL1. EachObjectMessagefromOBJECTProtocolMessage(also referred to as anOBJECTmessage) describes an operation to be applied to an object on a channel and must be handled as follows:(RTO9)OBJECTmessages can be applied toRealtimeObjectin the following way:(RTO9b)Expects the following arguments:(RTO9b1)ObjectMessage[]- the list ofObjectMessagesto apply(RTO9b2)sourceObjectsOperationSource- the source of the operation (see RTO22)
(RTO9a)For eachObjectMessagein the provided list:(RTO9a1)IfObjectMessage.operationis null or omitted, log a warning indicating that an unsupported object operation message has been received, and discard the currentObjectMessagewithout taking any action(RTO9a3)If theappliedOnAckSerialsset (RTO7b) containsObjectMessage.serial, log a debug or trace message indicating that the operation has already been applied upon receipt of the ACK, remove this value from the set, and discard the currentObjectMessagewithout taking any further action(RTO9a2)TheObjectMessage.operation.actionfield (seeObjectOperationAction) determines the type of operation to apply:(RTO9a2a)IfObjectMessage.operation.actionis one of the following:MAP_CREATE,MAP_SET,MAP_REMOVE,COUNTER_CREATE,COUNTER_INC,OBJECT_DELETE, orMAP_CLEAR, then:(RTO9a2a1)If it does not already exist, create a newLiveObjectin the internalObjectsPoolper RTO6 using theobjectIdfromObjectMessage.operation.objectId(RTO9a2a2)Get theLiveObjectinstance from the internalObjectsPoolusing theobjectIdfromObjectMessage.operation.objectId(RTO9a2a3)Apply theObjectMessage.operationto theLiveObject; see RTLC7, RTLM15, passing thesourceparameter. The operation returns a boolean indicating whether the operation was successfully applied(RTO9a2a4)IfsourceisLOCALand RTO9a2a3 returnedtrue, addObjectMessage.serialto the internalappliedOnAckSerialsset (RTO7b)
(RTO9a2b)Otherwise, log a warning that an object operation message with an unsupported action has been received, and discard the currentObjectMessagewithout taking any action
(RTO10)The client library must have a process in place to regularly check for objects and map entries that have been tombstoned for a period of time, and release their resources so they can be garbage collected. Tombstoned objects and map entries are retained in memory for a sufficient grace period (at least >2 minutes) to ensure that no late-arriving operation is mistakenly applied to an object or map entry the client has already "forgotten" about.(RTO10a)The check should occur at regular intervals, for example, every 5 minutes(RTO10b)The grace period for releasing resources for tombstoned objects and map entries is determined as follows:(RTO10b1)It is equal toConnectionDetails.objectsGCGracePeriodreceived in theCONNECTEDProtocolMessage(RTO10b2)The grace period value is updated to match the newConnectionDetails.objectsGCGracePeriodvalue whenever a newCONNECTEDProtocolMessageis received per RTN24(RTO10b3)A default value of 86,400,000 milliseconds (24 hours) is used ifConnectionDetails.objectsGCGracePeriodis not provided
(RTO10c)On each check interval:(RTO10c1)For eachLiveObjectin theObjectsPool:(RTO10c1a)Check if theLiveObjectneeds to release any resources, see RTLM19(RTO10c1b)IfLiveObject.isTombstoneistrue, and the difference between the current time andLiveObject.tombstonedAtis greater than or equal to the grace period, remove the object from theObjectsPooland release resources for the corresponding object entity to allow it to be garbage collected
(RTO13)This clause has been deleted (redundant to RTO11f15 and RTO12f13) as of specification version 6.0.0.(RTO13a)This clause has been deleted as of specification version 6.0.0.(RTO13a1)This clause has been deleted as of specification version 6.0.0.
(RTO13b)This clause has been deleted as of specification version 6.0.0.(RTO13c)This clause has been deleted as of specification version 6.0.0.
(RTO14)An Object ID can be created in the client library for a newLiveObjectinstance in the following way:(RTO14a)Expects the following arguments:(RTO14a1)typeString- the type of object this Object ID is generated for. Must be one ofmaporcounter(RTO14a2)initialValueString- a JSON string representation of the initial value for the object. This protects against Object IDs being reused for create operations with differing content(RTO14a3)nonceString- a random string to ensure uniqueness across clients(RTO14a4)timestampTime- the current server time. This protects against Object IDs being reused across time
(RTO14b)Generate ahashstring for the Object ID:(RTO14b1)Generate a SHA-256 digest from a UTF-8 encoded string in the format[initialValue]:[nonce](RTO14b2)Base64URL-encode the generated digest. This must follow the URL-safe Base64 encoding as described in RFC 4648 s.5, not standard Base64 encoding
(RTO14c)Return an Object ID in the format[type]:[hash]@[timestamp], wheretimestampis represented as milliseconds since the epoch
(RTO15)InternalRealtimeObject#publishfunction:(RTO15a)Expects the following arguments:(RTO15a1)ObjectMessage[]- an array ofObjectMessageto be published on a channel
(RTO15b)Must adhere to the same connection and channel state conditions as message publishing, see RTL6c(RTO15c)Must encode the providedObjectMessagesas described in OM4(RTO15d)Should validate that the total size of the encodedObjectMessages, calculated as per OM3, does not exceedmaxMessageSize. If it does, the client library must reject the publish and throw anErrorInfoerror withstatusCode400 andcode40009(RTO15e)Must construct the followingProtocolMessage:(RTO15e1)SetProtocolMessage.actiontoOBJECT(RTO15e2)SetProtocolMessage.channelto the channel name(RTO15e3)SetProtocolMessage.stateto the encodedObjectMessages
(RTO15f)Must send theProtocolMessageto the connection(RTO15g)Must indicate success or failure of the publish (onceACKedorNACKed) in the same way asRealtimeChannel#publish(RTO15h)Upon success, must return thePublishResultfrom the first element of theACK'sresarray (TR4s), in the same way asRealtimeChannel#publish(RTL6j)
(RTO20)InternalRealtimeObject#publishAndApplyfunction:(RTO20a)Expects the following arguments:(RTO20a1)ObjectMessage[]- an array ofObjectMessageto be published on a channel
(RTO20b)CallsRealtimeObject#publish(RTO15) with the providedObjectMessage[]and awaits thePublishResult. Ifpublishfails, rethrow the error and do not proceed(RTO20c)If the information needed to apply the operations locally on ACK is not available, this is unexpected incorrect behaviour from the server. Log an error indicating the reason the operations will not be applied locally, and do not proceed with the remaining steps. (The operations have already been published successfully, but cannot be applied locally on ACK; they will instead be applied when the echoed messages are received from the server.) The required information is:(RTO20c1)AsiteCodefrom CD2jConnectionDetails.siteCode(RTO20c2)APublishResult.serialsarray with the same length as the providedObjectMessage[]argument
(RTO20d)Create a list of synthetic inboundObjectMessagesas follows. For eachObjectMessagein the providedObjectMessage[]argument, paired with the corresponding serial fromPublishResult.serialsat the same index:(RTO20d1)If the serial from thePublishResultisnull(indicating that the operation was conflated --- not currently an expected behaviour but we wish to handle it gracefully if the server introduces this behaviour in the future), log a debug or trace message indicating that the operation will not be applied locally because it was not assigned aserial, and skip thisObjectMessagewithout adding it to the list(RTO20d2)Create a synthetic inboundObjectMessageby copying the outboundObjectMessageand setting:(RTO20d2a)ObjectMessage.serialto the serial from thePublishResult(RTO20d2b)ObjectMessage.siteCodeto the CD2jConnectionDetails.siteCode
(RTO20d3)Add the syntheticObjectMessageto the list
(RTO20e)If the RTO17 sync state is notSYNCED, wait for the sync state to transition toSYNCED(RTO20e1)If the channel enters theDETACHED,SUSPENDED, orFAILEDstate while waiting for the sync state to transition toSYNCED, thepublishAndApplyoperation must fail with anErrorInfoerror withcode92008, astatusCodeof400, amessagestating that the operation could not be applied locally due to the channel entering the respective state whilst waiting for objects sync to complete, andcauseset to theRealtimeChannel.errorReasonif it is set
(RTO20f)Apply the syntheticObjectMessagesas described in RTO9, passingsourceasLOCAL
(RTO16)Server time can be retrieved usingRestClient#time(RTO16a)The server time offset can be persisted by the client library and used to calculate the server time without making a request, in a similar way to how it is described in RSA10k. The persisted offset from either operation can be used interchangeably
(RTO17)TheRealtimeObjectinstance must maintain an internal sync state to track the status of synchronising the local objects data with the Ably service.(RTO17a)The sync state has typeObjectsSyncState, which is an enum with the following cases (note that their descriptions are purely informative; the rules for state transitions are described elsewhere in this specification):(RTO17a1)INITIALIZED- the initial state whenRealtimeObjectis created(RTO17a2)SYNCING- in this state, the local copy of objects on the channel is currently being synchronised with the Ably service(RTO17a3)SYNCED- in this state, the local copy of objects on the channel has been synchronised with the Ably service
(RTO17b)When the sync state transitions, an event with theObjectsEventvalue matching the new state must be emitted to any listeners registered viaRealtimeObject#on(RTO18).
(RTO18)RealtimeObject#onfunction - registers a listener for sync state events(RTO18a)Expects the following arguments:(RTO18a1)event- the event name to listen for, of typeObjectsEvent(see RTO18b)(RTO18a2)callback- the event listener function to be called when the event is emitted
(RTO18b)ObjectsEventis an enum with the following cases:(RTO18c)Registers the provided listener for the specified event(RTO18d)Ifonis called more than once with the same listener and event, the listener is added multiple times to the listener registry. As such, if the same listener is registered twice and an event is emitted once, the listener will be invoked twice(RTO18e)When an event is emitted, all registered listeners for that event must be called with no arguments(RTO18f)The client library may return a subscription object (or the idiomatic equivalent for the language) as a result of this operation:(RTO18f1)The subscription object includes anofffunction(RTO18f2)Callingoffderegisters the listener previously registered by the user via the correspondingoncall
(RTO19)RealtimeObject#offfunction - deregisters an event listener previously registered viaRealtimeObject#on(RTO18)(RTO22)ObjectsOperationSourceis an internal enum describing the source of an operation being applied:(RTO22a)LOCAL- an operation that originated locally, being applied upon receipt of theACKfrom Realtime(RTO22b)CHANNEL- an operation received over a Realtime channel
(RTO24)InternalPathObjectSubscriptionRegister- manages path-based subscriptions forPathObject#subscribe(RTPO19)(RTO24a)TheRealtimeObjectinstance maintains a singlePathObjectSubscriptionRegisterthat manages all path-based subscriptions for the channel(RTO24b)Path-based subscription dispatch: given aLiveObjectand aLiveObjectUpdate, thePathObjectSubscriptionRegistermust determine which subscriptions should be notified by performing the following actions in order:(RTO24b1)LetpathsToThisbe the set of paths returned by callinggetFullPaths(RTLO4f) on theLiveObject(RTO24b2)For eachpathToThisinpathsToThis:(RTO24b2a)Construct an ordered list of candidate pathscandidatePaths, in order of decreasing preference:(RTO24b2a1)The first (most-preferred) candidate ispathToThisitself(RTO24b2a2)If theLiveObjectUpdateis aLiveMapUpdate, then for each key inLiveMapUpdate.update, append a further candidate consisting ofpathToThisextended by that key
(RTO24b2b)For each registered subscription, find the firsteventPathincandidatePathsthat the subscription covers per RTO24c1. If no sucheventPathexists, do nothing for this subscription. Otherwise, call the subscription's listener exactly once with aPathObjectSubscriptionEventthat has:(RTO24b2b1)object- a newPathObject(RTPO1) withpath(RTPO2a) set toeventPathandroot(RTPO2b) set to theInternalLiveMapwith idrootfrom the internalObjectsPool(RTO24b2b2)message- ifLiveObjectUpdate.objectMessageis populated and itsoperationfield is populated, aPublicAPI::ObjectMessagederived fromLiveObjectUpdate.objectMessageper PAOM3; otherwise omitted
(RTO24b2c)If a listener throws an error, the error must be caught and logged without affecting the dispatch to other subscriptions, nor to otherpathToThisiterations
(RTO24c)Subscription coverage:(RTO24c1)A subscription with subscribed pathsubPathanddepthoption is said to cover a patheventPathif and only ifsubPathis a prefix ofeventPath(treatingsubPathas a prefix of itself, so that an exact path match also satisfies this condition), and eitherdepthis undefined/null oreventPath.length - subPath.length + 1 <= depth(RTO24c2)(non-normative) Coverage examples, for a subscription at path["users"]:(RTO24c2a)Withdepthundefined/null: covers["users"],["users", "emma"],["users", "emma", "visits"], and so on at any depth(RTO24c2b)Withdepth = 1: covers["users"]; does not cover["users", "emma"]or any deeper path(RTO24c2c)Withdepth = 2: covers["users"]and["users", "emma"]; does not cover["users", "emma", "visits"]or any deeper path(RTO24c2d)With anydepth: does not cover["admins"]or["userPosts"], since the subscription path is not a prefix of either
(RTLO1)TheLiveObjectrepresents the common interface and includes shared functionality for concrete object types(RTLO2)The client library may choose to implementLiveObjectas an abstract class(RTLO3)LiveObjectproperties:(RTLO3a)protectedobjectIdstring - an Object ID for this object(RTLO3a1)Must be provided and set in the constructor
(RTLO3b)protectedsiteTimeserialsDict<String, String>- a map of serials keyed by siteCode, representing the last operations applied to this object(RTLO3b1)Set to an empty map when theLiveObjectis initialized, so that any future operation can be applied to this object
(RTLO3c)protectedcreateOperationIsMergedboolean - a flag indicating whether the correspondingMAP_CREATEorCOUNTER_CREATEoperation has been applied to thisLiveObjectinstance(RTLO3c1)Set tofalsewhen theLiveObjectis initialized
(RTLO3d)protectedisTombstoneboolean - a flag indicating whether this object has been tombstoned, i.e. marked for deletion from the objects pool(RTLO3d1)Set tofalsewhen theLiveObjectis initialized
(RTLO3e)protectedtombstonedAt(optional) Time - a timestamp indicating when this object was tombstoned. This property is nullable, and specification points that manipulate this value maintain the invariant that it is non-null if and only ifisTombstoneistrue(RTLO3e1)Set to undefined/null when theLiveObjectis initialized
(RTLO3f)protectedparentReferencesDict<String, Set<String>>- tracks whichInternalLiveMaps in the localObjectsPoolcurrently reference thisLiveObject, and at which keys they do so. The mapping is keyed by the parentInternalLiveMap'sobjectId, with each value being the set of keys at which thatInternalLiveMapreferences thisLiveObject. Used bygetFullPaths(RTLO4f) to determine every path the object currently occupies in the LiveObjects graph(RTLO3f1)This mapping is keyed byobjectIdfor consistency with the rest of the LiveObjects spec, where references between objects are stored asobjectIds and resolved via theObjectsPoolon demand. Implementations may store a direct reference to the parentInternalLiveMapinstead — for example to avoid anObjectsPoollookup at each step ofgetFullPaths(RTLO4f) traversal — provided the observable behaviour is unchanged. Such implementations should be aware that this may introduce reference cycles betweenInternalLiveMaps, and must ensure this does not cause memory leaks(RTLO3f2)Set to an empty map when theLiveObjectis initialized
(RTLO4)LiveObjectmethods:(RTLO4b)subscribe- subscribes a user to data updates on thisLiveObjectinstance(RTLO4b1)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(RTLO4b2)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(RTLO4b3)A user may provide a listener to subscribe to data updates on thisLiveObjectinstance(RTLO4b4)An update toLiveObjectdata is communicated by internally emitting aLiveObjectUpdateobject for thisLiveObject, or in any other platform-appropriate manner:(RTLO4b4a)LiveObjectUpdate.updatecontains the specific information about what was changed on the object. The exact type depends on the object type(RTLO4b4b)TheLiveObjectUpdate.noopinternal property can be used to indicate that the update was a no-op(RTLO4b4d)LiveObjectUpdate.objectMessageis an optionalObjectMessage- the sourceObjectMessagethat caused this update, if any(RTLO4b4e)TheLiveObjectUpdate.tombstoneinternal Boolean property indicates that this update was emitted as a result of thisLiveObjectbeing tombstoned. It defaults tofalseif not explicitly set(RTLO4b4c)When aLiveObjectUpdateis emitted:(RTLO4b4c1)IfLiveObjectUpdateis indicated to be a no-op, do nothing(RTLO4b4c2)This clause has been replaced by RTLO4b4c3 as of specification version 6.0.0.(RTLO4b4c3)Otherwise:(RTLO4b4c3a)The registered listener of each subscription created viaLiveObject#subscribe(RTLO4b) on thisLiveObjectis called with theLiveObjectUpdate(RTLO4b4c3b)Perform path-based subscription dispatch as described in RTO24b, passing thisLiveObjectand theLiveObjectUpdate(RTLO4b4c3c)IfLiveObjectUpdate.tombstoneistrue, after RTLO4b4c3a has completed, the library must deregister all listeners on thisLiveObjectthat were registered viaLiveObject#subscribe(RTLO4b)(RTLO4b4c3c1)(non-normative) Path-based subscriptions (RTPO19) are unaffected, because their lifetime is tied to the path rather than to thisLiveObjectinstance
(RTLO4b5)This clause has been replaced by RTLO4b7(RTLO4b7)Returns aSubscriptionobject(RTLO4b6)This operation must not have any side effects onRealtimeObject, the underlying channel, or their status
(RTLO4c)This clause has been deleted(RTLO4c1)This clause has been deleted(RTLO4c2)This clause has been deleted(RTLO4c3)This clause has been deleted(RTLO4c4)This clause has been deleted
(RTLO4a)protectedcanApplyOperation- a convenience method used to determine whether theObjectMessage.operationshould be applied to this object based on a serial value(RTLO4a1)Expects the following arguments:(RTLO4a1a)ObjectMessage
(RTLO4a2)Returns a boolean indicating whether the operation should be applied to this object(RTLO4a3)BothObjectMessage.serialandObjectMessage.siteCodemust be non-empty strings. Otherwise, log a warning that the object operation message has invalid serial values. The client library must not apply this operation to the object(RTLO4a4)Get thesiteSerialvalue stored for thisLiveObjectin thesiteTimeserialsmap using the keyObjectMessage.siteCode(RTLO4a5)If thesiteSerialfor thisLiveObjectis null or an empty string, return true(RTLO4a6)If thesiteSerialfor thisLiveObjectis not an empty string, return true ifObjectMessage.serialis greater thansiteSerialwhen compared lexicographically
(RTLO4g)internaladdParentReference(parent, key)method - records that theInternalLiveMapparentreferences thisLiveObjectatkey(RTLO4g1)IfparentReferencesalready contains an entry whose key isparent.objectId, addkeyto that entry's set(RTLO4g2)Otherwise, insert intoparentReferencesa new entry whose key isparent.objectIdand whose value is a set containing onlykey
(RTLO4h)internalremoveParentReference(parent, key)method - removes the recorded reference fromparentatkey(RTLO4h1)IfparentReferencesdoes not contain an entry whose key isparent.objectId, do nothing(RTLO4h2)Otherwise, removekeyfrom that entry's set(RTLO4h3)If, as a result of RTLO4h2, that entry's set is empty, remove the entry fromparentReferences
(RTLO4e)protectedtombstone- a convenience method used to tombstone thisLiveObject. The realtime system reserves the right to tombstone an object (i.e. mark it for deletion from the objects pool) by publishing anOBJECT_DELETEoperation at any time if the object is orphaned (not a descendant of the root object) or remains uninitialized (no*_CREATEoperation has been received) for an extended period. Only the realtime system may publish anOBJECT_DELETEoperation; clients must never send it. This method describes the steps the client library must take when it needs to tombstone an object locally. Eventually, tombstoned objects will be garbage collected following the procedure described in RTO10(RTLO4e1)Expects the following arguments:(RTLO4e1a)ObjectMessage
(RTLO4e2)SetLiveObject.isTombstonetotrue(RTLO4e3)SetLiveObject.tombstonedAtto the value calculated per RTLO6, usingObjectMessage.serialTimestamp(RTLO4e9)If theLiveObjectis anInternalLiveMap, then beforeInternalLiveMap.datais reset per RTLO4e4, for eachObjectsMapEntryinInternalLiveMap.data:(RTLO4e9a)IfObjectsMapEntry.data.objectIdis populated, fetch the object with thisobjectIdfrom theObjectsPool(RTLO4e9b)If theRTLO4e9afetch returned an object, call itsRTLO4hremoveParentReference(parent, key)method, passing thisInternalLiveMapasparentand the iterated entry's key askey
(RTLO4e4)Set thedataattribute of theLiveObjectto the value described in RTLC4b or RTLM4c, depending on the object type(RTLO4e5)Compute aLiveObjectUpdaterepresenting the data change resulting from thisLiveObjectbeing tombstoned, by calculating the diff between thedatavalue from before RTLO4e4 was applied (aspreviousData) and the currentdatavalue (asnewData), per RTLC14 or RTLM22, depending on the object type(RTLO4e6)SetLiveObjectUpdate.tombstonetotrueon the object computed in RTLO4e5(RTLO4e7)SetLiveObjectUpdate.objectMessageon the object computed in RTLO4e5 to theObjectMessageargument(RTLO4e8)Return theLiveObjectUpdateobject computed in RTLO4e5
(RTLO4f)internalgetFullPathsfunction - returns the list of all key-paths from the rootInternalLiveMap(objectIdroot) to thisLiveObject. A key-path is a list of zero or more keys (the same concept as "path" elsewhere in this spec, e.g. onPathObject); we use the term key-path in this clause specifically to distinguish it from the graph-theoretical "simple path" used in RTLO4f2(RTLO4f1)Which key-paths are returned is determined via a directed graph G defined as follows. The nodes of G are theLiveObjects in theObjectsPool. For each(parent, key)pair recorded inchild'sparentReferences(RTLO3f), G has a directed edge fromparenttochildlabelledkey(RTLO4f2)A simple path in G is a sequence of edges visiting each node at most once. Each such path in G fromrootto thisLiveObjectcontributes one key-path to the returned list: the list of its edge labels. The empty simple path (which exists only when thisLiveObjectis itselfroot) contributes the empty key-path[](RTLO4f3)Each such key-path appears in the returned list exactly once. The order is unspecified(RTLO4f4)(non-normative) A typical approach is iterative DFS with an explicit stack: walk upward from thisLiveObjecttowardrootviaparentReferences, collecting keys along the way and skipping branches that would revisit a node
(RTLO5)AnOBJECT_DELETEoperation can be applied to aLiveObjectin the following way:(RTLO5a)Expects the following arguments:(RTLO5a1)ObjectMessage
(RTLO5b)Tombstone the currentLiveObjectusingLiveObject.tombstone, passing in theObjectMessage(RTLO5c)Return theLiveObjectUpdatereturned by theLiveObject.tombstonecall performed in RTLO5b
(RTLO6)AtombstonedAtvalue can be calculated from a providedserialTimestampas follows:(RTLO6a)It is equal toserialTimestampif it exists(RTLO6b)Otherwise, it is equal to the current time using the local clock(RTLO6b1)Log a debug or trace message indicating thatserialTimestampwas not provided and the local clock is being used instead for the tombstone timestamp
(RTLC1)TheInternalLiveCounterextendsLiveObject(RTLC2)Represents the counter object type for Object IDs of typecounter(RTLC3)Holds a 64-bit floating-point number as a privatedata(RTLC4)A new emptyInternalLiveCountercan be created with the following values:(RTLC4a)objectIdis passed into the constructor and set upon creation(RTLC4b)datais set to 0
(RTLC11)Data updates for anInternalLiveCounterare emitted using theLiveCounterUpdateobject:(RTLC11a)LiveCounterUpdateextendsLiveObjectUpdate(RTLC11b)LiveCounterUpdate.updatehas the following properties:(RTLC11b1)amountnumber - the value by which the counter was incremented or decremented
(RTLC5)InternalLiveCounter#valuefunction:(RTLC12)InternalLiveCounter#incrementfunction:(RTLC12a)Expects the following arguments:(RTLC12a1)amountNumber- the amount by which to increment the counter value
(RTLC12b)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLC12c)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLC12d)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLC12e)Creates anObjectMessagefor aCOUNTER_INCaction in the following way:(RTLC12e1)Ifamountis null, not of typeNumber, not a finite number, or omitted, the library should throw anErrorInfoerror withstatusCode400 andcode40003, indicating thatamountmust be a valid number(RTLC12e2)SetObjectMessage.operation.actiontoObjectOperationAction.COUNTER_INC(RTLC12e3)SetObjectMessage.operation.objectIdto the Object ID of thisInternalLiveCounter(RTLC12e4)This clause has been replaced by RTLC12e5 as of specification version 6.0.0.(RTLC12e5)SetObjectMessage.operation.counterInc.numberto the providedamountvalue
(RTLC12f)This clause has been replaced by RTLC12g(RTLC12g)Publishes theObjectMessagefrom RTLC12e usingRealtimeObject#publishAndApply(RTO20), passing theObjectMessageas a single element in the array
(RTLC13)InternalLiveCounter#decrementfunction:(RTLC13a)Expects the following arguments:(RTLC13a1)amountNumber- the amount by which to decrement the counter value
(RTLC13b)This is an alias for callingInternalLiveCounter#incrementwith a negativeamountand must be implemented with the same behavior(RTLC13c)If the client library chooses to delegate toInternalLiveCounter#incrementwith a negatedamount, then in languages where negating a non-number may result in implicit type coercion, theamountargument must first be validated as described in RTLC12e1 before proceeding
(RTLC6)InternalLiveCounter's internaldatacan be replaced with theObjectStatefrom a providedObjectMessage(which the caller must ensure has itsobjectfield populated; letObjectStaterefer toObjectMessage.object) in the following way:(RTLC6a)Replace the privatesiteTimeserialsof theInternalLiveCounterwith the value fromObjectState.siteTimeserials(RTLC6e)IfInternalLiveCounter.isTombstoneistrue, finish processing theObjectState(RTLC6e1)Return aLiveCounterUpdateobject withLiveCounterUpdate.noopset totrue, indicating that no update was made to the object
(RTLC6f)IfObjectState.tombstoneistrue, tombstone the currentInternalLiveCounterusingLiveObject.tombstone, passing in theObjectMessage. Finish processing theObjectState(RTLC6g)Store the currentdatavalue aspreviousDatafor use in RTLC6h(RTLC6b)Set the private flagcreateOperationIsMergedtofalse(RTLC6c)Setdatato the value ofObjectState.counter.count, or to 0 if it does not exist(RTLC6d)IfObjectState.createOpis present, merge the initial value into theInternalLiveCounteras described in RTLC16, passing in theObjectState.createOpinstance and theObjectMessage. Discard theLiveCounterUpdateobject returned by the merge operation(RTLC6h)Calculate the diff betweenpreviousDatafrom RTLC6g and the currentdataper RTLC14, setLiveCounterUpdate.objectMessageon the resulting update to the providedObjectMessage, and return the resultingLiveCounterUpdateobject
(RTLC7)AnObjectOperationfromObjectMessage.operationcan be applied to anInternalLiveCounterby performing the following actions in order:(RTLC7f)Expects the following arguments:(RTLC7f1)ObjectMessage- anObjectMessageinstance with an existingObjectMessage.operationobject, withObjectMessage.operation.objectIdmatching the Object ID of thisInternalLiveCounter. ThisObjectMessagerepresents the operation to be applied to thisInternalLiveCounter(RTLC7f2)sourceObjectsOperationSource- the source of the operation (see RTO22)
(RTLC7g)Returns a boolean indicating whether the operation was successfully applied(RTLC7a)A client library may choose to implement this logic as a convenience method namedapplyOperation, which accepts the arguments described in RTLC7f(RTLC7b)IfObjectMessage.operationcannot be applied based on the result ofLiveObject.canApplyOperation, log a debug or trace message indicating that the operation cannot be applied because its serial value is not newer than the object's, and discard theObjectMessagewithout taking any further action. Returnfalse(RTLC7c)IfsourceisCHANNEL, set the entry in the privatesiteTimeserialsmap at the keyObjectMessage.siteCodeto equalObjectMessage.serial(RTLC7e)IfInternalLiveCounter.isTombstoneistrue, the operation cannot be applied to the object. Finish processing theObjectMessagewithout taking any further action. No data update event is emitted. Returnfalse(RTLC7d)TheObjectMessage.operation.actionfield (seeObjectOperationAction) determines the type of operation to apply:(RTLC7d1)IfObjectMessage.operation.actionis set toCOUNTER_CREATE, apply the operation as described in RTLC8, passing inObjectMessage.operationandObjectMessage(RTLC7d1a)Emit theLiveCounterUpdateobject returned as a result of applying the operation(RTLC7d1b)Returntrue
(RTLC7d2)This clause has been replaced by RTLC7d5 as of specification version 6.0.0.(RTLC7d5)IfObjectMessage.operation.actionis set toCOUNTER_INC, apply the operation as described in RTLC9, passing inObjectMessage.operation.counterIncandObjectMessage(RTLC7d5a)Emit theLiveCounterUpdateobject returned as a result of applying the operation(RTLC7d5b)Returntrue
(RTLC7d4)IfObjectMessage.operation.actionis set toOBJECT_DELETE, apply the operation as described in RTLO5, passing inObjectMessage(RTLC7d4a)This clause has been replaced by RTLC7d4c as of specification version 6.0.0.(RTLC7d4c)Emit theLiveCounterUpdateobject returned as a result of applying the operation(RTLC7d4b)Returntrue
(RTLC7d3)Otherwise, log a warning that an object operation message with an unsupported action has been received, and discard the currentObjectMessagewithout taking any further action. No data update event is emitted. Returnfalse
(RTLC8)ACOUNTER_CREATEoperation can be applied to anInternalLiveCounterin the following way:(RTLC8a)Expects the following arguments:(RTLC8a1)ObjectOperation(RTLC8a2)ObjectMessage- the sourceObjectMessagethat contains the operation
(RTLC8d)The return type is aLiveCounterUpdateobject, which indicates the data update for thisInternalLiveCounter(RTLC8b)If the private flagcreateOperationIsMergedistrue, log a debug or trace message indicating that the operation will not be applied because aCOUNTER_CREATEoperation has already been applied to thisInternalLiveCounter. Discard the operation without taking any further action, and return aLiveCounterUpdateobject withLiveCounterUpdate.noopset totrue, indicating that no update was made to the object(RTLC8c)Otherwise merge the initial value into theInternalLiveCounteras described in RTLC16, passing in theObjectOperationinstance and theObjectMessage(RTLC8e)Return theLiveCounterUpdateobject returned by RTLC16
(RTLC9)ACOUNTER_INCoperation can be applied to anInternalLiveCounterin the following way:(RTLC9a)Expects the following arguments:(RTLC9a1)This clause has been replaced by RTLC9a2 as of specification version 6.0.0.(RTLC9a2)CounterInc(RTLC9a3)ObjectMessage- the sourceObjectMessagethat contains the operation
(RTLC9c)The return type is aLiveCounterUpdateobject, which indicates the data update for thisInternalLiveCounter(RTLC9b)This clause has been replaced by RTLC9f as of specification version 6.0.0.(RTLC9d)This clause has been replaced by RTLC9g as of specification version 6.0.0.(RTLC9e)This clause has been replaced by RTLC9h as of specification version 6.0.0.(RTLC9f)AddCounterInc.numbertodata, if it exists(RTLC9g)IfCounterInc.numberexists, return aLiveCounterUpdateobject withLiveCounterUpdate.update.amountset toCounterInc.numberandLiveCounterUpdate.objectMessageset to the providedObjectMessage(RTLC9h)IfCounterInc.numberdoes not exist, return aLiveCounterUpdateobject withLiveCounterUpdate.noopset totrue
(RTLC10)This clause has been replaced by RTLC16 as of specification version 6.0.0.(RTLC10a)This clause has been replaced by RTLC16a as of specification version 6.0.0.(RTLC10b)This clause has been replaced by RTLC16b as of specification version 6.0.0.(RTLC10c)This clause has been replaced by RTLC16c as of specification version 6.0.0.(RTLC10d)This clause has been replaced by RTLC16d as of specification version 6.0.0.
(RTLC16)The initial value from anObjectOperationcan be merged into thisInternalLiveCounterin the following way. Expects anObjectOperationand anObjectMessage(the sourceObjectMessagethat contains the operation) as arguments. LetcounterCreatebeObjectOperation.counterCreateif present, else theCounterCreatefrom whichObjectOperation.counterCreateWithObjectIdwas derived (see RTLCV4g5):(RTLC16a)AddcounterCreate.counttodata, if it exists(RTLC16b)Set the private flagcreateOperationIsMergedtotrue(RTLC16c)IfcounterCreate.countexists, return aLiveCounterUpdateobject withLiveCounterUpdate.update.amountset tocounterCreate.countandLiveCounterUpdate.objectMessageset to the providedObjectMessage(RTLC16d)IfcounterCreate.countdoes not exist, return aLiveCounterUpdateobject withLiveCounterUpdate.noopset totrue
(RTLC14)The diff between twoInternalLiveCounterdata values can be calculated in the following way:(RTLC14a)Expects the following arguments:(RTLC14a1)previousDataNumber- the previousdatavalue(RTLC14a2)newDataNumber- the newdatavalue
(RTLC14b)Return aLiveCounterUpdateobject withLiveCounterUpdate.update.amountset tonewData - previousData
(RTLM1)TheInternalLiveMapextendsLiveObject(RTLM2)Represents the map object type for Object IDs of typemap(RTLM3)Holds aDict<String, ObjectsMapEntry>as a privatedatamap(RTLM3a)ObjectsMapEntryentries in anInternalLiveMaphave the following attributes in addition to those defined in OME2:(RTLM3a1)tombstonedAt(optional) Time - a timestamp indicating when this map entry was tombstoned. This property is nullable, and specification points that manipulate this value maintain the invariant that it is non-null if and only if the correspondingObjectsMapEntry.tombstoneistrue
(RTLM25)Holds a nullable privateclearTimeserialstring, initiallynull(RTLM4)A new emptyInternalLiveMapcan be created with the following values:(RTLM4a)objectIdis passed into the constructor and set upon creation(RTLM4b)semanticsmay be passed into the constructor and set upon creation; if not provided, it defaults toObjectsMapSemantics.LWW(RTLM4c)datais set to an empty map(RTLM4d)clearTimeserialis set tonull
(RTLM18)Data updates for anInternalLiveMapare emitted using theLiveMapUpdateobject:(RTLM18a)LiveMapUpdateextendsLiveObjectUpdate(RTLM18b)LiveMapUpdate.updateis of typeDict<String, 'updated' | 'removed'>- a map ofInternalLiveMapkeys that were either updated or removed, with the corresponding value indicating the type of change for each key
(RTLM5)InternalLiveMap#getfunction:(RTLM5a)Accepts a key of type String(RTLM5b)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(RTLM5c)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(RTLM5e)IfInternalLiveMap.isTombstoneistrue, return undefined/null(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:(RTLM5d2h)If theObjectsMapEntryis tombstoned (per RTLM14), return undefined/null(RTLM5d2a)This clause has been replaced by RTLM5d2h(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(RTLM5d2f3)This clause has been replaced by RTLM5d2h(RTLM5d2f2)Otherwise, return the object with idobjectId
(RTLM5d2g)Otherwise, return undefined/null
(RTLM10)InternalLiveMap#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)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(RTLM10c)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(RTLM10d)Returns the number of non-tombstoned entries (per RTLM14) in the internaldatamap
(RTLM11)InternalLiveMap#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)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(RTLM11c)This clause has been replaced by RTO25; the access API preconditions are now checked by callers(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 thisInternalLiveMap#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)InternalLiveMap#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 toInternalLiveMap#entries, except that it returns only the keys from the internaldatamap
(RTLM13)InternalLiveMap#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 toInternalLiveMap#entries, except that it returns only the values from the internaldatamap
(RTLM20)InternalLiveMap#setfunction:(RTLM20a)Expects the following arguments:(RTLM20a1)keyString- the key to set the value for(RTLM20a2)This clause has been replaced by RTLM20a3.(RTLM20a3)valueBoolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap- the value to assign to the key
(RTLM20b)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLM20c)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLM20d)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLM20e)Creates anObjectMessagefor aMAP_SETaction in the following way:(RTLM20e1)Validates the providedkeyandvaluein a similar way as described in RTLMV4b and RTLMV4c(RTLM20e2)SetObjectMessage.operation.actiontoObjectOperationAction.MAP_SET(RTLM20e3)SetObjectMessage.operation.objectIdto the Object ID of thisInternalLiveMap(RTLM20e4)This clause has been replaced by RTLM20e6 as of specification version 6.0.0.(RTLM20e5)This clause has been replaced by RTLM20e7 as of specification version 6.0.0.(RTLM20e5a)This clause has been replaced by RTLM20e7a as of specification version 6.0.0.(RTLM20e5b)This clause has been replaced by RTLM20e7b as of specification version 6.0.0.(RTLM20e5c)This clause has been replaced by RTLM20e7c as of specification version 6.0.0.(RTLM20e5d)This clause has been replaced by RTLM20e7d as of specification version 6.0.0.(RTLM20e5e)This clause has been replaced by RTLM20e7e as of specification version 6.0.0.(RTLM20e5f)This clause has been replaced by RTLM20e7f as of specification version 6.0.0.
(RTLM20e6)SetObjectMessage.operation.mapSet.keyto the providedkeyvalue(RTLM20e7)SetObjectMessage.operation.mapSet.valuedepending on the type of the providedvalue:(RTLM20e7a)This clause has been replaced by RTLM20e7g.(RTLM20e7g)If thevalueis of typeLiveCounterorLiveMap:(RTLM20e7g1)Evaluate the value type per RTLCV4 or RTLMV4 respectively. Collect all generatedObjectMessagesinto an ordered list — for RTLCV4 the list contains the single returnedObjectMessage; for RTLMV4 the list is the returned array(RTLM20e7g2)SetObjectMessage.operation.mapSet.value.objectIdto theobjectIdfrom the finalObjectMessagein the list gathered inRTLM20e7g1(which is the create operation for theLiveObjectwhose creation the value type represents)
(RTLM20e7b)If thevalueis of typeJsonArrayorJsonObject, setObjectMessage.operation.mapSet.value.jsonto that value(RTLM20e7c)If thevalueis of typeString, setObjectMessage.operation.mapSet.value.stringto that value(RTLM20e7d)If thevalueis of typeNumber, setObjectMessage.operation.mapSet.value.numberto that value(RTLM20e7e)If thevalueis of typeBoolean, setObjectMessage.operation.mapSet.value.booleanto that value(RTLM20e7f)If thevalueis of typeBinary, setObjectMessage.operation.mapSet.value.bytesto that value
(RTLM20f)This clause has been replaced by RTLM20g(RTLM20g)This clause has been replaced by RTLM20h.(RTLM20h)Publishes allObjectMessagesusingRealtimeObject#publishAndApply(RTO20):(RTLM20h1)If thevalueis of typeLiveCounterorLiveMap, the array contains the*_CREATEObjectMessagescollected in RTLM20e7g1 followed by theMAP_SETObjectMessagefrom RTLM20e(RTLM20h2)Otherwise, theMAP_SETObjectMessagefrom RTLM20e is passed as a single element in the array
(RTLM21)InternalLiveMap#removefunction:(RTLM21a)Expects the following arguments:(RTLM21a1)keyString- the key to remove the value for
(RTLM21b)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLM21c)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLM21d)This clause has been replaced by RTO26; the write API preconditions are now checked by callers(RTLM21e)Creates anObjectMessagefor aMAP_REMOVEaction in the following way:(RTLM21e1)Validates the providedkeyin a similar way as described in RTLMV4b(RTLM21e2)SetObjectMessage.operation.actiontoObjectOperationAction.MAP_REMOVE(RTLM21e3)SetObjectMessage.operation.objectIdto the Object ID of thisInternalLiveMap(RTLM21e4)This clause has been replaced by RTLM21e5 as of specification version 6.0.0.(RTLM21e5)SetObjectMessage.operation.mapRemove.keyto the providedkeyvalue
(RTLM21f)This clause has been replaced by RTLM21g(RTLM21g)Publishes theObjectMessagefrom RTLM21e usingRealtimeObject#publishAndApply(RTO20), passing theObjectMessageas a single element in the array
(RTLM14)AnObjectsMapEntryin the internaldatamap can be checked for being tombstoned using the convenience method:(RTLM14a)The method returns true ifObjectsMapEntry.tombstoneis true(RTLM14c)The method returns true ifObjectsMapEntry.data.objectIdexists, there is an object in the localObjectsPoolwith that id, and thatLiveObject.isTombstoneproperty istrue(RTLM14b)Otherwise, it returns false
(RTLM6)InternalLiveMapinternaldatacan be replaced with theObjectStatefrom a providedObjectMessage(which the caller must ensure has itsobjectfield populated; letObjectStaterefer toObjectMessage.object) in the following way:(RTLM6a)Replace the privatesiteTimeserialsof theInternalLiveMapwith the value fromObjectState.siteTimeserials(RTLM6e)IfInternalLiveMap.isTombstoneistrue, finish processing theObjectState(RTLM6e1)Return aLiveMapUpdateobject withLiveMapUpdate.noopset totrue, indicating that no update was made to the object
(RTLM6f)IfObjectState.tombstoneistrue, tombstone the currentInternalLiveMapusingLiveObject.tombstone, passing in theObjectMessage. Finish processing theObjectState(RTLM6g)Store the currentdatavalue aspreviousDatafor use in RTLM6h(RTLM6b)Set the private flagcreateOperationIsMergedtofalse(RTLM6i)Set the privateclearTimeserialtoObjectState.map.clearTimeserial, or tonullif not provided(RTLM6c)SetdatatoObjectState.map.entries, or to an empty map if it does not exist(RTLM6c1)For eachObjectsMapEntrywithObjectsMapEntry.tombstoneequal totrue, additionally set theObjectsMapEntry.tombstonedAtfield to the value calculated per RTLO6, usingObjectsMapEntry.serialTimestamp
(RTLM6d)IfObjectState.createOpis present, merge the initial value into theInternalLiveMapas described in RTLM23, passing in theObjectState.createOpinstance and theObjectMessage. Discard theLiveMapUpdateobject returned by the merge operation(RTLM6h)Calculate the diff betweenpreviousDatafrom RTLM6g and the currentdataper RTLM22, setLiveMapUpdate.objectMessageon the resulting update to the providedObjectMessage, and return the resultingLiveMapUpdateobject
(RTLM15)AnObjectOperationfromObjectMessage.operationcan be applied to anInternalLiveMapby performing the following actions in order:(RTLM15f)Expects the following arguments:(RTLM15f1)ObjectMessage- anObjectMessageinstance with an existingObjectMessage.operationobject, withObjectMessage.operation.objectIdmatching the Object ID of thisInternalLiveMap. ThisObjectMessagerepresents the operation to be applied to thisInternalLiveMap(RTLM15f2)sourceObjectsOperationSource- the source of the operation (see RTO22)
(RTLM15g)Returns a boolean indicating whether the operation was successfully applied(RTLM15a)A client library may choose to implement this logic as a convenience method namedapplyOperation, which accepts the arguments described in RTLM15f(RTLM15b)IfObjectMessage.operationcannot be applied based on the result ofLiveObject.canApplyOperation, log a debug or trace message indicating that the operation cannot be applied because its serial value is not newer than the object's, and discard theObjectMessagewithout taking any further action. Returnfalse(RTLM15c)IfsourceisCHANNEL, set the entry in the privatesiteTimeserialsmap at the keyObjectMessage.siteCodeto equalObjectMessage.serial(RTLM15e)IfInternalLiveMap.isTombstoneistrue, the operation cannot be applied to the object. Finish processing theObjectMessagewithout taking any further action. No data update event is emitted. Returnfalse(RTLM15d)TheObjectMessage.operation.actionfield (seeObjectOperationAction) determines the type of operation to apply:(RTLM15d1)IfObjectMessage.operation.actionis set toMAP_CREATE, apply the operation as described in RTLM16, passing inObjectMessage.operationandObjectMessage(RTLM15d1a)Emit theLiveMapUpdateobject returned as a result of applying the operation(RTLM15d1b)Returntrue
(RTLM15d2)This clause has been replaced by RTLM15d6 as of specification version 6.0.0.(RTLM15d6)IfObjectMessage.operation.actionis set toMAP_SET, apply the operation as described in RTLM7, passing inObjectMessage.operation.mapSet,ObjectMessage.serial, andObjectMessage(RTLM15d6a)Emit theLiveMapUpdateobject returned as a result of applying the operation(RTLM15d6b)Returntrue
(RTLM15d3)This clause has been replaced by RTLM15d7 as of specification version 6.0.0.(RTLM15d7)IfObjectMessage.operation.actionis set toMAP_REMOVE, apply the operation as described in RTLM8, passing inObjectMessage.operation.mapRemove,ObjectMessage.serial,ObjectMessage.serialTimestamp, andObjectMessage(RTLM15d7a)Emit theLiveMapUpdateobject returned as a result of applying the operation(RTLM15d7b)Returntrue
(RTLM15d5)IfObjectMessage.operation.actionis set toOBJECT_DELETE, apply the operation as described in RTLO5, passing inObjectMessage(RTLM15d5a)This clause has been replaced by RTLM15d5c as of specification version 6.0.0.(RTLM15d5c)Emit theLiveMapUpdateobject returned as a result of applying the operation(RTLM15d5b)Returntrue
(RTLM15d8)IfObjectMessage.operation.actionis set toMAP_CLEAR, apply the operation as described in RTLM24, passing inObjectMessage.serialandObjectMessage(RTLM15d8a)Emit theLiveMapUpdateobject returned as a result of applying the operation(RTLM15d8b)Returntrue
(RTLM15d4)Otherwise, log a warning that an object operation message with an unsupported action has been received, and discard the currentObjectMessagewithout taking any further action. No data update event is emitted. Returnfalse
(RTLM16)AMAP_CREATEoperation can be applied to anInternalLiveMapin the following way:(RTLM16a)Expects the following arguments:(RTLM16a1)ObjectOperation(RTLM16a2)ObjectMessage- the sourceObjectMessagethat contains the operation
(RTLM16e)The return type is aLiveMapUpdateobject, which indicates the data update for thisInternalLiveMap(RTLM16b)If the private flagcreateOperationIsMergedistrue, log a debug or trace message indicating that the operation will not be applied because aMAP_CREATEoperation has already been applied to thisInternalLiveMap. Discard the operation without taking any further action, and return aLiveMapUpdateobject withLiveMapUpdate.noopset totrue, indicating that no update was made to the object(RTLM16c)This clause has been deleted.(RTLM16d)Otherwise merge the initial value into theInternalLiveMapas described in RTLM23, passing in theObjectOperationinstance and theObjectMessage(RTLM16f)Return theLiveMapUpdateobject returned by RTLM23
(RTLM7)AMAP_SEToperation for a key can be applied to anInternalLiveMapin the following way:(RTLM7d)Expects the following arguments:(RTLM7d1)This clause has been replaced by RTLM7d3 as of specification version 6.0.0.(RTLM7d3)MapSet(RTLM7d2)serialstring - operation's serial value(RTLM7d4)ObjectMessage- the sourceObjectMessagethat contains the operation
(RTLM7e)The return type is aLiveMapUpdateobject, which indicates the data update for thisInternalLiveMap(RTLM7h)If the privateclearTimeserialis non-null, and the providedserialis null or theclearTimeserialis lexicographically greater than or equal toserial, discard the operation without taking any action. Return aLiveMapUpdateobject withLiveMapUpdate.noopset totrue, indicating that no update was made to the object(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. Return aLiveMapUpdateobject withLiveMapUpdate.noopset totrue, indicating that no update was made to the object(RTLM7a2)Otherwise, apply the operation to the existing entry:(RTLM7a3)BeforeObjectsMapEntry.datais set per RTLM7a2e:(RTLM7a2a)This clause has been replaced by RTLM7a2e as of specification version 6.0.0.(RTLM7a2e)SetObjectsMapEntry.datato theMapSet.value(RTLM7a2b)SetObjectsMapEntry.timeserialto the providedserial(RTLM7a2c)SetObjectsMapEntry.tombstonetofalse(RTLM7a2d)SetObjectsMapEntry.tombstonedAtto undefined/null
(RTLM7b)If an entry does not exist in the privatedatafor the specified key:(RTLM7b1)This clause has been replaced by RTLM7b4 as of specification version 6.0.0.(RTLM7b4)Create a newObjectsMapEntryindatafor the specified key, withObjectsMapEntry.dataset toMapSet.valueandObjectsMapEntry.timeserialset toserial(RTLM7b2)SetObjectsMapEntry.tombstonefor the new entry tofalse(RTLM7b3)SetObjectsMapEntry.tombstonedAtfor the new entry to undefined/null
(RTLM7c)This clause has been replaced by RTLM7g as of specification version 6.0.0.(RTLM7c1)This clause has been replaced by RTLM7g1 as of specification version 6.0.0.
(RTLM7g)IfMapSet.value.objectIdis non-empty:(RTLM7f)Return aLiveMapUpdateobject with aLiveMapUpdate.updatemap containing the key used in this operation set toupdated, andLiveMapUpdate.objectMessageset to the providedObjectMessage
(RTLM8)AMAP_REMOVEoperation for a key can be applied to anInternalLiveMapin the following way:(RTLM8c)Expects the following arguments:(RTLM8c1)This clause has been replaced by RTLM8c4 as of specification version 6.0.0.(RTLM8c4)MapRemove(RTLM8c2)serialstring - operation's serial value(RTLM8c3)serialTimestampTime - operation's serial timestamp value(RTLM8c5)ObjectMessage- the sourceObjectMessagethat contains the operation
(RTLM8d)The return type is aLiveMapUpdateobject, which indicates the data update for thisInternalLiveMap(RTLM8g)If the privateclearTimeserialis non-null, and the providedserialis null or theclearTimeserialis lexicographically greater than or equal toserial, discard the operation without taking any action. Return aLiveMapUpdateobject withLiveMapUpdate.noopset totrue, indicating that no update was made to the object(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. Return aLiveMapUpdateobject withLiveMapUpdate.noopset totrue, indicating that no update was made to the object(RTLM8a2)Otherwise, apply the operation to the existing entry:(RTLM8a3)BeforeObjectsMapEntry.datais cleared per RTLM8a2a:(RTLM8a2a)SetObjectsMapEntry.datato undefined/null(RTLM8a2b)SetObjectsMapEntry.timeserialto the providedserial(RTLM8a2c)SetObjectsMapEntry.tombstonetotrue(RTLM8a2d)SetObjectsMapEntry.tombstonedAtto the value calculated per RTLO6, using the providedserialTimestamp
(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(RTLM8b3)SetObjectsMapEntry.tombstonedAtfor the new entry to the value calculated per RTLO6, using the providedserialTimestamp
(RTLM8f)This clause has been replaced by RTLO6(RTLM8e)Return aLiveMapUpdateobject with aLiveMapUpdate.updatemap containing the key used in this operation set toremoved, andLiveMapUpdate.objectMessageset to the providedObjectMessage
(RTLM24)AMAP_CLEARoperation can be applied to anInternalLiveMapin the following way:(RTLM24a)Expects the following arguments:(RTLM24a1)serialstring - the operation's serial value(RTLM24a2)ObjectMessage- the sourceObjectMessagethat contains the operation
(RTLM24b)The return type is aLiveMapUpdateobject, which indicates the data update for thisInternalLiveMap(RTLM24c)If the privateclearTimeserialis non-null and is lexicographically greater than the providedserial, discard the operation without taking any action. Return aLiveMapUpdateobject withLiveMapUpdate.noopset totrue, indicating that no update was made to the object(RTLM24d)Set the privateclearTimeserialto the providedserial(RTLM24e)For eachObjectsMapEntryin the internaldata:(RTLM24e1)IfObjectsMapEntry.timeserialis null or omitted, or theserialis lexicographically greater thanObjectsMapEntry.timeserial:(RTLM24e1c)Before theObjectsMapEntryis removed per RTLM24e1a:(RTLM24e1c1)IfObjectsMapEntry.data.objectIdis populated, fetch the object with thisobjectIdfrom theObjectsPool(RTLM24e1c2)If theRTLM24e1c1fetch returned an object, call itsRTLO4hremoveParentReference(parent, key)method, passing thisInternalLiveMapasparentand the iterated entry's key askey
(RTLM24e1a)Remove the entry from the internaldatamap. The entry is not retained as a tombstone.(RTLM24e1b)Record the key for theLiveMapUpdateasremoved
(RTLM24f)Return aLiveMapUpdateobject withLiveMapUpdate.updatecontaining each key recorded in RTLM24e1b set toremoved, andLiveMapUpdate.objectMessageset to the providedObjectMessage
(RTLM9)Whether a map operation can be applied to a map entry is determined as follows:(RTLM9a)For anInternalLiveMapwithsemanticsset 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
(RTLM17)This clause has been replaced by RTLM23 as of specification version 6.0.0.(RTLM23)The initial value from anObjectOperationcan be merged into thisInternalLiveMapin the following way. Expects anObjectOperationand anObjectMessage(the sourceObjectMessagethat contains the operation) as arguments. LetmapCreatebeObjectOperation.mapCreateif present, else theMapCreatefrom whichObjectOperation.mapCreateWithObjectIdwas derived (see RTLMV4j5):(RTLM23a)For each key-ObjectsMapEntrypair inmapCreate.entries:(RTLM23a1)IfObjectsMapEntry.tombstoneisfalseor omitted, apply theMAP_SEToperation to the current key as described in RTLM7, passing inObjectsMapEntry.dataand the current key asMapSet,ObjectsMapEntry.timeserialasserial, and theObjectMessage. Store the returnedLiveMapUpdateobject for use in RTLM23c(RTLM23a2)IfObjectsMapEntry.tombstoneistrue, apply theMAP_REMOVEoperation to the current key as described in RTLM8, passing in the current key asMapRemove,ObjectsMapEntry.timeserialasserial,ObjectsMapEntry.serialTimestampasserialTimestamp, and theObjectMessage. Store the returnedLiveMapUpdateobject for use in RTLM23c
(RTLM23b)Set the private flagcreateOperationIsMergedtotrue(RTLM23c)Return a singleLiveMapUpdateobject, whereLiveMapUpdate.updateis a merged map containing all key-value pairs from theLiveMapUpdate.updatemaps of the storedLiveMapUpdateobjects (skipping any storedLiveMapUpdateobjects marked as no-op), andLiveMapUpdate.objectMessageis set to the providedObjectMessage
(RTLM19)TheInternalLiveMapcan be checked to determine whether it should release resources for its tombstonedObjectsMapEntryentries as follows:(RTLM19a)For eachObjectsMapEntryin the internaldata:(RTLM19a1)IfObjectsMapEntry.tombstoneistrue, and the difference between the current time andObjectsMapEntry.tombstonedAtis greater than or equal to the grace period, remove the entry from the internaldatamap and release resources for the correspondingObjectsMapEntryentity to allow it to be garbage collected
(RTLM22)The diff between twoInternalLiveMapdata values can be calculated in the following way:(RTLM22a)Expects the following arguments:(RTLM22a1)previousDataDict<String, ObjectsMapEntry>- the previousdatavalue(RTLM22a2)newDataDict<String, ObjectsMapEntry>- the newdatavalue
(RTLM22b)Return aLiveMapUpdateobject whereLiveMapUpdate.updateis calculated by considering only the non-tombstoned entries frompreviousDataandnewData. An entry is non-tombstoned if itsObjectsMapEntry.tombstonefield isfalse. The update is populated as follows:(RTLM22b1)For each key that exists in the non-tombstoned entries ofpreviousDatabut does not exist in the non-tombstoned entries ofnewData, add the key toLiveMapUpdate.updatewith the valueremoved(RTLM22b2)For each key that exists in the non-tombstoned entries ofnewDatabut does not exist in the non-tombstoned entries ofpreviousData, add the key toLiveMapUpdate.updatewith the valueupdated(RTLM22b3)For each key that exists in the non-tombstoned entries of bothpreviousDataandnewData, perform a deep comparison of thedataattributes frompreviousDataandnewData. If the data values differ, add the key toLiveMapUpdate.updatewith the valueupdated
A LiveCounter is an immutable blueprint for creating a new InternalLiveCounter object. It stores the desired initial count value and is evaluated when passed to a mutation method such as InternalLiveMap#set (RTLM20) or as an entry value in LiveMap.create (RTLMV3).
(RTLCV1)LiveCounteris an immutable value type representing the intent to create a newInternalLiveCounterwith a specific initial count(RTLCV2)LiveCounterhas the following internal properties:(RTLCV2a)countNumber- the initial count value for theInternalLiveCounterto be created
(RTLCV3)LiveCounter.createstatic factory function:(RTLCV3a)Expects the following arguments:(RTLCV3a1)initialCountNumber(optional) - the initial count for the newInternalLiveCounterobject. Defaults to 0
(RTLCV3b)Returns a newLiveCounterinstance with the internalcountset to the providedinitialCount(or 0 if omitted)(RTLCV3c)No input validation is performed at creation time. Validation is deferred to the evaluation procedure (RTLCV4)(RTLCV3d)The returnedLiveCounteris immutable and must not be modified after creation
(RTLCV4)Internal evaluation procedure - when aLiveCounteris evaluated by a mutation method (e.g.InternalLiveMap#setor as an entry value duringLiveMapevaluation per RTLMV4), aCOUNTER_CREATEObjectMessageis generated as follows:(RTLCV4a)If the internalcountis not undefined and (is not of typeNumberor is not a finite number), the library should throw anErrorInfoerror withstatusCode400 andcode40003, indicating that the counter value must be a valid number(RTLCV4b)Create aCounterCreateobject:(RTLCV4b1)SetCounterCreate.countto the internalcountvalue, or to 0 if undefined
(RTLCV4c)Create an initial value JSON string by generating a JSON string representation of theCounterCreateobject(RTLCV4d)Create a unique string nonce with 16+ characters(RTLCV4e)Get the current server time as described in RTO16(RTLCV4f)Create anobjectIdfor the newInternalLiveCounteras described in RTO14, passing incounteras thetype, the initial value JSON string from RTLCV4c, the nonce from RTLCV4d, and the server time from RTLCV4e(RTLCV4g)Create anObjectMessagewith:(RTLCV4g1)ObjectMessage.operation.actionset toObjectOperationAction.COUNTER_CREATE(RTLCV4g2)ObjectMessage.operation.objectIdset to theobjectIdfrom RTLCV4f(RTLCV4g3)ObjectMessage.operation.counterCreateWithObjectId.nonceset to the nonce from RTLCV4d(RTLCV4g4)ObjectMessage.operation.counterCreateWithObjectId.initialValueset to the JSON string from RTLCV4c(RTLCV4g5)The client library must retain theCounterCreateobject from RTLCV4b alongside theCounterCreateWithObjectId. It is the operation from which theCounterCreateWithObjectIdwas derived, and is needed for message size calculation (OOP4k2) and local application of the operation (RTLC16). ThisCounterCreateis for local use only and must not be sent over the wire.
(RTLCV4h)Return theObjectMessage
A LiveMap is an immutable blueprint for creating a new InternalLiveMap object. It stores the desired initial entries and is evaluated when passed to a mutation method such as InternalLiveMap#set (RTLM20) or as an entry value in another LiveMap.create (RTLMV3) call. Supports arbitrarily deep nesting of LiveMap and LiveCounter values within entries.
(RTLMV1)LiveMapis an immutable value type representing the intent to create a newInternalLiveMapwith specific initial entries(RTLMV2)LiveMaphas the following internal properties:(RTLMV2a)entriesDict<String, Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap>(optional) - the initial entries for theInternalLiveMapto be created
(RTLMV3)LiveMap.createstatic factory function:(RTLMV3a)Expects the following arguments:(RTLMV3a1)entriesDict<String, Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap>(optional) - the initial entries for the newInternalLiveMapobject
(RTLMV3b)Returns a newLiveMapinstance with the internalentriesset to the providedentries(or undefined if omitted)(RTLMV3c)No input validation is performed at creation time. Validation is deferred to the evaluation procedure (RTLMV4)(RTLMV3d)The returnedLiveMapis immutable and must not be modified after creation
(RTLMV4)Internal evaluation procedure - when aLiveMapis evaluated by a mutation method (e.g.InternalLiveMap#setor as an entry value during anotherLiveMapevaluation),ObjectMessagesare generated as follows:(RTLMV4a)If the internalentriesis not undefined and (is null or is not of typeDict), the library should throw anErrorInfoerror withstatusCode400 andcode40003, indicating that entries must be aDict(RTLMV4b)If any of the keys in the internalentriesare not of typeString, the library should throw anErrorInfoerror withstatusCode400 andcode40003, indicating that keys must beString(RTLMV4c)If any of the values in the internalentriesare not of an expected type, the library should throw anErrorInfoerror withstatusCode400 andcode40013, indicating that such data type is unsupported(RTLMV4d)Build entries for theMapCreateobject. For each key-value pair in the internalentries(if present), create anObjectsMapEntryfor the value:(RTLMV4d1)If the value is of typeLiveCounter, evaluate it per RTLCV4 to generate aCOUNTER_CREATEObjectMessage. Collect the generatedObjectMessageand setObjectsMapEntry.data.objectIdto theobjectIdfrom theObjectMessage(RTLMV4d2)If the value is of typeLiveMap, recursively evaluate it per RTLMV4 to generate an ordered array ofObjectMessages. Collect all generatedObjectMessagesand setObjectsMapEntry.data.objectIdto theobjectIdfrom the finalObjectMessagein the array (which is theMAP_CREATEfor theInternalLiveMapwhose creation thisLiveMaprepresents, per RTLMV4k; earlier entries create objects nested within it)(RTLMV4d3)If the value is of typeJsonArrayorJsonObject, setObjectsMapEntry.data.jsonto that value(RTLMV4d4)If the value is of typeString, setObjectsMapEntry.data.stringto that value(RTLMV4d5)If the value is of typeNumber, setObjectsMapEntry.data.numberto that value(RTLMV4d6)If the value is of typeBoolean, setObjectsMapEntry.data.booleanto that value(RTLMV4d7)If the value is of typeBinary, setObjectsMapEntry.data.bytesto that value
(RTLMV4e)Create aMapCreateobject:(RTLMV4e1)SetMapCreate.semanticstoObjectsMapSemantics.LWW(RTLMV4e2)SetMapCreate.entriesto an empty map if the internalentriesis undefined, otherwise to the entries built in RTLMV4d
(RTLMV4f)Create an initial value JSON string based on theMapCreateobject:(RTLMV4f1)TheMapCreateobject may contain user-providedObjectDatathat requires encoding. Encode theObjectDatavalues using the procedure described in OD4(RTLMV4f2)Return a JSON string representation of the encodedMapCreateobject
(RTLMV4g)Create a unique string nonce with 16+ characters(RTLMV4h)Get the current server time as described in RTO16(RTLMV4i)Create anobjectIdfor the newInternalLiveMapas described in RTO14, passing inmapas thetype, the initial value JSON string from RTLMV4f, the nonce from RTLMV4g, and the server time from RTLMV4h(RTLMV4j)Create anObjectMessagewith:(RTLMV4j1)ObjectMessage.operation.actionset toObjectOperationAction.MAP_CREATE(RTLMV4j2)ObjectMessage.operation.objectIdset to theobjectIdfrom RTLMV4i(RTLMV4j3)ObjectMessage.operation.mapCreateWithObjectId.nonceset to the nonce from RTLMV4g(RTLMV4j4)ObjectMessage.operation.mapCreateWithObjectId.initialValueset to the JSON string from RTLMV4f(RTLMV4j5)The client library must retain theMapCreateobject from RTLMV4e alongside theMapCreateWithObjectId. It is the operation from which theMapCreateWithObjectIdwas derived, and is needed for message size calculation (OOP4h2) and local application of the operation (RTLM23). ThisMapCreateis for local use only and must not be sent over the wire.
(RTLMV4k)Return an ordered array containing allObjectMessagescollected from nested value type evaluations in RTLMV4d (in depth-first order), followed by theMAP_CREATEObjectMessagefrom RTLMV4j
A PathObject is a lazy, path-based reference into the LiveObjects graph. It stores a path (as an ordered list of string segments) from the root InternalLiveMap and resolves it at the time each method is called. This means a PathObject survives object replacements: if the object at a given path changes (e.g. via a MAP_SET operation), the same PathObject will resolve to the new object on subsequent calls.
A PathObject is obtained from RealtimeObject#get (RTO23), which returns a PathObject rooted at the channel's root InternalLiveMap with an empty path. Further PathObjects are obtained by navigating with PathObject#get or PathObject#at.
(RTPO1)ThePathObjectclass provides a path-based view over the LiveObjects graph(RTPO1a)A specific SDK implementation may choose to expose a subset of the methods available on thePathObjectclass based on the expected type at the path. For example, when the user provides a type structure as a generic type parameter toRealtimeObject#get, the SDK may use type-specific class names (e.g.LiveMapPathObject,LiveCounterPathObject,PrimitivePathObject) that only expose the methods applicable to that type. The specification describes the generalPathObjectclass with the full set of methods
(RTPO2)PathObjecthas the following internal properties:(RTPO2a)path- an ordered list of string segments representing the path from the rootInternalLiveMapto this position in the graph(RTPO2b)root- a reference to the rootInternalLiveMapinstance from the internalObjectsPool
(RTPO3)Internal path resolution procedure - resolves the storedpathagainst the LiveObjects graph:(RTPO3a)Starting fromroot, walk through the path segments in order. For each segment:(RTPO3a1)The current object must be anInternalLiveMap. If it is not, the resolution has failed(RTPO3a2)Look up the segment as a key in the currentInternalLiveMapusingInternalLiveMap#get(RTLM5). If the result is undefined/null, the resolution has failed(RTPO3a3)The result becomes the current object for the next segment
(RTPO3b)If the path is empty, the result is therootInternalLiveMapitself(RTPO3c)On resolution failure:(RTPO3c1)For read operations (value,instance,entries,keys,values,size,compact,compactJson), return undefined/null. The client library may log a debug or trace message(RTPO3c2)For write operations (set,remove,increment,decrement), the library must throw anErrorInfoerror withstatusCode400 andcode92005, indicating that the path could not be resolved
(RTPO4)PathObject#pathfunction:(RTPO4a)Returns a dot-delimited string representation of the stored path segments(RTPO4b)Any dot characters (.) occurring within individual path segments must be escaped with a backslash (\) in the returned string. For example, a path with segments["a", "b.c", "d"]is represented asa.b\.c.d(RTPO4c)An empty path (rootPathObject) returns an empty string
(RTPO5)PathObject#getfunction:(RTPO5a)Expects the following arguments:(RTPO5a1)keyString- the key to navigate to
(RTPO5b)Ifkeyis not of typeString, the library must throw anErrorInfoerror withstatusCode400 andcode40003, indicating that the key must be aString(RTPO5c)Returns a newPathObjectwith the samerootand withkeyappended to the currentpathsegments(RTPO5d)This is purely navigational and does not resolve the path or access anyLiveObjectdata
(RTPO6)PathObject#atfunction:(RTPO6a)Expects the following arguments:(RTPO6a1)pathString- a dot-delimited path string
(RTPO6b)Parses the dot-delimitedpathstring into individual segments, respecting backslash-escaped dots (a\.sequence is treated as a literal dot within a segment, not a separator)(RTPO6c)Returns a newPathObjectwith the samerootand with the parsed segments appended to the currentpathsegments(RTPO6d)This is a convenience for chaining multiplePathObject#getcalls. For example,pathObject.at("a.b.c")is equivalent topathObject.get("a").get("b").get("c")
(RTPO7)PathObject#valuefunction:(RTPO7a)Checks the access API preconditions per RTO25(RTPO7b)Resolves the path using the path resolution procedure (RTPO3)(RTPO7c)If the resolved value is anInternalLiveCounter, delegates toInternalLiveCounter#value(RTLC5)(RTPO7d)If the resolved value is a primitive (Boolean,Binary,Number,String,JsonArray,JsonObject), returns the value directly(RTPO7e)If the resolved value is anInternalLiveMap, returns undefined/null(RTPO7f)If path resolution fails, returns undefined/null per RTPO3c1
(RTPO8)PathObject#instancefunction:(RTPO8a)Checks the access API preconditions per RTO25(RTPO8b)Resolves the path using the path resolution procedure (RTPO3)(RTPO8c)If the resolved value is aLiveObject(i.e. anInternalLiveMaporInternalLiveCounter), returns a newInstance(RTINS1) wrapping thatLiveObject(RTPO8d)If the resolved value is a primitive, returns undefined/null(RTPO8e)If path resolution fails, returns undefined/null per RTPO3c1
(RTPO9)PathObject#entriesfunction:(RTPO9a)Checks the access API preconditions per RTO25(RTPO9b)Resolves the path using the path resolution procedure (RTPO3)(RTPO9c)If the resolved value is anInternalLiveMap, delegates toInternalLiveMap#keys(RTLM12) and returns an array of[key, PathObject]pairs, where eachPathObjectis created as if by callingPathObject#getwith the corresponding key on thisPathObject(RTPO9d)If the resolved value is not anInternalLiveMap, or if path resolution fails, returns an empty array
(RTPO10)PathObject#keysfunction:(RTPO10a)Checks the access API preconditions per RTO25(RTPO10b)Resolves the path using the path resolution procedure (RTPO3)(RTPO10c)If the resolved value is anInternalLiveMap, delegates toInternalLiveMap#keys(RTLM12)(RTPO10d)If the resolved value is not anInternalLiveMap, or if path resolution fails, returns an empty array
(RTPO11)PathObject#valuesfunction:(RTPO11a)Checks the access API preconditions per RTO25(RTPO11b)Resolves the path using the path resolution procedure (RTPO3)(RTPO11c)If the resolved value is anInternalLiveMap, delegates toInternalLiveMap#keys(RTLM12) and returns an array ofPathObjects, where eachPathObjectis created as if by callingPathObject#getwith the corresponding key on thisPathObject(RTPO11d)If the resolved value is not anInternalLiveMap, or if path resolution fails, returns an empty array
(RTPO12)PathObject#sizefunction:(RTPO12a)Checks the access API preconditions per RTO25(RTPO12b)Resolves the path using the path resolution procedure (RTPO3)(RTPO12c)If the resolved value is anInternalLiveMap, delegates toInternalLiveMap#size(RTLM10)(RTPO12d)If the resolved value is not anInternalLiveMap, or if path resolution fails, returns undefined/null
(RTPO13)PathObject#compactfunction:(RTPO13a)Checks the access API preconditions per RTO25(RTPO13b)Resolves the path using the path resolution procedure (RTPO3)(RTPO13c)If the resolved value is anInternalLiveMap, returns a recursively compacted representation as a plain key-value object:(RTPO13c1)Each entry in theInternalLiveMapis included in the result. Tombstoned entries are excluded(RTPO13c2)NestedInternalLiveMapvalues are recursively compacted into nested plain key-value objects(RTPO13c3)NestedInternalLiveCountervalues are resolved to their numeric value(RTPO13c4)Primitive values (Boolean,Binary,Number,String,JsonArray,JsonObject) are included as-is(RTPO13c5)Cyclic references (anInternalLiveMapthat has already been visited during this compaction) are represented by reusing the same in-memory object reference to the already-compacted result for thatInternalLiveMap
(RTPO13d)If the resolved value is anInternalLiveCounter, returns its current numeric value (equivalent toPathObject#value)(RTPO13e)If the resolved value is a primitive, returns the value directly (equivalent toPathObject#value)(RTPO13f)If path resolution fails, returns undefined/null per RTPO3c1
(RTPO14)PathObject#compactJsonfunction:(RTPO14a)Checks the access API preconditions per RTO25(RTPO14b)Behaves identically toPathObject#compact(RTPO13) except for the following differences, which ensure the result is JSON-serializable:(RTPO14b1)Binaryvalues are encoded as base64 strings instead of being included as-is(RTPO14b2)Cyclic references are represented as an object with a singleobjectIdproperty containing the Object ID of the referencedInternalLiveMap, instead of reusing the in-memory object reference
(RTPO15)PathObject#setfunction:(RTPO15a)Expects the following arguments:(RTPO15a1)keyString- the key to set the value for(RTPO15a2)value- the value to assign to the key. Accepted types are the same as forInternalLiveMap#set(RTLM20)
(RTPO15b)Checks the write API preconditions per RTO26(RTPO15c)Resolves the path using the path resolution procedure (RTPO3). On failure, throws per RTPO3c2(RTPO15d)If the resolved value is anInternalLiveMap, delegates toInternalLiveMap#set(RTLM20) with the providedkeyandvalue(RTPO15e)If the resolved value is not anInternalLiveMap, the library must throw anErrorInfoerror withstatusCode400 andcode92007, indicating that the operation is not supported for the resolved object type
(RTPO16)PathObject#removefunction:(RTPO16a)Expects the following arguments:(RTPO16a1)keyString- the key to remove the value for
(RTPO16b)Checks the write API preconditions per RTO26(RTPO16c)Resolves the path using the path resolution procedure (RTPO3). On failure, throws per RTPO3c2(RTPO16d)If the resolved value is anInternalLiveMap, delegates toInternalLiveMap#remove(RTLM21) with the providedkey(RTPO16e)If the resolved value is not anInternalLiveMap, the library must throw anErrorInfoerror withstatusCode400 andcode92007
(RTPO17)PathObject#incrementfunction:(RTPO17a)Expects the following arguments:(RTPO17a1)amountNumber(optional) - the amount by which to increment the counter value. Defaults to 1
(RTPO17b)Checks the write API preconditions per RTO26(RTPO17c)Resolves the path using the path resolution procedure (RTPO3). On failure, throws per RTPO3c2(RTPO17d)If the resolved value is anInternalLiveCounter, delegates toInternalLiveCounter#increment(RTLC12) with the providedamount(RTPO17e)If the resolved value is not anInternalLiveCounter, the library must throw anErrorInfoerror withstatusCode400 andcode92007
(RTPO18)PathObject#decrementfunction:(RTPO18a)Expects the following arguments:(RTPO18a1)amountNumber(optional) - the amount by which to decrement the counter value. Defaults to 1
(RTPO18b)Checks the write API preconditions per RTO26(RTPO18c)Resolves the path using the path resolution procedure (RTPO3). On failure, throws per RTPO3c2(RTPO18d)If the resolved value is anInternalLiveCounter, delegates toInternalLiveCounter#decrement(RTLC13) with the providedamount(RTPO18e)If the resolved value is not anInternalLiveCounter, the library must throw anErrorInfoerror withstatusCode400 andcode92007
(RTPO19)PathObject#subscribefunction:(RTPO19a)Expects the following arguments:(RTPO19a1)listener- a callback function that receives aPathObjectSubscriptionEvent(RTPO19e)(RTPO19a2)optionsPathObjectSubscriptionOptions(optional) - subscription options
(RTPO19b)Checks the access API preconditions per RTO25(RTPO19c)PathObjectSubscriptionOptionshas the following properties:(RTPO19c1)depthNumber(optional) - controls how many levels of path nesting below the subscription path trigger the listener. Defaults to undefined/null. Thedepthvalue is interpreted by the subscription coverage rule in RTO24c1; see RTO24c2 for worked examples(RTPO19c1a)Ifdepthis provided and is not a positive integer, the library must throw anErrorInfoerror withstatusCode400 andcode40003
(RTPO19d)Returns aSubscriptionobject(RTPO19e)The listener receives aPathObjectSubscriptionEventobject with:(RTPO19e1)object- aPathObjectpointing to the path where the change occurred(RTPO19e2)messagePublicAPI::ObjectMessage(optional) - ifLiveObjectUpdate.objectMessagefrom the RTLO4b4 emission that triggered this event is populated and itsoperationfield is populated, aPublicAPI::ObjectMessage(PAOM1) derived from it per PAOM3; otherwise omitted
(RTPO19f)Adds a subscription to theRealtimeObject'sPathObjectSubscriptionRegister(RTO24) with subscribed path equal to thisPathObject'spath(per RTPO2a), the providedlistener, and the providedoptions.depth(RTPO19g)This operation must not have any side effects onRealtimeObject, the underlying channel, or their status
An Instance holds a direct reference to a specific resolved LiveObject or primitive value. Unlike PathObject which is path-addressed and re-resolves on each call, Instance is identity-addressed: it follows the specific object it was created with, regardless of where that object sits in the graph.
(RTINS1)TheInstanceclass provides a direct-reference view of aLiveObjector primitive value(RTINS1a)A specific SDK implementation may choose to expose a subset of the methods available on theInstanceclass based on the known underlying type. For example, the SDK may use type-specific class names (e.g.LiveMapInstance,LiveCounterInstance,PrimitiveInstance) that only expose the methods applicable to the wrapped type. The specification describes the generalInstanceclass with the full set of methods
(RTINS2)Instancehas the following internal properties:(RTINS2a)value- a reference to the wrappedLiveObjector primitive value
(RTINS3)Instance#idproperty:(RTINS3a)If the wrapped value is aLiveObject, returns theobjectIdof that object(RTINS3b)If the wrapped value is a primitive, returns undefined/null
(RTINS4)Instance#valuefunction:(RTINS4a)Checks the access API preconditions per RTO25(RTINS4b)If the wrapped value is anInternalLiveCounter, delegates toInternalLiveCounter#value(RTLC5)(RTINS4c)If the wrapped value is a primitive (Boolean,Binary,Number,String,JsonArray,JsonObject), returns the value directly(RTINS4d)If the wrapped value is anInternalLiveMap, returns undefined/null
(RTINS5)Instance#getfunction:(RTINS5a)Expects the following arguments:(RTINS5a1)keyString- the key to look up
(RTINS5b)Checks the access API preconditions per RTO25(RTINS5c)If the wrapped value is anInternalLiveMap, looks up the value atkeyusingInternalLiveMap#get(RTLM5) and returns a newInstancewrapping the result. If the result is undefined/null, returns undefined/null(RTINS5d)If the wrapped value is not anInternalLiveMap, returns undefined/null
(RTINS6)Instance#entriesfunction:(RTINS6a)Checks the access API preconditions per RTO25(RTINS6b)If the wrapped value is anInternalLiveMap, delegates toInternalLiveMap#entries(RTLM11) and returns an array of[key, Instance]pairs, where eachInstancewraps the corresponding value(RTINS6c)If the wrapped value is not anInternalLiveMap, returns an empty array
(RTINS7)Instance#keysfunction:(RTINS8)Instance#valuesfunction:(RTINS8a)Checks the access API preconditions per RTO25(RTINS8b)If the wrapped value is anInternalLiveMap, delegates toInternalLiveMap#values(RTLM13) and returns an array ofInstances, where eachInstancewraps the corresponding value(RTINS8c)If the wrapped value is not anInternalLiveMap, returns an empty array
(RTINS9)Instance#sizefunction:(RTINS10)Instance#compactfunction:(RTINS11)Instance#compactJsonfunction:(RTINS12)Instance#setfunction:(RTINS12a)Expects the following arguments:(RTINS12a1)keyString- the key to set the value for(RTINS12a2)value- the value to assign to the key. Accepted types are the same as forInternalLiveMap#set(RTLM20)
(RTINS12b)Checks the write API preconditions per RTO26(RTINS12c)If the wrapped value is anInternalLiveMap, delegates toInternalLiveMap#set(RTLM20) with the providedkeyandvalue(RTINS12d)If the wrapped value is not anInternalLiveMap, the library must throw anErrorInfoerror withstatusCode400 andcode92007
(RTINS13)Instance#removefunction:(RTINS13a)Expects the following arguments:(RTINS13a1)keyString- the key to remove the value for
(RTINS13b)Checks the write API preconditions per RTO26(RTINS13c)If the wrapped value is anInternalLiveMap, delegates toInternalLiveMap#remove(RTLM21) with the providedkey(RTINS13d)If the wrapped value is not anInternalLiveMap, the library must throw anErrorInfoerror withstatusCode400 andcode92007
(RTINS14)Instance#incrementfunction:(RTINS14a)Expects the following arguments:(RTINS14a1)amountNumber(optional) - the amount by which to increment the counter value. Defaults to 1
(RTINS14b)Checks the write API preconditions per RTO26(RTINS14c)If the wrapped value is anInternalLiveCounter, delegates toInternalLiveCounter#increment(RTLC12) with the providedamount(RTINS14d)If the wrapped value is not anInternalLiveCounter, the library must throw anErrorInfoerror withstatusCode400 andcode92007
(RTINS15)Instance#decrementfunction:(RTINS15a)Expects the following arguments:(RTINS15a1)amountNumber(optional) - the amount by which to decrement the counter value. Defaults to 1
(RTINS15b)Checks the write API preconditions per RTO26(RTINS15c)If the wrapped value is anInternalLiveCounter, delegates toInternalLiveCounter#decrement(RTLC13) with the providedamount(RTINS15d)If the wrapped value is not anInternalLiveCounter, the library must throw anErrorInfoerror withstatusCode400 andcode92007
(RTINS16)Instance#subscribefunction:(RTINS16a)Expects the following arguments:(RTINS16a1)listener- a callback function that receives anInstanceSubscriptionEvent(RTINS16e) when the wrapped object is updated
(RTINS16b)Checks the access API preconditions per RTO25(RTINS16c)If the wrapped value is not aLiveObject(i.e. it is a primitive), the library must throw anErrorInfoerror withstatusCode400 andcode92007, indicating that subscribe is not supported for primitive values(RTINS16d)Subscribes to data updates on the underlyingLiveObjectusingLiveObject#subscribe(RTLO4b)(RTINS16e)The listener receives anInstanceSubscriptionEventobject with:(RTINS16e1)object- anInstancewrapping the underlyingLiveObject(RTINS16e2)messagePublicAPI::ObjectMessage(optional) - ifLiveObjectUpdate.objectMessagefrom the underlyingLiveObject#subscribenotification is populated and itsoperationfield is populated, aPublicAPI::ObjectMessage(PAOM1) derived from it per PAOM3; otherwise omitted
(RTINS16f)Returns aSubscriptionobject(RTINS16g)The subscription is identity-based: it follows the specificLiveObjectinstance, regardless of where it sits in the graph(RTINS16h)This operation must not have any side effects onRealtimeObject, the underlying channel, or their status
(PAOM1)APublicAPI::ObjectMessageis the user-facing representation of an inboundObjectMessage(OM1) that carried an operation. It is delivered to user subscription listeners (see RTPO19e2, RTINS16e2) so that user code can inspect the metadata of the message that triggered an object change. ThePublicAPI::prefix is used to avoid a name clash withObjectMessage; SDKs expose this type to users asObjectMessage.(PAOM2)The attributes available in aPublicAPI::ObjectMessageare:(PAOM2a)idstring (optional) - theid(OM2a) of the sourceObjectMessage(PAOM2b)clientIdstring (optional) - theclientId(OM2b) of the sourceObjectMessage(PAOM2c)connectionIdstring (optional) - theconnectionId(OM2c) of the sourceObjectMessage(PAOM2d)timestampTime (optional) - thetimestamp(OM2e) of the sourceObjectMessage(PAOM2e)channelstring - the name of the channel on which the sourceObjectMessagewas received(PAOM2f)operationPublicAPI::ObjectOperation(PAOOP1) - aPublicAPI::ObjectOperationderived per PAOOP3 from theoperation(OM2f) of the sourceObjectMessage(PAOM2g)serialstring (optional) - theserial(OM2h) of the sourceObjectMessage(PAOM2h)serialTimestampTime (optional) - theserialTimestamp(OM2j) of the sourceObjectMessage(PAOM2i)siteCodestring (optional) - thesiteCode(OM2i) of the sourceObjectMessage(PAOM2j)extrasJSON-encodable object (optional) - theextras(OM2d) of the sourceObjectMessage
(PAOM3)To construct aPublicAPI::ObjectMessagefrom a sourceObjectMessagereceived on a channelchannel:(PAOM3a)Preconditions (callers are responsible for ensuring these):(PAOM3a1)The sourceObjectMessagehas itsoperation(OM2f) field populated
(PAOM3b)Set thechannelattribute tochannel.name(PAOM3c)Copyid,clientId,connectionId,timestamp,serial,serialTimestamp,siteCode, andextrasfrom the sourceObjectMessageto the corresponding attributes of thePublicAPI::ObjectMessage(PAOM3d)Setoperationto aPublicAPI::ObjectOperationderived per PAOOP3 from theoperation(OM2f) of the sourceObjectMessage
(PAOOP1)APublicAPI::ObjectOperationis the user-facing representation of anObjectOperation(OOP1). It is the type of theoperationattribute of aPublicAPI::ObjectMessage(PAOM2f). ThePublicAPI::prefix is used to avoid a name clash withObjectOperation; SDKs expose this type to users asObjectOperation. It differs fromObjectOperationin that it does not carry themapCreateWithObjectId(OOP3p) orcounterCreateWithObjectId(OOP3q) variants: these are outbound-only representations that are resolved back to their derivedMapCreate/CounterCreateforms when constructing aPublicAPI::ObjectOperation.(PAOOP2)The attributes available in aPublicAPI::ObjectOperationare:(PAOOP2a)actionObjectOperationAction(OOP2) - theaction(OOP3a) of the sourceObjectOperation(PAOOP2b)objectIdstring - theobjectId(OOP3b) of the sourceObjectOperation(PAOOP2c)mapCreateMapCreate(optional) - theMapCreatepayload, if applicable (see PAOOP3b)(PAOOP2d)mapSetMapSet(optional) - themapSet(OOP3k) of the sourceObjectOperation(PAOOP2e)mapRemoveMapRemove(optional) - themapRemove(OOP3l) of the sourceObjectOperation(PAOOP2f)counterCreateCounterCreate(optional) - theCounterCreatepayload, if applicable (see PAOOP3c)(PAOOP2g)counterIncCounterInc(optional) - thecounterInc(OOP3n) of the sourceObjectOperation(PAOOP2h)objectDeleteObjectDelete(optional) - theobjectDelete(OOP3o) of the sourceObjectOperation(PAOOP2i)mapClearMapClear(optional) - themapClear(OOP3r) of the sourceObjectOperation
(PAOOP3)To construct aPublicAPI::ObjectOperationfrom a sourceObjectOperation:(PAOOP3a)Copyaction,objectId,mapSet,mapRemove,counterInc,objectDelete, andmapClearfrom the sourceObjectOperationto the corresponding attributes of thePublicAPI::ObjectOperation(PAOOP3b)SetmapCreateas follows:(PAOOP3c)SetcounterCreateas follows:(PAOOP3c1)IfcounterCreate(OOP3m) is present on the source, setcounterCreateto that value(PAOOP3c2)Else ifcounterCreateWithObjectId(OOP3q) is present on the source, setcounterCreateto theCounterCreatefrom which it was derived (retained per RTLCV4g5)(PAOOP3c3)Otherwise omitcounterCreate
Describes types for RealtimeObject.
Types and their properties/methods are public and exposed to users by default. An internal label may be used to indicate that a type or its property/method must not be exposed to users and is intended for internal SDK use only.
class RealtimeObject: // RTO*
get() => io PathObject // RTO23
on(ObjectsEvent event, (() ->) callback) -> StatusSubscription // RTO18
off(() ->) // RTO19
publish(ObjectMessage[]) => io PublishResult // RTO15, internal
publishAndApply(ObjectMessage[]) => io // RTO20, internal
enum ObjectsSyncState: // RTO17a
INITIALIZED // RTO17a1
SYNCING // RTO17a2
SYNCED // RTO17a3
enum ObjectsEvent: // RTO18b
SYNCING // RTO18b1
SYNCED // RTO18b2
enum ObjectsOperationSource: // RTO22, internal
LOCAL // RTO22a
CHANNEL // RTO22b
interface StatusSubscription: // RTO18f
off() // RTO18f1
class LiveObject: // RTLO*, internal
objectId: String // RTLO3a
siteTimeserials: Dict<String, String> // RTLO3b
createOperationIsMerged: Boolean // RTLO3c
isTombstone: Boolean // RTLO3d
tombstonedAt: Time? // RTLO3e
parentReferences: Dict<String, Set<String>> // RTLO3f
canApplyOperation(ObjectMessage) -> Boolean // RTLO4a
tombstone(ObjectMessage) -> LiveObjectUpdate // RTLO4e
getFullPaths() -> String[][] // RTLO4f
addParentReference(InternalLiveMap parent, String key) // RTLO4g
removeParentReference(InternalLiveMap parent, String key) // RTLO4h
subscribe((LiveObjectUpdate) ->) -> Subscription // RTLO4b
interface LiveObjectUpdate: // RTLO4b4, internal
update: Object // RTLO4b4a
noop: Boolean // RTLO4b4b
objectMessage: ObjectMessage? // RTLO4b4d
tombstone: Boolean // RTLO4b4e
class InternalLiveCounter extends LiveObject: // RTLC*, RTLC1, internal
value() -> Number // RTLC5
increment(Number amount) => io // RTLC12
decrement(Number amount) => io // RTLC13
interface LiveCounterUpdate extends LiveObjectUpdate: // RTLC11, RTLC11a, internal
update: { amount: Number } // RTLC11b, RTLC11b1
class InternalLiveMap extends LiveObject: // RTLM*, RTLM1, internal
clearTimeserial: String? // RTLM25
get(key: String) -> (Boolean | Binary | Number | String | JsonArray | JsonObject | InternalLiveCounter | InternalLiveMap)? // RTLM5
size() -> Number // RTLM10
entries() -> [String, (Boolean | Binary | Number | String | JsonArray | JsonObject | InternalLiveCounter | InternalLiveMap)?][] // RTLM11
keys() -> String[] // RTLM12
values() -> (Boolean | Binary | Number | String | JsonArray | JsonObject | InternalLiveCounter | InternalLiveMap)?[] // RTLM13
set(String key, (Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap) value) => io // RTLM20
remove(String key) => io // RTLM21
interface LiveMapUpdate extends LiveObjectUpdate: // RTLM18, RTLM18a, internal
update: Dict<String, 'updated' | 'removed'> // RTLM18b
class LiveCounter: // RTLCV*
count: Number // RTLCV2a, internal
static create(Number initialCount?) -> LiveCounter // RTLCV3
class LiveMap: // RTLMV*
entries: Dict<String, (Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap)>? // RTLMV2a, internal
static create(Dict<String, Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap> entries?) -> LiveMap // RTLMV3
interface PathObjectSubscriptionEvent: // RTPO19e
object: PathObject // RTPO19e1
message: PublicAPI::ObjectMessage? // RTPO19e2
interface PathObjectSubscriptionOptions: // RTPO19c
depth: Number? // RTPO19c1
interface InstanceSubscriptionEvent: // RTINS16e
object: Instance // RTINS16e1
message: PublicAPI::ObjectMessage? // RTINS16e2
class PublicAPI::ObjectMessage: // PAOM*
id: String? // PAOM2a
clientId: String? // PAOM2b
connectionId: String? // PAOM2c
timestamp: Time? // PAOM2d
channel: String // PAOM2e
operation: PublicAPI::ObjectOperation // PAOM2f
serial: String? // PAOM2g
serialTimestamp: Time? // PAOM2h
siteCode: String? // PAOM2i
extras: JsonObject? // PAOM2j
class PublicAPI::ObjectOperation: // PAOOP*
action: ObjectOperationAction // PAOOP2a
objectId: String // PAOOP2b
mapCreate: MapCreate? // PAOOP2c
mapSet: MapSet? // PAOOP2d
mapRemove: MapRemove? // PAOOP2e
counterCreate: CounterCreate? // PAOOP2f
counterInc: CounterInc? // PAOOP2g
objectDelete: ObjectDelete? // PAOOP2h
mapClear: MapClear? // PAOOP2i
class PathObject: // RTPO*
path() -> String // RTPO4
get(String key) -> PathObject // RTPO5
at(String path) -> PathObject // RTPO6
value() -> (Boolean | Binary | Number | String | JsonArray | JsonObject)? // RTPO7
instance() -> Instance? // RTPO8
entries() -> [String, PathObject][] // RTPO9
keys() -> String[] // RTPO10
values() -> PathObject[] // RTPO11
size() -> Number? // RTPO12
compact() -> Object? // RTPO13
compactJson() -> Object? // RTPO14
set(String key, (Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap) value) => io // RTPO15
remove(String key) => io // RTPO16
increment(Number amount?) => io // RTPO17
decrement(Number amount?) => io // RTPO18
subscribe((PathObjectSubscriptionEvent) -> listener, PathObjectSubscriptionOptions? options) -> Subscription // RTPO19
class Instance: // RTINS*
id: String? // RTINS3
value() -> (Boolean | Binary | Number | String | JsonArray | JsonObject)? // RTINS4
get(String key) -> Instance? // RTINS5
entries() -> [String, Instance][] // RTINS6
keys() -> String[] // RTINS7
values() -> Instance[] // RTINS8
size() -> Number? // RTINS9
compact() -> Object? // RTINS10
compactJson() -> Object? // RTINS11
set(String key, (Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap) value) => io // RTINS12
remove(String key) => io // RTINS13
increment(Number amount?) => io // RTINS14
decrement(Number amount?) => io // RTINS15
subscribe((InstanceSubscriptionEvent) -> listener) -> Subscription // RTINS16