Skip to content

Latest commit

 

History

History
180 lines (169 loc) · 18.4 KB

File metadata and controls

180 lines (169 loc) · 18.4 KB
title Objects Features
section client-lib-development-guide
index 65
jump_to
Help with
Objects Features Overview#overview

Overview

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.

RealtimeObjects

  • (RTO1) Objects#getRoot function:
    • (RTO1a) Requires the OBJECT_SUBSCRIBE channel mode to be granted per RTO2
    • (RTO1b) If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
    • (RTO1c) Waits for the objects sync sequence to complete and for RTO5c to finish
    • (RTO1d) Returns the object with id root from the internal ObjectsPool as a LiveMap
  • (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 the ATTACHED state, 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 an ErrorInfo error with statusCode 400 and code 40024, 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 an ErrorInfo error with statusCode 400 and code 40024, indicating that the operation cannot be performed without the required channel mode
  • (RTO3) An internal ObjectsPool should be used to maintain the list of objects present on a channel
    • (RTO3a) ObjectsPool is a Dict<String, LiveObject> – a map of @LiveObject@s keyed by objectId string
    • (RTO3b) It must always contain a LiveMap object with id root
      • (RTO3b1) Upon initialization of the ObjectsPool, create a new LiveMap (per RTLM4) with objectId set to root and add it to the ObjectsPool
  • (RTO4) When a channel ATTACHED ProtocolMessage is received, the ProtocolMessage may contain a HAS_OBJECTS bit flag indicating that it will perform an objects sync, see TR3 . Note that this does not imply that objects are definitely present on the channel, only that there may be; the OBJECT_SYNC message may be empty
    • (RTO4a) If the HAS_OBJECTS flag is 1, the server will shortly perform an OBJECT_SYNC sequence as described in RTO5
    • (RTO4b) If the HAS_OBJECTS flag is 0 or there is no flags field, the sync sequence must be considered complete immediately, and the client library must perform the following actions in order:
      • (RTO4b1) All objects except the one with id root must be removed from the internal ObjectsPool
      • (RTO4b2) The data for the LiveMap with id root must be cleared by setting it to a zero-value per RTLM4. Note that the client SDK must not create a new LiveMap instance with id root; it must only clear the internal data of the existing LiveMap with id root
      • (RTO4b3) The SyncObjectsPool must be cleared
      • (RTO4b4) Perform the actions for objects sync completion as described in RTO5c
  • (RTO5) The realtime system reserves the right to initiate an objects sync of the objects on a channel at any point once a channel is attached. A server initiated objects sync provides Ably with a means to send a complete list of objects present on the channel at any point
    • (RTO5d) If an OBJECT_SYNC ProtocolMessage is received and ObjectMessage.object is null or omitted, the client library should skip processing that ProtocolMessage
    • (RTO5a) When an OBJECT_SYNC ProtocolMessage is received with a channel attribute matching the channel name, the client library must parse the channelSerial attribute:
      • (RTO5a1) The channelSerial is used as the sync cursor and is a two-part identifier: <sequence id>:<cursor value>
      • (RTO5a2) If a new sequence id is sent from Ably, the client library must treat it as the start of a new objects sync sequence, and any previous in-flight sync must be discarded:
        • (RTO5a2a) The current SyncObjectsPool list must be cleared
      • (RTO5a3) If the sequence id matches the previously received sequence id, the client library should continue the sync process
      • (RTO5a4) The objects sync sequence for that sequence identifier is considered complete once the cursor is empty; that is when the channelSerial looks like <sequence id>:
      • (RTO5a5) An OBJECT_SYNC may also be sent with no channelSerial attribute. In this case, the sync data is entirely contained within the ProtocolMessage
    • (RTO5b) During the sync sequence, the ObjectMessage.object values from incoming OBJECT_SYNC @ProtocolMessage@s must be temporarily stored in the internal SyncObjectsPool list
    • (RTO5c) When the objects sync has completed, the client library must perform the following actions in order:
      • (RTO5c1) For each ObjectState in the SyncObjectsPool list:
        • (RTO5c1a) If an object with ObjectState.objectId exists in the internal ObjectsPool:
          • (RTO5c1a1) Replace the internal data for the object as described in RTLC6 or RTLM6 depending on the object type, passing in current ObjectState
        • (RTO5c1b) If an object with ObjectState.objectId does not exist in the internal ObjectsPool:
          • (RTO5c1b1) Create a new LiveObject using the data from ObjectState and add it to the internal ObjectsPool:
            • (RTO5c1b1a) If ObjectState.counter is present, create a zero-value LiveCounter (per RTLC4), set its private objectId equal to ObjectState.objectId and replace its internal data using the current ObjectState per RTLC6
            • (RTO5c1b1b) If ObjectState.map is present, create a zero-value LiveMap (per RTLM4), set its private objectId equal to ObjectState.objectId, set its private semantics equal to ObjectState.map.semantics and replace its internal data using the current ObjectState per RTLM6
            • (RTO5c1b1c) Otherwise, log a warning that an unsupported object state message has been received, and discard the current ObjectState without taking any action
      • (RTO5c2) Remove any objects from the internal ObjectsPool for which @objectId@s were not received during the sync sequence
        • (RTO5c2a) The object with ID root must not be removed from ObjectsPool, as per RTO3b
      • (RTO5c3) Clear any stored sync sequence identifiers and cursor values
      • (RTO5c4) The SyncObjectsPool must be cleared
  • (RTO6) Certain object operations may require creating a zero-value object if one does not already exist in the internal ObjectsPool for the given objectId. This can be done as follows:
    • (RTO6a) If an object with objectId exists in ObjectsPool, do not create a new object
    • (RTO6b) The expected type of the object can be inferred from the provided objectId:
      • (RTO6b1) Split the objectId (formatted as type:hash&#64;timestamp) on the separator : and parse the first part as the type string
      • (RTO6b2) If the parsed type is map, create a zero-value LiveMap per RTLM4 in the ObjectsPool
      • (RTO6b3) If the parsed type is counter, create a zero-value LiveCounter per RTLC4 in the ObjectsPool

LiveObject

  • (RTLO1) The LiveObject represents the common interface and includes shared functionality for concrete object types

LiveCounter

  • (RTLC1) The LiveCounter extends LiveObject
  • (RTLC2) Represents the counter object type for Object IDs of type counter
  • (RTLC3) Holds a 64-bit floating-point number as a private data
  • (RTLC4) The zero-value LiveCounter is a LiveCounter with data set to 0
  • (RTLC5) LiveCounter#value function:
    • (RTLC5a) Requires the OBJECT_SUBSCRIBE channel mode to be granted per RTO2
    • (RTLC5b) If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
    • (RTLC5c) Returns the current data value
  • (RTLC6) @LiveCounter@’s internal data can be replaced with the provided ObjectState in the following way:
    • (RTLC6a) Replace the private siteTimeserials of the LiveCounter with the value from ObjectState.siteTimeserials
    • (RTLC6b) Set the private flag createOperationIsMerged to false
    • (RTLC6c) Set data to the value of ObjectState.counter.count, or to 0 if it does not exist
    • (RTLC6d) If ObjectState.createOp is present:
      • (RTLC6d1) Add ObjectState.createOp.counter.count to data, if it exists
      • (RTLC6d2) Set the private flag createOperationIsMerged to true

LiveMap

  • (RTLM1) The LiveMap extends LiveObject
  • (RTLM2) Represents the map object type for Object IDs of type map
  • (RTLM3) Holds a Dict<String, ObjectsMapEntry> as a private data map
  • (RTLM4) The zero-value LiveMap is a LiveMap with data set to an empty map
  • (RTLM5) LiveMap#get function:
    • (RTLM5a) Accepts a key of type String
    • (RTLM5b) Requires the OBJECT_SUBSCRIBE channel mode to be granted per RTO2
    • (RTLM5c) If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
    • (RTLM5d) Returns the value from the current data at the specified key, as follows:
      • (RTLM5d1) If no ObjectsMapEntry exists at the key, return undefined/null
      • (RTLM5d2) If an ObjectsMapEntry exists at the key:
        • (RTLM5d2a) If ObjectsMapEntry.tombstone is true, return undefined/null
        • (RTLM5d2b) If ObjectsMapEntry.data.boolean exists, return it
        • (RTLM5d2c) If ObjectsMapEntry.data.bytes exists, return it
        • (RTLM5d2d) If ObjectsMapEntry.data.number exists, return it
        • (RTLM5d2e) If ObjectsMapEntry.data.string exists, return it
        • (RTLM5d2f) If ObjectsMapEntry.data.objectId exists, get the object stored at that objectId from the internal ObjectsPool:
          • (RTLM5d2f1) If an object with id objectId does not exist, return undefined/null
          • (RTLM5d2f2) If an object with id objectId exists, return it
        • (RTLM5d2g) Otherwise, return undefined/null
  • (RTLM10) LiveMap#size:
    • (RTLM10a) A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a property similar to Map.size for the native Map class
    • (RTLM10b) Requires the OBJECT_SUBSCRIBE channel mode to be granted per RTO2
    • (RTLM10c) If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
    • (RTLM10d) Returns the number of non-tombstoned entries (per RTLM14) in the internal data map
  • (RTLM11) LiveMap#entries:
    • (RTLM11a) A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a method similar to Map.entries() for the native Map class
    • (RTLM11b) Requires the OBJECT_SUBSCRIBE channel mode to be granted per RTO2
    • (RTLM11c) If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
    • (RTLM11d) Returns key-value pairs from the internal data map:
      • (RTLM11d1) Pairs with tombstoned entries (per RTLM14) are not returned
      • (RTLM11d3) ObjectsMapEntry values are mapped to user-facing values following the same procedure as in RTLM5d2
        • (RTLM11d3a) Note that if RTLM5d2 results in an ObjectsMapEntry being mapped to an undefined/null value, the corresponding key-value pair is still returned by this LiveMap#entries call
      • (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 by Map.entries() method for the native Map class
  • (RTLM12) LiveMap#keys:
    • (RTLM12a) A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a method similar to Map.keys() for the native Map class
    • (RTLM12b) The implementation is identical to LiveMap#entries, except that it returns only the keys from the internal data map
  • (RTLM13) LiveMap#values:
    • (RTLM13a) A method or property, depending on what is more idiomatic for the platform to use for a Map/Dictionary interface. For example, in JavaScript, this is a method similar to Map.values() for the native Map class
    • (RTLM13b) The implementation is identical to LiveMap#entries, except that it returns only the values from the internal data map
  • (RTLM14) An ObjectsMapEntry in the internal data map can be checked for being tombstoned using the convenience method:
    • (RTLM14a) The method returns true if ObjectsMapEntry.tombstone is true
    • (RTLM14b) Otherwise, it returns false
  • (RTLM6) LiveMap internal data can be replaced with the provided ObjectState in the following way:
    • (RTLM6a) Replace the private siteTimeserials of the LiveMap with the value from ObjectState.siteTimeserials
    • (RTLM6b) Set the private flag createOperationIsMerged to false
    • (RTLM6c) Set data to ObjectState.map.entries, or to an empty map if it does not exist
    • (RTLM6d) If ObjectState.createOp is present:
      • (RTLM6d1) For each key–@ObjectsMapEntry@ pair in ObjectState.createOp.map.entries:
        • (RTLM6d1a) If ObjectsMapEntry.tombstone is false or omitted, apply the MAP_SET operation to the current key as described in RTLM7, passing in ObjectsMapEntry.data and the current key as ObjectsMapOp, and ObjectsMapEntry.timeserial as serial
        • (RTLM6d1b) If ObjectsMapEntry.tombstone is true, apply the MAP_REMOVE operation to the current key as described in RTLM8, passing in the current key as ObjectsMapOp, and ObjectsMapEntry.timeserial as serial
      • (RTLM6d2) Set the private flag createOperationIsMerged to true
  • (RTLM7) MAP_SET operation for a key can be applied to a LiveMap in the following way:
    • (RTLM7d) Expects the following arguments:
      • (RTLM7d1) ObjectsMapOp
      • (RTLM7d2) serial string – operation’s serial value
    • (RTLM7a) If an ObjectsMapEntry exists in the private data for the specified key:
      • (RTLM7a1) If the operation cannot be applied to the existing entry as per RTLM9, discard the operation without taking any action
      • (RTLM7a2) Otherwise, apply the operation to the existing entry:
        • (RTLM7a2a) Set ObjectsMapEntry.data to the ObjectData from the operation
        • (RTLM7a2b) Set ObjectsMapEntry.timeserial to the provided serial
        • (RTLM7a2c) Set ObjectsMapEntry.tombstone to false
    • (RTLM7b) If an entry does not exist in the private data for the specified key:
      • (RTLM7b1) Create a new ObjectsMapEntry in data for the specified key, with ObjectsMapEntry.data set to the provided ObjectData and ObjectsMapEntry.timeserial set to serial
      • (RTLM7b2) Set ObjectsMapEntry.tombstone for the new entry to false
    • (RTLM7c) If the operation has a non-empty ObjectData.objectId attribute:
      • (RTLM7c1) Create a zero-value LiveObject for this ObjectData.objectId in the internal ObjectsPool per RTO6
  • (RTLM8) MAP_REMOVE operation for a key can be applied to a LiveMap in the following way:
    • (RTLM8c) Expects the following arguments:
      • (RTLM8c1) ObjectsMapOp
      • (RTLM8c2) serial string – operation’s serial value
    • (RTLM8a) If an ObjectsMapEntry exists in the private data for the specified key:
      • (RTLM8a1) If the operation cannot be applied to the existing entry as per RTLM9, discard the operation without taking any action
      • (RTLM8a2) Otherwise, apply the operation to the existing entry:
        • (RTLM8a2a) Set ObjectsMapEntry.data to undefined/null
        • (RTLM8a2b) Set ObjectsMapEntry.timeserial to the provided serial
        • (RTLM8a2c) Set ObjectsMapEntry.tombstone to true
    • (RTLM8b) If an entry does not exist in the private data for the specified key:
      • (RTLM8b1) Create a new ObjectsMapEntry in data for the specified key, with ObjectsMapEntry.data set to undefined/null and ObjectsMapEntry.timeserial set to the provided serial
      • (RTLM8b2) Set ObjectsMapEntry.tombstone for the new entry to true
  • (RTLM9) Whether a map operation can be applied to a map entry is determined as follows:
    • (RTLM9a) For a LiveMap with semantics set to ObjectsMapSemantics.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