diff --git a/specifications/features.md b/specifications/features.md index e363354d..6df4e2d5 100644 --- a/specifications/features.md +++ b/specifications/features.md @@ -1299,7 +1299,7 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info #### ObjectOperation - `(OOP1)` An `ObjectOperation` describes an operation to be applied to an object on a channel -- `(OOP2)` `ObjectOperationAction` enum has the following values in order from zero: `MAP_CREATE`, `MAP_SET`, `MAP_REMOVE`, `COUNTER_CREATE`, `COUNTER_INC`, `OBJECT_DELETE` +- `(OOP2)` `ObjectOperationAction` enum has the following values in order from zero: `MAP_CREATE`, `MAP_SET`, `MAP_REMOVE`, `COUNTER_CREATE`, `COUNTER_INC`, `OBJECT_DELETE`, `MAP_CLEAR` - `(OOP3)` The attributes available in an `ObjectOperation` are: - `(OOP3a)` `action` `ObjectOperationAction` enum - defines the operation to be applied to the object - `(OOP3b)` `objectId` string - the object ID of the object on a channel to which the operation should be applied @@ -1318,6 +1318,7 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info - `(OOP3o)` `objectDelete` `ObjectDelete` object - the payload for an `OBJECT_DELETE` operation - `(OOP3p)` `mapCreateWithObjectId` `MapCreateWithObjectId` object - the payload for a `MAP_CREATE` operation when sent to the server with a client-specified object ID - `(OOP3q)` `counterCreateWithObjectId` `CounterCreateWithObjectId` object - the payload for a `COUNTER_CREATE` operation when sent to the server with a client-specified object ID + - `(OOP3r)` `mapClear` `MapClear` object - the payload for a `MAP_CLEAR` operation - `(OOP4)` The size of the `ObjectOperation` is calculated as follows: - `(OOP4a)` This clause has been replaced by [OOP4g](#OOP4g) as of specification version 6.0.0. - `(OOP4b)` This clause has been replaced by [OOP4i](#OOP4i) and [OOP4j](#OOP4j) as of specification version 6.0.0. @@ -1432,6 +1433,11 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info - `(CCRO2a)` `initialValue` string - a JSON string representation of the encoded `CounterCreate` that contains the initial value for the counter - `(CCRO2b)` `nonce` string - the nonce used to generate the object ID +#### MapClear + +- `(MCL1)` A `MapClear` describes the payload for a `MAP_CLEAR` operation on a map object +- `(MCL2)` This type has no attributes + #### ObjectsMapOp - `(OMO1)` This clause has been deleted (redundant to [MST1](#MST1) and [MRM1](#MRM1)) as of specification version 6.0.0. @@ -1460,6 +1466,7 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info - `(OMP3)` The attributes available in an `ObjectsMap` are: - `(OMP3a)` `semantics` `ObjectsMapSemantics` enum - the conflict-resolution semantics used by the map object - `(OMP3b)` `entries` `Dict` - the map entries, indexed by key + - `(OMP3c)` `clearTimeserial` string - the [serial](#OM2h) value of the last `MAP_CLEAR` operation applied to the map. If no `MAP_CLEAR` has been applied, this field is omitted - `(OMP4)` The size of the `ObjectsMap` is calculated as follows: - `(OMP4a)` The size is the sum of the sizes of all map entries in `entries` property - `(OMP4a1)` Includes the size of the `String` key for the map entry, calculated as its length @@ -1480,7 +1487,7 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info - `(OME1)` An `ObjectsMapEntry` represents the value at a given key in an `ObjectsMap` object - `(OME2)` The attributes available in an `ObjectsMapEntry` are: - `(OME2a)` `tombstone` boolean - indicates whether the map entry has been removed - - `(OME2b)` `timeserial` string - the `serial`#OM2h value of the last operation that was applied to the map entry + - `(OME2b)` `timeserial` string - the "serial":#OM2h value of the last operation that was applied to the map entry - `(OME2d)` `serialTimestamp` Time - a timestamp from the `timeserial` field. It is only present if `tombstone` is `true` - `(OME2c)` `data` `ObjectData` object - the data that represents the value of the map entry. - `(OME3)` The size of the `ObjectsMapEntry` is calculated as follows: @@ -2462,6 +2469,7 @@ Each type, method, and attribute is labelled with the name of one or more clause COUNTER_CREATE // OOP2 COUNTER_INC // OOP2 OBJECT_DELETE // OOP2 + MAP_CLEAR // OOP2 enum ObjectsMapSemantics: // OMP2, internal LWW // OMP2 @@ -2542,6 +2550,7 @@ Each type, method, and attribute is labelled with the name of one or more clause objectDelete: ObjectDelete? // OOP3o mapCreateWithObjectId: MapCreateWithObjectId? // OOP3p counterCreateWithObjectId: CounterCreateWithObjectId? // OOP3q + mapClear: MapClear? // OOP3r class ObjectState // OST*, internal objectId: String // OST2a @@ -2570,6 +2579,8 @@ Each type, method, and attribute is labelled with the name of one or more clause class ObjectDelete // ODE*, internal + class MapClear // MCL*, internal + class MapCreateWithObjectId // MCRO*, internal initialValue: String // MCRO2a nonce: String // MCRO2b @@ -2581,6 +2592,7 @@ Each type, method, and attribute is labelled with the name of one or more clause class ObjectsMap // OMP*, internal semantics: ObjectsMapSemantics // OMP3a entries: Dict? // OMP3b + clearTimeserial: String? // OMP3c class ObjectsCounter // OCN*, internal count: Number? // OCN2a diff --git a/specifications/objects-features.md b/specifications/objects-features.md index fc8a7736..5a1e64db 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -213,7 +213,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTO9a1)` If `ObjectMessage.operation` is null or omitted, log a warning indicating that an unsupported object operation message has been received, and discard the current `ObjectMessage` without taking any action - `(RTO9a3)` If the `appliedOnAckSerials` set ([RTO7b](#RTO7b)) contains `ObjectMessage.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 current `ObjectMessage` without taking any further action - `(RTO9a2)` The `ObjectMessage.operation.action` field (see [`ObjectOperationAction`](../features#OOP2)) determines the type of operation to apply: - - `(RTO9a2a)` If `ObjectMessage.operation.action` is one of the following: `MAP_CREATE`, `MAP_SET`, `MAP_REMOVE`, `COUNTER_CREATE`, `COUNTER_INC`, or `OBJECT_DELETE`, then: + - `(RTO9a2a)` If `ObjectMessage.operation.action` is one of the following: `MAP_CREATE`, `MAP_SET`, `MAP_REMOVE`, `COUNTER_CREATE`, `COUNTER_INC`, `OBJECT_DELETE`, or `MAP_CLEAR`, then: - `(RTO9a2a1)` If it does not already exist, create a zero-value `LiveObject` in the internal `ObjectsPool` per [RTO6](#RTO6) using the `objectId` from `ObjectMessage.operation.objectId` - `(RTO9a2a2)` Get the `LiveObject` instance from the internal `ObjectsPool` using the `objectId` from `ObjectMessage.operation.objectId` - `(RTO9a2a3)` Apply the `ObjectMessage.operation` to the `LiveObject`; see [RTLC7](#RTLC7), [RTLM15](#RTLM15), passing the `source` parameter. The operation returns a boolean indicating whether the operation was successfully applied @@ -346,15 +346,19 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO4e1)` Expects the following arguments: - `(RTLO4e1a)` `ObjectMessage` - `(RTLO4e2)` Set `LiveObject.isTombstone` to `true` - - `(RTLO4e3)` Set `LiveObject.tombstonedAt` as follows: - - `(RTLO4e3a)` Set it equal to `ObjectMessage.serialTimestamp` if it exists - - `(RTLO4e3b)` Otherwise, set it to the current time using the local clock - - `(RTLO4e3b1)` Log a debug or trace message indicating that `serialTimestamp` was not found in the message and the local clock is being used instead for the tombstone timestamp + - `(RTLO4e3)` Set `LiveObject.tombstonedAt` to the value calculated per [RTLO6](#RTLO6), using `ObjectMessage.serialTimestamp` + - `(RTLO4e3a)` This clause has been replaced by [RTLO6a](#RTLO6a) + - `(RTLO4e3b)` This clause has been replaced by [RTLO6b](#RTLO6b) + - `(RTLO4e3b1)` This clause has been replaced by [RTLO6b1](#RTLO6b1) - `(RTLO4e4)` Set the data for the `LiveObject` to a zero-value, as described in [RTLC4](#RTLC4) or [RTLM4](#RTLM4) depending on the object type - `(RTLO5)` An `OBJECT_DELETE` operation can be applied to a `LiveObject` in the following way: - `(RTLO5a)` Expects the following arguments: - `(RTLO5a1)` `ObjectMessage` - `(RTLO5b)` Tombstone the current `LiveObject` using [`LiveObject.tombstone`](#RTLO4e), passing in the `ObjectMessage` +- `(RTLO6)` A `tombstonedAt` value can be calculated from a provided `serialTimestamp` as follows: + - `(RTLO6a)` It is equal to `serialTimestamp` if it exists + - `(RTLO6b)` Otherwise, it is equal to the current time using the local clock + - `(RTLO6b1)` Log a debug or trace message indicating that `serialTimestamp` was not provided and the local clock is being used instead for the tombstone timestamp ### LiveCounter @@ -432,9 +436,6 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLC8b)` If the private flag `createOperationIsMerged` is `true`, log a debug or trace message indicating that the operation will not be applied because a `COUNTER_CREATE` operation has already been applied to this `LiveCounter`. Discard the operation without taking any further action, and return a `LiveCounterUpdate` object with `LiveCounterUpdate.noop` set to `true`, indicating that no update was made to the object - `(RTLC8c)` Otherwise merge the initial value into the `LiveCounter` as described in [RTLC16](#RTLC16), passing in the `ObjectOperation` instance - `(RTLC8e)` Return the `LiveCounterUpdate` object returned by [RTLC16](#RTLC16) - - - - `(RTLC9)` A `COUNTER_INC` operation can be applied to a `LiveCounter` in the following way: - `(RTLC9a)` Expects the following arguments: - `(RTLC9a1)` This clause has been replaced by [RTLC9a2](#RTLC9a2) as of specification version 6.0.0. @@ -469,7 +470,8 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM3)` Holds a `Dict` as a private `data` map - `(RTLM3a)` `ObjectsMapEntry` entries in a `LiveMap` have the following attributes in addition to those defined in [OME2](../features#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 corresponding `ObjectsMapEntry.tombstone` is `true` -- `(RTLM4)` The zero-value `LiveMap` is a `LiveMap` with `data` set to an empty map +- `(RTLM25)` Holds a nullable private `clearTimeserial` string, initially `null` +- `(RTLM4)` The zero-value `LiveMap` is a `LiveMap` with `data` set to an empty map and `clearTimeserial` set to `null` - `(RTLM18)` Data updates for a `LiveMap` are emitted using the `LiveMapUpdate` object: - `(RTLM18a)` `LiveMapUpdate` extends `LiveObjectUpdate` - `(RTLM18b)` `LiveMapUpdate.update` is of type `Dict` - a map of `LiveMap` keys that were either updated or removed, with the corresponding value indicating the type of change for each key @@ -566,11 +568,12 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM6f1)` Return a `LiveMapUpdate` object with `LiveMapUpdate.update` consisting of entries for the keys that were removed as a result of the object being tombstoned, each set to `removed` - `(RTLM6g)` Store the current `data` value as `previousData` for use in [RTLM6h](#RTLM6h) - `(RTLM6b)` Set the private flag `createOperationIsMerged` to `false` + - `(RTLM6i)` Set the private `clearTimeserial` to `ObjectState.map.clearTimeserial`, or to `null` if not provided - `(RTLM6c)` Set `data` to `ObjectState.map.entries`, or to an empty map if it does not exist - - `(RTLM6c1)` For each `ObjectsMapEntry` with `ObjectsMapEntry.tombstone` equal to `true`, additionally set the `ObjectsMapEntry.tombstonedAt` field as follows: - - `(RTLM6c1a)` Set it equal to `ObjectsMapEntry.serialTimestamp` if it exists - - `(RTLM6c1b)` Otherwise, set it to the current time using the local clock - - `(RTLM6c1b1)` Log a debug or trace message indicating that `ObjectsMapEntry.serialTimestamp` was not provided and the local clock is being used instead for the tombstone timestamp + - `(RTLM6c1)` For each `ObjectsMapEntry` with `ObjectsMapEntry.tombstone` equal to `true`, additionally set the `ObjectsMapEntry.tombstonedAt` field to the value calculated per [RTLO6](#RTLO6), using `ObjectsMapEntry.serialTimestamp` + - `(RTLM6c1a)` This clause has been replaced by [RTLO6a](#RTLO6a) + - `(RTLM6c1b)` This clause has been replaced by [RTLO6b](#RTLO6b) + - `(RTLM6c1b1)` This clause has been replaced by [RTLO6b1](#RTLO6b1) - `(RTLM6d)` If `ObjectState.createOp` is present, merge the initial value into the `LiveMap` as described in [RTLM23](#RTLM23), passing in the `ObjectState.createOp` instance. Discard the `LiveMapUpdate` object returned by the merge operation - `(RTLM6d1)` This clause has been replaced by [RTLM17a](#RTLM17a) - `(RTLM6d1a)` This clause has been replaced by [RTLM17a1](#RTLM17a1) @@ -605,6 +608,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM15d5)` If `ObjectMessage.operation.action` is set to `OBJECT_DELETE`, apply the operation as described in [RTLO5](#RTLO5), passing in `ObjectMessage` - `(RTLM15d5a)` Emit a `LiveMapUpdate` object with `LiveMapUpdate.update` consisting of entries for the keys that were removed as a result of applying the `OBJECT_DELETE` operation, each set to `removed` - `(RTLM15d5b)` Return `true` + - `(RTLM15d8)` If `ObjectMessage.operation.action` is set to `MAP_CLEAR`, apply the operation as described in [RTLM24](#RTLM24), passing in `ObjectMessage.serial` + - `(RTLM15d8a)` Emit the `LiveMapUpdate` object returned as a result of applying the operation + - `(RTLM15d8b)` Return `true` - `(RTLM15d4)` Otherwise, log a warning that an object operation message with an unsupported action has been received, and discard the current `ObjectMessage` without taking any further action. No data update event is emitted. Return `false` - `(RTLM16)` A `MAP_CREATE` operation can be applied to a `LiveMap` in the following way: - `(RTLM16a)` Expects the following arguments: @@ -620,6 +626,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM7d3)` `MapSet` - `(RTLM7d2)` `serial` string - operation's serial value - `(RTLM7e)` The return type is a `LiveMapUpdate` object, which indicates the data update for this `LiveMap` + - `(RTLM7h)` If the private `clearTimeserial` is non-null, and the provided `serial` is null or the `clearTimeserial` is lexicographically greater than or equal to `serial`, discard the operation without taking any action. Return a `LiveMapUpdate` object with `LiveMapUpdate.noop` set to `true`, indicating that no update was made to the object - `(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](#RTLM9), discard the operation without taking any action. Return a `LiveMapUpdate` object with `LiveMapUpdate.noop` set to `true`, indicating that no update was made to the object - `(RTLM7a2)` Otherwise, apply the operation to the existing entry: @@ -645,22 +652,34 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM8c2)` `serial` string - operation's serial value - `(RTLM8c3)` `serialTimestamp` Time - operation's serial timestamp value - `(RTLM8d)` The return type is a `LiveMapUpdate` object, which indicates the data update for this `LiveMap` + - `(RTLM8g)` If the private `clearTimeserial` is non-null, and the provided `serial` is null or the `clearTimeserial` is lexicographically greater than or equal to `serial`, discard the operation without taking any action. Return a `LiveMapUpdate` object with `LiveMapUpdate.noop` set to `true`, indicating that no update was made to the object - `(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](#RTLM9), discard the operation without taking any action. Return a `LiveMapUpdate` object with `LiveMapUpdate.noop` set to `true`, indicating that no update was made to the object - `(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` - - `(RTLM8a2d)` Set `ObjectsMapEntry.tombstonedAt` to the value from [RTLM8f](#RTLM8f) + - `(RTLM8a2d)` Set `ObjectsMapEntry.tombstonedAt` to the value calculated per [RTLO6](#RTLO6), using the provided `serialTimestamp` - `(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` - - `(RTLM8b3)` Set `ObjectsMapEntry.tombstonedAt` for the new entry to the value from [RTLM8f](#RTLM8f) - - `(RTLM8f)` The `tombstonedAt` value for the map entry can be calculated in the following way: - - `(RTLM8f1)` It is equal to `serialTimestamp` if it exists - - `(RTLM8f2)` Otherwise, it is equal to the current time using the local clock - - `(RTLM8f2a)` Log a debug or trace message that `serialTimestamp` was not provided for the message and the local clock is used for the tombstone timestamp instead + - `(RTLM8b3)` Set `ObjectsMapEntry.tombstonedAt` for the new entry to the value calculated per [RTLO6](#RTLO6), using the provided `serialTimestamp` + - `(RTLM8f)` This clause has been replaced by [RTLO6](#RTLO6) + - `(RTLM8f1)` This clause has been replaced by [RTLO6a](#RTLO6a) + - `(RTLM8f2)` This clause has been replaced by [RTLO6b](#RTLO6b) + - `(RTLM8f2a)` This clause has been replaced by [RTLO6b1](#RTLO6b1) - `(RTLM8e)` Return a `LiveMapUpdate` object with a `LiveMapUpdate.update` map containing the key used in this operation set to `removed` +- `(RTLM24)` A `MAP_CLEAR` operation can be applied to a `LiveMap` in the following way: + - `(RTLM24a)` Expects the following arguments: + - `(RTLM24a1)` `serial` string - the operation's serial value + - `(RTLM24b)` The return type is a `LiveMapUpdate` object, which indicates the data update for this `LiveMap` + - `(RTLM24c)` If the private `clearTimeserial` is non-null and is lexicographically greater than the provided `serial`, discard the operation without taking any action. Return a `LiveMapUpdate` object with `LiveMapUpdate.noop` set to `true`, indicating that no update was made to the object + - `(RTLM24d)` Set the private `clearTimeserial` to the provided `serial` + - `(RTLM24e)` For each `ObjectsMapEntry` in the internal `data`: + - `(RTLM24e1)` If `ObjectsMapEntry.timeserial` is null or omitted, or the `serial` is lexicographically greater than `ObjectsMapEntry.timeserial`: + - `(RTLM24e1a)` Remove the entry from the internal `data` map. The entry is not retained as a tombstone. + - `(RTLM24e1b)` Record the key for the `LiveMapUpdate` as `removed` + - `(RTLM24f)` Return a `LiveMapUpdate` object with `LiveMapUpdate.update` containing each key recorded in [RTLM24e1b](#RTLM24e1b) set to `removed` - `(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 @@ -748,6 +767,7 @@ Types and their properties/methods are public and exposed to users by default. A update: { amount: Number } // RTLC11b, RTLC11b1 class LiveMap extends LiveObject: // RTLM*, RTLM1 + clearTimeserial: String? // RTLM25, internal get(key: String) -> (Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap)? // RTLM5 size() -> Number // RTLM10 entries() -> [String, (Boolean | Binary | Number | String | JsonArray | JsonObject | LiveCounter | LiveMap)?][] // RTLM11