From 907b4fecac21349effbcaf49151d2e4cdae314c8 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 00:32:52 +0530 Subject: [PATCH 1/9] Specify multi-parent path resolution, tombstone listener cleanup, and Subscription-only deregistration --- specifications/features.md | 1 + specifications/objects-features.md | 51 +++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/specifications/features.md b/specifications/features.md index 9ccb41f0..0282218b 100644 --- a/specifications/features.md +++ b/specifications/features.md @@ -1875,6 +1875,7 @@ The core SDK provides an API for wrapper SDKs to supply Ably with analytics info - `(SUB1)` A `Subscription` represents a registration for receiving events from a subscribe operation - `(SUB2)` The `Subscription` object has the following method: - `(SUB2a)` `unsubscribe` - deregisters the listener that was registered by the corresponding `subscribe` call. Once `unsubscribe` is called, the listener must not be called for any subsequent events + - `(SUB2b)` Calling `unsubscribe` more than once is a no-op ### Option types {#options} diff --git a/specifications/objects-features.md b/specifications/objects-features.md index 289b199b..21f2f880 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -180,6 +180,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTO5c4)` The `SyncObjectsPool` must be cleared - `(RTO5c5)` The `bufferedObjectOperations` list must be cleared - `(RTO5c9)` The `appliedOnAckSerials` set ([RTO7b](#RTO7b)) must be cleared. A state sync causes the channel's LiveObjects data to be replaced, so after a state sync the `appliedOnAckSerials` no longer accurately describes which operations have been applied to the channel's LiveObjects data + - `(RTO5c10)` After re-establishing the `ObjectsPool` per [RTO5c1](#RTO5c1) and [RTO5c2](#RTO5c2), the client MUST rebuild every `parentReferences` map ([RTLO3f](#RTLO3f)). Specifically: + - `(RTO5c10a)` Clear all existing `parentReferences` on every `LiveObject` in the pool + - `(RTO5c10b)` For each `LiveMap` in the pool, iterate its current non-tombstoned entries; for each entry whose `data.objectId` references another `LiveObject` in the pool, add a parent reference on the referenced object with `(this LiveMap, entryKey)` per [RTLO3f](#RTLO3f) - `(RTO5c8)` The [RTO17](#RTO17) sync state must transition to `SYNCED` - `(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 @@ -291,7 +294,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTO24)` Internal `PathObjectSubscriptionRegister` - manages path-based subscriptions for `PathObject#subscribe` ([RTPO19](#RTPO19)) - `(RTO24a)` The `RealtimeObject` instance maintains a single `PathObjectSubscriptionRegister` that manages all path-based subscriptions for the channel - `(RTO24b)` When a `LiveObject` in the `ObjectsPool` emits a `LiveObjectUpdate` (per [RTLO4b4](#RTLO4b4)), the `PathObjectSubscriptionRegister` must determine which subscriptions should be notified: - - `(RTO24b1)` Determine the paths in the LiveObjects tree at which the updated `LiveObject` is located + - `(RTO24b1)` Determine the paths in the LiveObjects tree at which the updated `LiveObject` is located by calling [`LiveObject#getFullPaths`](#RTLO3g) - `(RTO24b2)` For each registered subscription, check whether the event path starts with (or equals) the subscription's path - `(RTO24b3)` If the event path matches, apply depth filtering: the event is dispatched to the subscription if the number of path segments from the subscription path to the event path plus 1 does not exceed the subscription's `depth` option (or if `depth` is undefined). Formally, the event is dispatched if `eventPath.length - subscriptionPath.length + 1 <= depth` - `(RTO24b4)` Create a `PathObjectSubscriptionEvent` whose `object` is a `PathObject` pointing to the event path and whose `message` is a `PublicAPI::ObjectMessage` derived from the source `ObjectMessage` per [PAOM3](#PAOM3), and call the subscription's listener @@ -312,6 +315,16 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO3d1)` Set to `false` when the `LiveObject` is initialized - `(RTLO3e)` protected `tombstonedAt` (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 if `isTombstone` is `true` - `(RTLO3e1)` Set to undefined/null when the `LiveObject` is initialized + - `(RTLO3f)` protected `parentReferences` - a mapping from each parent `LiveMap` to the set of keys at which that `LiveMap` currently references this `LiveObject`. The map MUST be maintained as map operations are applied so that path-based subscribers ([RTO24](#RTO24)) can determine every path the object currently occupies in the LiveObjects tree + - `(RTLO3f1)` Set to an empty map when the `LiveObject` is initialized + - `(RTLO3f2)` When a `MAP_SET` operation adds an entry whose `ObjectData.objectId` references another `LiveObject` in the local `ObjectsPool`, the referenced object's `parentReferences` MUST be updated to include the entry `(parent LiveMap, key)` + - `(RTLO3f3)` When a `MAP_SET` operation replaces an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed before the new reference is added + - `(RTLO3f4)` When a `MAP_REMOVE` operation tombstones an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed + - `(RTLO3f5)` When a `MAP_CLEAR` operation removes an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed + - `(RTLO3f6)` When a `LiveMap` is tombstoned per [RTLO4e](#RTLO4e), all references it holds to other `LiveObjects` MUST be removed from those objects' `parentReferences` maps + - `(RTLO3g)` internal `getFullPaths` function - returns the set of distinct paths from the root `LiveMap` (objectId `root`) to this `LiveObject`, computed by traversing `parentReferences` upward + - `(RTLO3g1)` Implementations MUST guard against cycles using a visited set so that traversal of a cyclic reference graph terminates and returns a bounded number of paths + - `(RTLO3g2)` If the object has no parent references at the time of the call (e.g. orphaned or not yet reachable from root), the result MUST be an empty list and no path events are emitted for this `LiveObject` - `(RTLO4)` `LiveObject` methods: - `(RTLO4b)` `subscribe` - subscribes a user to data updates on this `LiveObject` instance - `(RTLO4b1)` Requires the `OBJECT_SUBSCRIBE` channel mode to be granted per [RTO2](#RTO2) @@ -328,11 +341,17 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO4b5b)` This clause has been replaced by [RTLO4b7](#RTLO4b7) - `(RTLO4b7)` Returns a [`Subscription`](../features#SUB1) object - `(RTLO4b6)` This operation must not have any side effects on `RealtimeObject`, the underlying channel, or their status - - `(RTLO4c)` `unsubscribe` - unsubscribes a previously registered listener - - `(RTLO4c1)` This operation does not require any specific channel modes to be granted, nor does it require the channel to be in a specific state - - `(RTLO4c2)` A user may provide a listener they wish to deregister from receiving data updates for this `LiveObject` - - `(RTLO4c3)` Once deregistered, subsequent data updates for this `LiveObject` must not result in the listener being called - - `(RTLO4c4)` This operation must not have any side effects on `RealtimeObject`, the underlying channel, or their status + - `(RTLO4b8)` When a `LiveObjectUpdate` is emitted as a result of a tombstone - i.e. an `OBJECT_DELETE` operation or a sync state with `tombstone: true` - the listener invocation order MUST be: + - `(RTLO4b8a)` Invoke all registered listeners with the tombstone `LiveObjectUpdate` first, per [RTLO4b4c2](#RTLO4b4c2) + - `(RTLO4b8b)` After all listeners have been invoked, the library MUST deregister every listener currently registered on this `LiveObject` (equivalent to removing all `Subscription` objects returned by prior calls to [RTLO4b](#RTLO4b)) + - `(RTLO4b8c)` Subsequent updates on this `LiveObject` MUST NOT invoke the deregistered listeners + - `(RTLO4b9)` Path-based subscriptions ([RTPO19](#RTPO19)) are NOT affected by [RTLO4b8](#RTLO4b8): they remain registered after the underlying object is tombstoned, because path subscriptions follow a path, not an object identity. A subsequent `MAP_SET` on the parent that creates a new object at the same path will deliver further events to the existing path subscription + - `(RTLO4b10)` If a registered listener throws when invoked with a `LiveObjectUpdate` per [RTLO4b4c2](#RTLO4b4c2), the error MUST be caught and logged. The error MUST NOT affect the dispatch to other listeners registered on the same `LiveObject`, nor abort the iteration over the listener list + - `(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)` protected `canApplyOperation` - a convenience method used to determine whether the `ObjectMessage.operation` should be applied to this object based on a serial value - `(RTLO4a1)` Expects the following arguments: - `(RTLO4a1a)` `ObjectMessage` @@ -907,9 +926,9 @@ A `PathObject` is obtained from `RealtimeObject#get` ([RTO23](#RTO23)), which re - `(RTPO19e)` The subscription is path-based: it follows the path, not a specific object. If the object at the path changes identity (e.g. via a `MAP_SET` operation replacing it), the subscription continues to deliver events for the new object at that path - `(RTPO19f)` Events at child paths bubble up to the subscription, subject to depth filtering. For example, a subscription at path `a.b` receives events for changes at `a.b`, `a.b.c`, `a.b.c.d`, etc., depending on the configured depth. The dispatch rules are described in [RTO24b](#RTO24b) - `(RTPO19g)` This operation must not have any side effects on `RealtimeObject`, the underlying channel, or their status -- `(RTPO20)` `PathObject#unsubscribe` function: - - `(RTPO20a)` Accepts a `listener` argument and deregisters it from receiving further events for this `PathObject`'s path - - `(RTPO20b)` This operation must not have any side effects on `RealtimeObject`, the underlying channel, or their status +- `(RTPO20)` This clause has been deleted + - `(RTPO20a)` This clause has been deleted + - `(RTPO20b)` This clause has been deleted ### Instance @@ -979,9 +998,9 @@ An `Instance` holds a direct reference to a specific resolved `LiveObject` or pr - `(RTINS16e)` Returns a [`Subscription`](../features#SUB1) object - `(RTINS16f)` The subscription is identity-based: it follows the specific `LiveObject` instance, regardless of where it sits in the tree - `(RTINS16g)` This operation must not have any side effects on `RealtimeObject`, the underlying channel, or their status -- `(RTINS17)` `Instance#unsubscribe` function: - - `(RTINS17a)` Accepts a `listener` argument and deregisters it from receiving further events using `LiveObject#unsubscribe` ([RTLO4c](#RTLO4c)) - - `(RTINS17b)` This operation must not have any side effects on `RealtimeObject`, the underlying channel, or their status +- `(RTINS17)` This clause has been deleted + - `(RTINS17a)` This clause has been deleted + - `(RTINS17b)` This clause has been deleted ### PublicAPI::ObjectMessage @@ -1031,6 +1050,9 @@ An `Instance` holds a direct reference to a specific resolved `LiveObject` or pr 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. + // Primitive is an alias used throughout the IDL + type Primitive = Boolean | Binary | Number | String | JsonArray | JsonObject // internal + class RealtimeObject: // RTO* get() => io PathObject // RTO23 on(ObjectsEvent event, (() ->) callback) -> StatusSubscription // RTO18 @@ -1063,7 +1085,6 @@ Types and their properties/methods are public and exposed to users by default. A canApplyOperation(ObjectMessage) -> Boolean // RTLO4a tombstone(ObjectMessage) // RTLO4e subscribe((LiveObjectUpdate) ->) -> Subscription // RTLO4b - unsubscribe((LiveObjectUpdate) ->) // RTLO4c interface LiveObjectUpdate: // RTLO4b4, internal update: Object // RTLO4b4a @@ -1091,9 +1112,11 @@ Types and their properties/methods are public and exposed to users by default. A update: Dict // RTLM18b class LiveCounterValueType: // RTLCV* + count: Number // RTLCV2a, internal static create(Number initialCount?) -> LiveCounterValueType // RTLCV3 class LiveMapValueType: // RTLMV* + entries: Dict? // RTLMV2a, internal static create(Dict entries?) -> LiveMapValueType // RTLMV3 interface PathObjectSubscriptionEvent: // RTPO19d @@ -1147,7 +1170,6 @@ Types and their properties/methods are public and exposed to users by default. A increment(Number amount?) => io // RTPO17 decrement(Number amount?) => io // RTPO18 subscribe((PathObjectSubscriptionEvent) -> listener, PathObjectSubscriptionOptions? options) -> Subscription // RTPO19 - unsubscribe((PathObjectSubscriptionEvent) -> listener) // RTPO20 class Instance: // RTINS* id: String? // RTINS3 @@ -1164,4 +1186,3 @@ Types and their properties/methods are public and exposed to users by default. A increment(Number amount?) => io // RTINS14 decrement(Number amount?) => io // RTINS15 subscribe((InstanceSubscriptionEvent) -> listener) -> Subscription // RTINS16 - unsubscribe((InstanceSubscriptionEvent) -> listener) // RTINS17 From ecf85df527c483282b75003d33e986892c95bb6a Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 17:30:30 +0530 Subject: [PATCH 2/9] Specified getFullPaths traversal semantics for LiveObject path enumeration --- specifications/objects-features.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index 12b015f2..a3626375 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -324,9 +324,14 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO3f4)` When a `MAP_REMOVE` operation tombstones an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed - `(RTLO3f5)` When a `MAP_CLEAR` operation removes an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed - `(RTLO3f6)` When a `LiveMap` is tombstoned per [RTLO4e](#RTLO4e), all references it holds to other `LiveObjects` MUST be removed from those objects' `parentReferences` maps - - `(RTLO3g)` internal `getFullPaths` function - returns the set of distinct paths from the root `LiveMap` (objectId `root`) to this `LiveObject`, computed by traversing `parentReferences` upward - - `(RTLO3g1)` Implementations MUST guard against cycles using a visited set so that traversal of a cyclic reference graph terminates and returns a bounded number of paths - - `(RTLO3g2)` If the object has no parent references at the time of the call (e.g. orphaned or not yet reachable from root), the result MUST be an empty list and no path events are emitted for this `LiveObject` + - `(RTLO3g)` internal `getFullPaths` function - returns the list of distinct paths from the root `LiveMap` (objectId `root`) to this `LiveObject`, computed by traversing `parentReferences` upward. Each returned path is an ordered sequence of keys from `root` to this `LiveObject`. + - `(RTLO3g1)` `getFullPaths` MUST be implemented as an enumeration of all *simple paths* from this `LiveObject` to the root `LiveMap` over the inverse of the `parentReferences` graph (i.e. walking child → parent). A *simple path* is a path along which no `LiveObject` appears more than once. This is the standard graph problem, typically solved by a depth-first traversal with path-local backtracking equivalent to NetworkX's `all_simple_paths`. Implementation should choose iterative DFS with explicit stack (easier to read and debugging). + - `(RTLO3g2)` If this `LiveObject` is the root `LiveMap` (objectId `root`), the returned list MUST contain exactly one path, and that path MUST be empty (zero key segments). This makes the root reachable from itself via the empty key sequence + - `(RTLO3g3)` If this `LiveObject` is not the root `LiveMap` and has no entries in its `parentReferences` at the time of the call (e.g. orphaned, or not yet reachable from root), the returned list MUST be empty + - `(RTLO3g4)` While traversing paths, suppress cyclic paths whenever a sibling branch had already revisited the same node. Reference behaviour on cyclic graphs is given by NetworkX's `all_simple_paths`, which implementations MAY consult for worked examples + - `(RTLO3g5)` When a single parent `LiveMap` references this `LiveObject` at multiple keys, the returned list MUST contain one distinct path per such key, each ending at the corresponding key + - `(RTLO3g6)` When this `LiveObject` is reachable via multiple distinct ancestor paths (either because it has multiple parents in `parentReferences`, or because any ancestor on the way to root itself has multiple paths to root), the returned list MUST contain one path per distinct ancestor path + - `(RTLO3g7)` The order of paths in the returned list is not mandatory. Implementations MAY return paths in any order; callers requiring a stable order MUST sort the result themselves - `(RTLO4)` `LiveObject` methods: - `(RTLO4b)` `subscribe` - subscribes a user to data updates on this `LiveObject` instance - `(RTLO4b1)` Requires the `OBJECT_SUBSCRIBE` channel mode to be granted per [RTO2](#RTO2) From 48046e261aa8c984e59e3251a7499bc1a3d7154e Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 17:45:06 +0530 Subject: [PATCH 3/9] Simplified `tombstone` update spec, added explicit `RTLO4b4c3c` spec point, removed unnecessary `RTLO4b8`, `RTLO4b9` and `RTLO4b10` --- specifications/objects-features.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index a3626375..d668a772 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -347,17 +347,12 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO4b4c3)` Otherwise: - `(RTLO4b4c3a)` The registered listener of each subscription created via `LiveObject#subscribe` ([RTLO4b](#RTLO4b)) on this `LiveObject` is called with the `LiveObjectUpdate` - `(RTLO4b4c3b)` Perform path-based subscription dispatch as described in [RTO24b](#RTO24b), passing this `LiveObject` and the `LiveObjectUpdate` + - `(RTLO4b4c3c)` When a `LiveObjectUpdate` is emitted as a result of a tombstone - i.e. an `OBJECT_DELETE` operation or a sync state with `tombstone: true`, after all listeners have been invoked from [RTLO4b4c3a](#RTLO4b4c3a) and [RTLO4b4c3b](#RTLO4b4c3b), the library MUST deregister all listeners on this `LiveObject`. Path-based subscriptions ([RTPO19](#RTPO19)) are NOT affected by `tombstone` update. - `(RTLO4b5)` This clause has been replaced by [RTLO4b7](#RTLO4b7) - `(RTLO4b5a)` This clause has been replaced by [RTLO4b7](#RTLO4b7) - `(RTLO4b5b)` This clause has been replaced by [RTLO4b7](#RTLO4b7) - `(RTLO4b7)` Returns a [`Subscription`](../features#SUB1) object - `(RTLO4b6)` This operation must not have any side effects on `RealtimeObject`, the underlying channel, or their status - - `(RTLO4b8)` When a `LiveObjectUpdate` is emitted as a result of a tombstone - i.e. an `OBJECT_DELETE` operation or a sync state with `tombstone: true` - the listener invocation order MUST be: - - `(RTLO4b8a)` Invoke all registered listeners with the tombstone `LiveObjectUpdate` first, per [RTLO4b4c2](#RTLO4b4c2) - - `(RTLO4b8b)` After all listeners have been invoked, the library MUST deregister every listener currently registered on this `LiveObject` (equivalent to removing all `Subscription` objects returned by prior calls to [RTLO4b](#RTLO4b)) - - `(RTLO4b8c)` Subsequent updates on this `LiveObject` MUST NOT invoke the deregistered listeners - - `(RTLO4b9)` Path-based subscriptions ([RTPO19](#RTPO19)) are NOT affected by [RTLO4b8](#RTLO4b8): they remain registered after the underlying object is tombstoned, because path subscriptions follow a path, not an object identity. A subsequent `MAP_SET` on the parent that creates a new object at the same path will deliver further events to the existing path subscription - - `(RTLO4b10)` If a registered listener throws when invoked with a `LiveObjectUpdate` per [RTLO4b4c2](#RTLO4b4c2), the error MUST be caught and logged. The error MUST NOT affect the dispatch to other listeners registered on the same `LiveObject`, nor abort the iteration over the listener list - `(RTLO4c)` This clause has been deleted - `(RTLO4c1)` This clause has been deleted - `(RTLO4c2)` This clause has been deleted From 638c85a3ccc9ee5bb5fa7b0e2de705eeeed5f4eb Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 21:03:30 +0530 Subject: [PATCH 4/9] Reviwed and updated spec for parentReferences with respect to ably-js code --- specifications/objects-features.md | 36 ++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index d668a772..74039b37 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -174,15 +174,16 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTO5c1b1c)` This clause has been deleted (redundant to [RTO5f3](#RTO5f3)). - `(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](#RTO3b) + - `(RTO5c10)` After re-establishing the `ObjectsPool` per [RTO5c1](#RTO5c1) and [RTO5c2](#RTO5c2), the client MUST rebuild every `parentReferences` map ([RTLO3f](#RTLO3f)). Specifically: + - `(RTO5c10a)` For each `LiveObject` in the internal `ObjectsPool` ([RTO3](#RTO3)), reset its `parentReferences` to an empty map as defined in [RTLO3f1](#RTLO3f1) + - `(RTO5c10b)` After [RTO5c10a](#RTO5c10a) has completed, for each `LiveMap` in the internal `ObjectsPool`, iterate its `LiveMap#entries` as per [RTLM11](#RTLM11) + - `(RTO5c10b1)` For each iterated entry whose value type is `LiveObject`, call `addParentReference(parent, key)` on the `LiveObject` (per [RTLO3f2](#RTLO3f2)), passing the iterated `LiveMap` as `parent` and the iterated entry key as `key` - `(RTO5c7)` For each previously existing object that was updated as a result of [RTO5c1a](#RTO5c1a), emit the corresponding stored `LiveObjectUpdate` object from [RTO5c1a2](#RTO5c1a2) - `(RTO5c6)` `ObjectMessages` stored in the `bufferedObjectOperations` list are applied as described in [RTO9](#RTO9), passing `source` as `CHANNEL` - `(RTO5c3)` Clear any stored sync sequence identifiers and cursor values - `(RTO5c4)` The `SyncObjectsPool` must be cleared - `(RTO5c5)` The `bufferedObjectOperations` list must be cleared - `(RTO5c9)` The `appliedOnAckSerials` set ([RTO7b](#RTO7b)) must be cleared. A state sync causes the channel's LiveObjects data to be replaced, so after a state sync the `appliedOnAckSerials` no longer accurately describes which operations have been applied to the channel's LiveObjects data - - `(RTO5c10)` After re-establishing the `ObjectsPool` per [RTO5c1](#RTO5c1) and [RTO5c2](#RTO5c2), the client MUST rebuild every `parentReferences` map ([RTLO3f](#RTLO3f)). Specifically: - - `(RTO5c10a)` Clear all existing `parentReferences` on every `LiveObject` in the pool - - `(RTO5c10b)` For each `LiveMap` in the pool, iterate its current non-tombstoned entries; for each entry whose `data.objectId` references another `LiveObject` in the pool, add a parent reference on the referenced object with `(this LiveMap, entryKey)` per [RTLO3f](#RTLO3f) - `(RTO5c8)` The [RTO17](#RTO17) sync state must transition to `SYNCED` - `(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 @@ -317,13 +318,15 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO3d1)` Set to `false` when the `LiveObject` is initialized - `(RTLO3e)` protected `tombstonedAt` (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 if `isTombstone` is `true` - `(RTLO3e1)` Set to undefined/null when the `LiveObject` is initialized - - `(RTLO3f)` protected `parentReferences` - a mapping from each parent `LiveMap` to the set of keys at which that `LiveMap` currently references this `LiveObject`. The map MUST be maintained as map operations are applied so that path-based subscribers ([RTO24](#RTO24)) can determine every path the object currently occupies in the LiveObjects tree + - `(RTLO3f)` protected `parentReferences` Map - contains mapping from each parent `LiveMap` to the set of keys at which that `LiveMap` currently references this `LiveObject`. This map should be updated as `LiveMap` operations are applied so that path-based subscribers ([RTO24](#RTO24)) can determine every path the object currently occupies in the LiveObjects tree - `(RTLO3f1)` Set to an empty map when the `LiveObject` is initialized - - `(RTLO3f2)` When a `MAP_SET` operation adds an entry whose `ObjectData.objectId` references another `LiveObject` in the local `ObjectsPool`, the referenced object's `parentReferences` MUST be updated to include the entry `(parent LiveMap, key)` - - `(RTLO3f3)` When a `MAP_SET` operation replaces an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed before the new reference is added - - `(RTLO3f4)` When a `MAP_REMOVE` operation tombstones an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed - - `(RTLO3f5)` When a `MAP_CLEAR` operation removes an entry that previously referenced an object, the previously-referenced object's `parentReferences` entry for `(parent LiveMap, key)` MUST be removed - - `(RTLO3f6)` When a `LiveMap` is tombstoned per [RTLO4e](#RTLO4e), all references it holds to other `LiveObjects` MUST be removed from those objects' `parentReferences` maps + - `(RTLO3f2)` internal `addParentReference(parent, key)` method - records that the `LiveMap` `parent` references this `LiveObject` at `key` + - `(RTLO3f2a)` If `parent` is already present in `parentReferences`, `key` MUST be added to the existing set associated with `parent` + - `(RTLO3f2b)` Otherwise, a new entry MUST be inserted into `parentReferences` for `parent` with a set containing only `key` + - `(RTLO3f3)` internal `removeParentReference(parent, key)` method - removes the recorded reference from `parent` at `key` + - `(RTLO3f3a)` If `parent` is not present in `parentReferences`, the call MUST be a no-op + - `(RTLO3f3b)` Otherwise, `key` MUST be removed from the set associated with `parent` + - `(RTLO3f3c)` If, as a result of [RTLO3f3b](#RTLO3f3b), the set associated with `parent` is empty, the `parent` entry MUST be removed from `parentReferences` - `(RTLO3g)` internal `getFullPaths` function - returns the list of distinct paths from the root `LiveMap` (objectId `root`) to this `LiveObject`, computed by traversing `parentReferences` upward. Each returned path is an ordered sequence of keys from `root` to this `LiveObject`. - `(RTLO3g1)` `getFullPaths` MUST be implemented as an enumeration of all *simple paths* from this `LiveObject` to the root `LiveMap` over the inverse of the `parentReferences` graph (i.e. walking child → parent). A *simple path* is a path along which no `LiveObject` appears more than once. This is the standard graph problem, typically solved by a depth-first traversal with path-local backtracking equivalent to NetworkX's `all_simple_paths`. Implementation should choose iterative DFS with explicit stack (easier to read and debugging). - `(RTLO3g2)` If this `LiveObject` is the root `LiveMap` (objectId `root`), the returned list MUST contain exactly one path, and that path MUST be empty (zero key segments). This makes the root reachable from itself via the empty key sequence @@ -374,6 +377,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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) + - `(RTLO4e5)` If this `LiveObject` is a `LiveMap`, before [RTLO4e4](#RTLO4e4) is applied, for each `ObjectsMapEntry` in this `LiveMap`'s current `data`: + - `(RTLO4e5a)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required for that entry + - `(RTLO4e5b)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the iterated entry's key as `key` - `(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: @@ -670,6 +676,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM7a2b)` Set `ObjectsMapEntry.timeserial` to the provided `serial` - `(RTLM7a2c)` Set `ObjectsMapEntry.tombstone` to `false` - `(RTLM7a2d)` Set `ObjectsMapEntry.tombstonedAt` to undefined/null + - `(RTLM7a2f)` Before [RTLM7a2e](#RTLM7a2e) is applied, the parent reference recorded on the previously-referenced `LiveObject` (if any) MUST be removed: + - `(RTLM7a2f1)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required + - `(RTLM7a2f2)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM7b)` If an entry does not exist in the private `data` for the specified key: - `(RTLM7b1)` This clause has been replaced by [RTLM7b4](#RTLM7b4) as of specification version 6.0.0. - `(RTLM7b4)` Create a new `ObjectsMapEntry` in `data` for the specified key, with `ObjectsMapEntry.data` set to `MapSet.value` and `ObjectsMapEntry.timeserial` set to `serial` @@ -679,6 +688,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM7c1)` This clause has been replaced by [RTLM7g1](#RTLM7g1) as of specification version 6.0.0. - `(RTLM7g)` If `MapSet.value.objectId` is non-empty: - `(RTLM7g1)` Create a zero-value `LiveObject` for this `objectId` in the internal `ObjectsPool` per [RTO6](#RTO6) + - `(RTLM7i)` A parent reference MUST be recorded on the `LiveObject` newly referenced by this entry (if any): + - `(RTLM7i1)` If `MapSet.value.objectId` is not present, no action is required + - `(RTLM7i2)` Otherwise, call `addParentReference(parent, key)` on the `LiveObject` in the local `ObjectsPool` with `objectId` equal to `MapSet.value.objectId` (guaranteed to exist per [RTLM7g](#RTLM7g)) per [RTLO3f2](#RTLO3f2), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM7f)` Return a `LiveMapUpdate` object with a `LiveMapUpdate.update` map containing the key used in this operation set to `updated`, and `LiveMapUpdate.objectMessage` set to the provided `ObjectMessage` - `(RTLM8)` A `MAP_REMOVE` operation for a key can be applied to a `LiveMap` in the following way: - `(RTLM8c)` Expects the following arguments: @@ -696,6 +708,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM8a2b)` Set `ObjectsMapEntry.timeserial` to the provided `serial` - `(RTLM8a2c)` Set `ObjectsMapEntry.tombstone` to `true` - `(RTLM8a2d)` Set `ObjectsMapEntry.tombstonedAt` to the value calculated per [RTLO6](#RTLO6), using the provided `serialTimestamp` + - `(RTLM8a2e)` Before [RTLM8a2a](#RTLM8a2a) is applied, the parent reference recorded on the previously-referenced `LiveObject` (if any) MUST be removed: + - `(RTLM8a2e1)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required + - `(RTLM8a2e2)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` - `(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` @@ -714,6 +729,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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`: + - `(RTLM24e1c)` Before [RTLM24e1a](#RTLM24e1a) is applied, the parent reference recorded on the `LiveObject` referenced by this entry (if any) MUST be removed: + - `(RTLM24e1c1)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required + - `(RTLM24e1c2)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the iterated entry key as `key` - `(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`, and `LiveMapUpdate.objectMessage` set to the provided `ObjectMessage` From 479e4f18176bb208799e001acaafcbc6a61626d2 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 22:16:41 +0530 Subject: [PATCH 5/9] Updated RTLO4e5 spec used while tombstoning the object --- specifications/objects-features.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index 74039b37..795d9a73 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -377,9 +377,10 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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) - - `(RTLO4e5)` If this `LiveObject` is a `LiveMap`, before [RTLO4e4](#RTLO4e4) is applied, for each `ObjectsMapEntry` in this `LiveMap`'s current `data`: - - `(RTLO4e5a)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required for that entry - - `(RTLO4e5b)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the iterated entry's key as `key` + - `(RTLO4e5)` If the current `LiveObject` is of type `LiveMap`, then before [RTLO4e4](#RTLO4e4) is applied, do following: + - `(RTLO4e5a)` For each iterated `entry` in current `LiveMap`'s internal `data`: + - `(RTLO4e5a1)` If `entry.value.data` have `objectId` as a field, retrieve corresponding child `LiveObject` from `ObjectsPool` using given `objectId` + - `(RTLO4e5a2)` If child `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing current `LiveMap` as `parent` and the iterated `entry.value` as `key` - `(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: From 67ad1d28b673cd45710f47a05a56c791d9852d35 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 23:16:58 +0530 Subject: [PATCH 6/9] Refactored specifications for handling parent references during MAP_SET op --- specifications/objects-features.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index 795d9a73..bcda2189 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -378,7 +378,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO4e3b)` This clause has been replaced by [RTLO6b](#RTLO6b) - `(RTLO4e3b1)` This clause has been replaced by [RTLO6b1](#RTLO6b1) - `(RTLO4e5)` If the current `LiveObject` is of type `LiveMap`, then before [RTLO4e4](#RTLO4e4) is applied, do following: - - `(RTLO4e5a)` For each iterated `entry` in current `LiveMap`'s internal `data`: + - `(RTLO4e5a)` For each iterated `entry` in current `LiveMap`'s private `data`: - `(RTLO4e5a1)` If `entry.value.data` have `objectId` as a field, retrieve corresponding child `LiveObject` from `ObjectsPool` using given `objectId` - `(RTLO4e5a2)` If child `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing current `LiveMap` as `parent` and the iterated `entry.value` as `key` - `(RTLO4e4)` Set the data for the `LiveObject` to a zero-value, as described in [RTLC4](#RTLC4) or [RTLM4](#RTLM4) depending on the object type @@ -671,15 +671,15 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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 + - `(RTLM7a3)` If the current `ObjectsMapEntry` is of type `LiveObject`, before [RTLM7a2](#RTLM7a2e) is applied, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed: + - `(RTLM7a3a)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId` + - `(RTLM7a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM7a2)` Otherwise, apply the operation to the existing entry: - `(RTLM7a2a)` This clause has been replaced by [RTLM7a2e](#RTLM7a2e) as of specification version 6.0.0. - `(RTLM7a2e)` Set `ObjectsMapEntry.data` to the `MapSet.value` - `(RTLM7a2b)` Set `ObjectsMapEntry.timeserial` to the provided `serial` - `(RTLM7a2c)` Set `ObjectsMapEntry.tombstone` to `false` - `(RTLM7a2d)` Set `ObjectsMapEntry.tombstonedAt` to undefined/null - - `(RTLM7a2f)` Before [RTLM7a2e](#RTLM7a2e) is applied, the parent reference recorded on the previously-referenced `LiveObject` (if any) MUST be removed: - - `(RTLM7a2f1)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required - - `(RTLM7a2f2)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM7b)` If an entry does not exist in the private `data` for the specified key: - `(RTLM7b1)` This clause has been replaced by [RTLM7b4](#RTLM7b4) as of specification version 6.0.0. - `(RTLM7b4)` Create a new `ObjectsMapEntry` in `data` for the specified key, with `ObjectsMapEntry.data` set to `MapSet.value` and `ObjectsMapEntry.timeserial` set to `serial` @@ -691,7 +691,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM7g1)` Create a zero-value `LiveObject` for this `objectId` in the internal `ObjectsPool` per [RTO6](#RTO6) - `(RTLM7i)` A parent reference MUST be recorded on the `LiveObject` newly referenced by this entry (if any): - `(RTLM7i1)` If `MapSet.value.objectId` is not present, no action is required - - `(RTLM7i2)` Otherwise, call `addParentReference(parent, key)` on the `LiveObject` in the local `ObjectsPool` with `objectId` equal to `MapSet.value.objectId` (guaranteed to exist per [RTLM7g](#RTLM7g)) per [RTLO3f2](#RTLO3f2), passing this `LiveMap` as `parent` and the operation's key as `key` + - `(RTLM7i2)` Otherwise, call `addParentReference(parent, key)` per [RTLO3f2](#RTLO3f2) on the `LiveObject` in the local `ObjectsPool` with `objectId` equal to `MapSet.value.objectId` (guaranteed to exist per [RTLM7g](#RTLM7g)), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM7f)` Return a `LiveMapUpdate` object with a `LiveMapUpdate.update` map containing the key used in this operation set to `updated`, and `LiveMapUpdate.objectMessage` set to the provided `ObjectMessage` - `(RTLM8)` A `MAP_REMOVE` operation for a key can be applied to a `LiveMap` in the following way: - `(RTLM8c)` Expects the following arguments: From 3abc3b772ebfd50a433544ebd50922fb7e06c8f3 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 23:26:17 +0530 Subject: [PATCH 7/9] Refactored specifications for handling parent references during MAP_REMOVE op --- specifications/objects-features.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index bcda2189..93756b4a 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -704,14 +704,14 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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 + - `(RTLM8a3)` If the current `ObjectsMapEntry` is of type `LiveObject`, before [RTLM8a2](#RTLM8a2) is applied, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed: + - `(RTLM8a3a)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId` + - `(RTLM8a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` - `(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 calculated per [RTLO6](#RTLO6), using the provided `serialTimestamp` - - `(RTLM8a2e)` Before [RTLM8a2a](#RTLM8a2a) is applied, the parent reference recorded on the previously-referenced `LiveObject` (if any) MUST be removed: - - `(RTLM8a2e1)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required - - `(RTLM8a2e2)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` - `(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` From 68e88d67507bdd49cfd45073d8465c34e3abd6dd Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 23:37:28 +0530 Subject: [PATCH 8/9] Refactored specifications for handling parent references during MAP_CLEAR op --- specifications/objects-features.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index 93756b4a..c9f38e16 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -730,9 +730,9 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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`: - - `(RTLM24e1c)` Before [RTLM24e1a](#RTLM24e1a) is applied, the parent reference recorded on the `LiveObject` referenced by this entry (if any) MUST be removed: - - `(RTLM24e1c1)` If `ObjectsMapEntry.data` is not an `ObjectIdObjectData`, no action is required - - `(RTLM24e1c2)` Otherwise, if a `LiveObject` with `objectId` equal to `ObjectsMapEntry.data.objectId` exists in the local `ObjectsPool`, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the iterated entry key as `key` + - `(RTLM24e1c)` If the current `ObjectsMapEntry` is of type `LiveObject`, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed: + - `(RTLM24e1c1)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId` + - `(RTLM24e1c2)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the iterated entry key as `key` - `(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`, and `LiveMapUpdate.objectMessage` set to the provided `ObjectMessage` From 1d96f530716c7bfe95856955a30059cda221eea6 Mon Sep 17 00:00:00 2001 From: sacOO7 Date: Fri, 22 May 2026 23:55:17 +0530 Subject: [PATCH 9/9] Moved addParentReference and removeParentReference to RTLO4* spec, updated IDL and references accordingly --- specifications/objects-features.md | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/specifications/objects-features.md b/specifications/objects-features.md index c9f38e16..f1cd9b2d 100644 --- a/specifications/objects-features.md +++ b/specifications/objects-features.md @@ -177,7 +177,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTO5c10)` After re-establishing the `ObjectsPool` per [RTO5c1](#RTO5c1) and [RTO5c2](#RTO5c2), the client MUST rebuild every `parentReferences` map ([RTLO3f](#RTLO3f)). Specifically: - `(RTO5c10a)` For each `LiveObject` in the internal `ObjectsPool` ([RTO3](#RTO3)), reset its `parentReferences` to an empty map as defined in [RTLO3f1](#RTLO3f1) - `(RTO5c10b)` After [RTO5c10a](#RTO5c10a) has completed, for each `LiveMap` in the internal `ObjectsPool`, iterate its `LiveMap#entries` as per [RTLM11](#RTLM11) - - `(RTO5c10b1)` For each iterated entry whose value type is `LiveObject`, call `addParentReference(parent, key)` on the `LiveObject` (per [RTLO3f2](#RTLO3f2)), passing the iterated `LiveMap` as `parent` and the iterated entry key as `key` + - `(RTO5c10b1)` For each iterated entry whose value type is `LiveObject`, call `addParentReference(parent, key)` on the `LiveObject` (per [RTLO4f](#RTLO4f)), passing the iterated `LiveMap` as `parent` and the iterated entry key as `key` - `(RTO5c7)` For each previously existing object that was updated as a result of [RTO5c1a](#RTO5c1a), emit the corresponding stored `LiveObjectUpdate` object from [RTO5c1a2](#RTO5c1a2) - `(RTO5c6)` `ObjectMessages` stored in the `bufferedObjectOperations` list are applied as described in [RTO9](#RTO9), passing `source` as `CHANNEL` - `(RTO5c3)` Clear any stored sync sequence identifiers and cursor values @@ -320,13 +320,6 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO3e1)` Set to undefined/null when the `LiveObject` is initialized - `(RTLO3f)` protected `parentReferences` Map - contains mapping from each parent `LiveMap` to the set of keys at which that `LiveMap` currently references this `LiveObject`. This map should be updated as `LiveMap` operations are applied so that path-based subscribers ([RTO24](#RTO24)) can determine every path the object currently occupies in the LiveObjects tree - `(RTLO3f1)` Set to an empty map when the `LiveObject` is initialized - - `(RTLO3f2)` internal `addParentReference(parent, key)` method - records that the `LiveMap` `parent` references this `LiveObject` at `key` - - `(RTLO3f2a)` If `parent` is already present in `parentReferences`, `key` MUST be added to the existing set associated with `parent` - - `(RTLO3f2b)` Otherwise, a new entry MUST be inserted into `parentReferences` for `parent` with a set containing only `key` - - `(RTLO3f3)` internal `removeParentReference(parent, key)` method - removes the recorded reference from `parent` at `key` - - `(RTLO3f3a)` If `parent` is not present in `parentReferences`, the call MUST be a no-op - - `(RTLO3f3b)` Otherwise, `key` MUST be removed from the set associated with `parent` - - `(RTLO3f3c)` If, as a result of [RTLO3f3b](#RTLO3f3b), the set associated with `parent` is empty, the `parent` entry MUST be removed from `parentReferences` - `(RTLO3g)` internal `getFullPaths` function - returns the list of distinct paths from the root `LiveMap` (objectId `root`) to this `LiveObject`, computed by traversing `parentReferences` upward. Each returned path is an ordered sequence of keys from `root` to this `LiveObject`. - `(RTLO3g1)` `getFullPaths` MUST be implemented as an enumeration of all *simple paths* from this `LiveObject` to the root `LiveMap` over the inverse of the `parentReferences` graph (i.e. walking child → parent). A *simple path* is a path along which no `LiveObject` appears more than once. This is the standard graph problem, typically solved by a depth-first traversal with path-local backtracking equivalent to NetworkX's `all_simple_paths`. Implementation should choose iterative DFS with explicit stack (easier to read and debugging). - `(RTLO3g2)` If this `LiveObject` is the root `LiveMap` (objectId `root`), the returned list MUST contain exactly one path, and that path MUST be empty (zero key segments). This makes the root reachable from itself via the empty key sequence @@ -369,6 +362,13 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO4a4)` Get the `siteSerial` value stored for this `LiveObject` in the `siteTimeserials` map using the key `ObjectMessage.siteCode` - `(RTLO4a5)` If the `siteSerial` for this `LiveObject` is null or an empty string, return true - `(RTLO4a6)` If the `siteSerial` for this `LiveObject` is not an empty string, return true if `ObjectMessage.serial` is greater than `siteSerial` when compared lexicographically + - `(RTLO4f)` internal `addParentReference(parent, key)` method - records that the `LiveMap` `parent` references this `LiveObject` at `key` + - `(RTLO4f1)` If `parent` is already present in `parentReferences`, `key` MUST be added to the existing set associated with `parent` + - `(RTLO4f2)` Otherwise, a new entry MUST be inserted into `parentReferences` for `parent` with a set containing only `key` + - `(RTLO4g)` internal `removeParentReference(parent, key)` method - removes the recorded reference from `parent` at `key` + - `(RTLO4g1)` If `parent` is not present in `parentReferences`, the call MUST be a no-op + - `(RTLO4g2)` Otherwise, `key` MUST be removed from the set associated with `parent` + - `(RTLO4g3)` If, as a result of [RTLO4g2](#RTLO4g2), the set associated with `parent` is empty, the `parent` entry MUST be removed from `parentReferences` - `(RTLO4e)` protected `tombstone` - a convenience method used to tombstone this `LiveObject`. The realtime system reserves the right to tombstone an object (i.e. mark it for deletion from the objects pool) by publishing an `OBJECT_DELETE` operation at any time if the object is orphaned (not a descendant of the root object) or remains uninitialized (no `*_CREATE` operation has been received) for an extended period. Only the realtime system may publish an `OBJECT_DELETE` operation; 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](#RTO10) - `(RTLO4e1)` Expects the following arguments: - `(RTLO4e1a)` `ObjectMessage` @@ -380,7 +380,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLO4e5)` If the current `LiveObject` is of type `LiveMap`, then before [RTLO4e4](#RTLO4e4) is applied, do following: - `(RTLO4e5a)` For each iterated `entry` in current `LiveMap`'s private `data`: - `(RTLO4e5a1)` If `entry.value.data` have `objectId` as a field, retrieve corresponding child `LiveObject` from `ObjectsPool` using given `objectId` - - `(RTLO4e5a2)` If child `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing current `LiveMap` as `parent` and the iterated `entry.value` as `key` + - `(RTLO4e5a2)` If child `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4g](#RTLO4g), passing current `LiveMap` as `parent` and the iterated `entry.value` as `key` - `(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: @@ -673,7 +673,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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 - `(RTLM7a3)` If the current `ObjectsMapEntry` is of type `LiveObject`, before [RTLM7a2](#RTLM7a2e) is applied, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed: - `(RTLM7a3a)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId` - - `(RTLM7a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` + - `(RTLM7a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4g](#RTLO4g), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM7a2)` Otherwise, apply the operation to the existing entry: - `(RTLM7a2a)` This clause has been replaced by [RTLM7a2e](#RTLM7a2e) as of specification version 6.0.0. - `(RTLM7a2e)` Set `ObjectsMapEntry.data` to the `MapSet.value` @@ -691,7 +691,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM7g1)` Create a zero-value `LiveObject` for this `objectId` in the internal `ObjectsPool` per [RTO6](#RTO6) - `(RTLM7i)` A parent reference MUST be recorded on the `LiveObject` newly referenced by this entry (if any): - `(RTLM7i1)` If `MapSet.value.objectId` is not present, no action is required - - `(RTLM7i2)` Otherwise, call `addParentReference(parent, key)` per [RTLO3f2](#RTLO3f2) on the `LiveObject` in the local `ObjectsPool` with `objectId` equal to `MapSet.value.objectId` (guaranteed to exist per [RTLM7g](#RTLM7g)), passing this `LiveMap` as `parent` and the operation's key as `key` + - `(RTLM7i2)` Otherwise, call `addParentReference(parent, key)` per [RTLO4f](#RTLO4f) on the `LiveObject` in the local `ObjectsPool` with `objectId` equal to `MapSet.value.objectId` (guaranteed to exist per [RTLM7g](#RTLM7g)), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM7f)` Return a `LiveMapUpdate` object with a `LiveMapUpdate.update` map containing the key used in this operation set to `updated`, and `LiveMapUpdate.objectMessage` set to the provided `ObjectMessage` - `(RTLM8)` A `MAP_REMOVE` operation for a key can be applied to a `LiveMap` in the following way: - `(RTLM8c)` Expects the following arguments: @@ -706,7 +706,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(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 - `(RTLM8a3)` If the current `ObjectsMapEntry` is of type `LiveObject`, before [RTLM8a2](#RTLM8a2) is applied, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed: - `(RTLM8a3a)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId` - - `(RTLM8a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the operation's key as `key` + - `(RTLM8a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4g](#RTLO4g), passing this `LiveMap` as `parent` and the operation's key as `key` - `(RTLM8a2)` Otherwise, apply the operation to the existing entry: - `(RTLM8a2a)` Set `ObjectsMapEntry.data` to undefined/null - `(RTLM8a2b)` Set `ObjectsMapEntry.timeserial` to the provided `serial` @@ -732,7 +732,7 @@ Objects feature enables clients to store shared data as "objects" on a channel. - `(RTLM24e1)` If `ObjectsMapEntry.timeserial` is null or omitted, or the `serial` is lexicographically greater than `ObjectsMapEntry.timeserial`: - `(RTLM24e1c)` If the current `ObjectsMapEntry` is of type `LiveObject`, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed: - `(RTLM24e1c1)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId` - - `(RTLM24e1c2)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO3f3](#RTLO3f3), passing this `LiveMap` as `parent` and the iterated entry key as `key` + - `(RTLM24e1c2)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4g](#RTLO4g), passing this `LiveMap` as `parent` and the iterated entry key as `key` - `(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`, and `LiveMapUpdate.objectMessage` set to the provided `ObjectMessage` @@ -1114,6 +1114,8 @@ Types and their properties/methods are public and exposed to users by default. A isTombstone: Boolean // RTLO3d tombstonedAt: Time? // RTLO3e canApplyOperation(ObjectMessage) -> Boolean // RTLO4a + addParentReference(parent, key) // RTLO4f + removeParentReference(parent, key) // RTLO4g tombstone(ObjectMessage) // RTLO4e subscribe((LiveObjectUpdate) ->) -> Subscription // RTLO4b