Skip to content

Commit 103f5b9

Browse files
lawrence-forooghianclaude
authored andcommitted
Pull parentReferences maintenance rules from PR #480
Imports the parentReferences bookkeeping spec from PR #480 [1] onto this integration branch, resolving the committed conflict marker at RTLO3f and the duplicate clause IDs introduced by the import. Imported from #480 verbatim: - RTO5c10: post-sync rebuild of every parentReferences map. - addParentReference and removeParentReference internal methods, with set-merge / set-remove / empty-set-delete semantics. - Tombstone-time children walk for LiveMap, stripping parent references from each referenced child before the data is cleared. - MAP_SET, MAP_REMOVE and MAP_CLEAR parent-reference maintenance (RTLM7a3, RTLM7i, RTLM8a3, RTLM24e1c). - IDL declarations for the two new internal methods. The Primitive type alias added in #480 was deliberately not imported, as it is unrelated to the parentReferences work. Conflicts reconciled: - The committed <<<<<<< / >>>>>>> block at RTLO3f. Kept the objectId-keyed Dict<String, Set<String>> description from this branch (consistent with #480's own IDL line and its set-style manipulation contracts; the alternative half mandated a specific in-memory representation that ably-js does not match literally). The "set to an empty map on initialisation" clause from #480 was moved to RTLO3f2; the prior RTLO3f2 TODO is deleted, since the imported maintenance rules now resolve it. RTO5c10a's back-reference was updated to point at the new RTLO3f2. - Duplicate clause IDs introduced by #480 were renamed per the "rename the later addition" convention in CONTRIBUTING.md: - addParentReference: RTLO4f -> RTLO4g - removeParentReference: RTLO4g -> RTLO4h - tombstone children walk: RTLO4e5* -> RTLO4e9* All cross-references to the renamed clauses were updated accordingly. The pre-existing RTLO4f (getFullPaths) and RTLO4e5-e8 (Compute LiveObjectUpdate through Return) are untouched. Linter passes. Still needs human review. [1] #480 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4a8e293 commit 103f5b9

1 file changed

Lines changed: 30 additions & 1 deletion

File tree

specifications/objects-features.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ Objects feature enables clients to store shared data as "objects" on a channel.
174174
- `(RTO5c1b1c)` This clause has been deleted (redundant to [RTO5f3](#RTO5f3)).
175175
- `(RTO5c2)` Remove any objects from the internal `ObjectsPool` for which `objectId`s were not received during the sync sequence
176176
- `(RTO5c2a)` The object with ID `root` must not be removed from `ObjectsPool`, as per [RTO3b](#RTO3b)
177+
- `(RTO5c10)` After re-establishing the `ObjectsPool` per [RTO5c1](#RTO5c1) and [RTO5c2](#RTO5c2), the client MUST rebuild every `parentReferences` map ([RTLO3f](#RTLO3f)). Specifically:
178+
- `(RTO5c10a)` For each `LiveObject` in the internal `ObjectsPool` ([RTO3](#RTO3)), reset its `parentReferences` to an empty map as defined in [RTLO3f2](#RTLO3f2)
179+
- `(RTO5c10b)` After [RTO5c10a](#RTO5c10a) has completed, for each `LiveMap` in the internal `ObjectsPool`, iterate its `LiveMap#entries` as per [RTLM11](#RTLM11)
180+
- `(RTO5c10b1)` For each iterated entry whose value type is `LiveObject`, call `addParentReference(parent, key)` on the `LiveObject` (per [RTLO4g](#RTLO4g)), passing the iterated `LiveMap` as `parent` and the iterated entry key as `key`
177181
- `(RTO5c7)` For each previously existing object that was updated as a result of [RTO5c1a](#RTO5c1a), emit the corresponding stored `LiveObjectUpdate` object from [RTO5c1a2](#RTO5c1a2)
178182
- `(RTO5c6)` `ObjectMessages` stored in the `bufferedObjectOperations` list are applied as described in [RTO9](#RTO9), passing `source` as `CHANNEL`
179183
- `(RTO5c3)` Clear any stored sync sequence identifiers and cursor values
@@ -325,7 +329,7 @@ Objects feature enables clients to store shared data as "objects" on a channel.
325329
- `(RTLO3e1)` Set to undefined/null when the `LiveObject` is initialized
326330
- `(RTLO3f)` protected `parentReferences` `Dict<String, Set<String>>` - tracks which `LiveMap`s in the local `ObjectsPool` currently reference this `LiveObject`, and at which keys they do so. The mapping is keyed by the parent `LiveMap`'s `objectId`, with each value being the set of keys at which that `LiveMap` references this `LiveObject`. Used by `getFullPaths` ([RTLO4f](#RTLO4f)) to determine every path the object currently occupies in the LiveObjects tree
327331
- `(RTLO3f1)` This mapping is keyed by `objectId` for consistency with the rest of the LiveObjects spec, where references between objects are stored as `objectId`s and resolved via the `ObjectsPool` on demand. Implementations may store a direct reference to the parent `LiveMap` instead — for example to avoid an `ObjectsPool` lookup at each step of `getFullPaths` ([RTLO4f](#RTLO4f)) traversal — provided the observable behaviour is unchanged. Such implementations should be aware that this may introduce reference cycles between `LiveMap`s, and must ensure this does not cause memory leaks
328-
- `(RTLO3f2)` TODO: The detailed maintenance rules for `parentReferences` (across `MAP_SET`, `MAP_REMOVE`, `MAP_CLEAR`, `LiveMap` tombstoning, and post-sync rebuild) are to be specified by Sachin in a follow-up; see https://github.com/ably/specification/pull/480 for the in-progress draft
332+
- `(RTLO3f2)` Set to an empty map when the `LiveObject` is initialized
329333
- `(RTLO4)` `LiveObject` methods:
330334
- `(RTLO4b)` `subscribe` - subscribes a user to data updates on this `LiveObject` instance
331335
- `(RTLO4b1)` Requires the `OBJECT_SUBSCRIBE` channel mode to be granted per [RTO2](#RTO2)
@@ -362,6 +366,13 @@ Objects feature enables clients to store shared data as "objects" on a channel.
362366
- `(RTLO4a4)` Get the `siteSerial` value stored for this `LiveObject` in the `siteTimeserials` map using the key `ObjectMessage.siteCode`
363367
- `(RTLO4a5)` If the `siteSerial` for this `LiveObject` is null or an empty string, return true
364368
- `(RTLO4a6)` If the `siteSerial` for this `LiveObject` is not an empty string, return true if `ObjectMessage.serial` is greater than `siteSerial` when compared lexicographically
369+
- `(RTLO4g)` internal `addParentReference(parent, key)` method - records that the `LiveMap` `parent` references this `LiveObject` at `key`
370+
- `(RTLO4g1)` If `parent` is already present in `parentReferences`, `key` MUST be added to the existing set associated with `parent`
371+
- `(RTLO4g2)` Otherwise, a new entry MUST be inserted into `parentReferences` for `parent` with a set containing only `key`
372+
- `(RTLO4h)` internal `removeParentReference(parent, key)` method - removes the recorded reference from `parent` at `key`
373+
- `(RTLO4h1)` If `parent` is not present in `parentReferences`, the call MUST be a no-op
374+
- `(RTLO4h2)` Otherwise, `key` MUST be removed from the set associated with `parent`
375+
- `(RTLO4h3)` If, as a result of [RTLO4h2](#RTLO4h2), the set associated with `parent` is empty, the `parent` entry MUST be removed from `parentReferences`
365376
- `(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)
366377
- `(RTLO4e1)` Expects the following arguments:
367378
- `(RTLO4e1a)` `ObjectMessage`
@@ -370,6 +381,10 @@ Objects feature enables clients to store shared data as "objects" on a channel.
370381
- `(RTLO4e3a)` This clause has been replaced by [RTLO6a](#RTLO6a)
371382
- `(RTLO4e3b)` This clause has been replaced by [RTLO6b](#RTLO6b)
372383
- `(RTLO4e3b1)` This clause has been replaced by [RTLO6b1](#RTLO6b1)
384+
- `(RTLO4e9)` If the current `LiveObject` is of type `LiveMap`, then before [RTLO4e4](#RTLO4e4) is applied, do following:
385+
- `(RTLO4e9a)` For each iterated `entry` in current `LiveMap`'s private `data`:
386+
- `(RTLO4e9a1)` If `entry.value.data` have `objectId` as a field, retrieve corresponding child `LiveObject` from `ObjectsPool` using given `objectId`
387+
- `(RTLO4e9a2)` If child `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4h](#RTLO4h), passing current `LiveMap` as `parent` and the iterated `entry.value` as `key`
373388
- `(RTLO4e4)` Set the `data` attribute of the `LiveObject` to the value described in [RTLC4b](#RTLC4b) or [RTLM4c](#RTLM4c), depending on the object type
374389
- `(RTLO4e5)` Compute a `LiveObjectUpdate` representing the data change resulting from this `LiveObject` being tombstoned, by calculating the diff between the `data` value from before [RTLO4e4](#RTLO4e4) was applied (as `previousData`) and the current `data` value (as `newData`), per [RTLC14](#RTLC14) or [RTLM22](#RTLM22), depending on the object type
375390
- `(RTLO4e6)` Set `LiveObjectUpdate.tombstone` to `true` on the object computed in [RTLO4e5](#RTLO4e5)
@@ -684,6 +699,9 @@ Objects feature enables clients to store shared data as "objects" on a channel.
684699
- `(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
685700
- `(RTLM7a)` If an `ObjectsMapEntry` exists in the private `data` for the specified key:
686701
- `(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
702+
- `(RTLM7a3)` If the current `ObjectsMapEntry` is of type `LiveObject`, before [RTLM7a2](#RTLM7a2e) is applied, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed:
703+
- `(RTLM7a3a)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId`
704+
- `(RTLM7a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4h](#RTLO4h), passing this `LiveMap` as `parent` and the operation's key as `key`
687705
- `(RTLM7a2)` Otherwise, apply the operation to the existing entry:
688706
- `(RTLM7a2a)` This clause has been replaced by [RTLM7a2e](#RTLM7a2e) as of specification version 6.0.0.
689707
- `(RTLM7a2e)` Set `ObjectsMapEntry.data` to the `MapSet.value`
@@ -699,6 +717,9 @@ Objects feature enables clients to store shared data as "objects" on a channel.
699717
- `(RTLM7c1)` This clause has been replaced by [RTLM7g1](#RTLM7g1) as of specification version 6.0.0.
700718
- `(RTLM7g)` If `MapSet.value.objectId` is non-empty:
701719
- `(RTLM7g1)` Create a new `LiveObject` for this `objectId` in the internal `ObjectsPool` per [RTO6](#RTO6)
720+
- `(RTLM7i)` A parent reference MUST be recorded on the `LiveObject` newly referenced by this entry (if any):
721+
- `(RTLM7i1)` If `MapSet.value.objectId` is not present, no action is required
722+
- `(RTLM7i2)` Otherwise, call `addParentReference(parent, key)` per [RTLO4g](#RTLO4g) 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`
702723
- `(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`
703724
- `(RTLM8)` A `MAP_REMOVE` operation for a key can be applied to a `LiveMap` in the following way:
704725
- `(RTLM8c)` Expects the following arguments:
@@ -711,6 +732,9 @@ Objects feature enables clients to store shared data as "objects" on a channel.
711732
- `(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
712733
- `(RTLM8a)` If an `ObjectsMapEntry` exists in the private `data` for the specified key:
713734
- `(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
735+
- `(RTLM8a3)` If the current `ObjectsMapEntry` is of type `LiveObject`, before [RTLM8a2](#RTLM8a2) is applied, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed:
736+
- `(RTLM8a3a)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId`
737+
- `(RTLM8a3b)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4h](#RTLO4h), passing this `LiveMap` as `parent` and the operation's key as `key`
714738
- `(RTLM8a2)` Otherwise, apply the operation to the existing entry:
715739
- `(RTLM8a2a)` Set `ObjectsMapEntry.data` to undefined/null
716740
- `(RTLM8a2b)` Set `ObjectsMapEntry.timeserial` to the provided `serial`
@@ -734,6 +758,9 @@ Objects feature enables clients to store shared data as "objects" on a channel.
734758
- `(RTLM24d)` Set the private `clearTimeserial` to the provided `serial`
735759
- `(RTLM24e)` For each `ObjectsMapEntry` in the internal `data`:
736760
- `(RTLM24e1)` If `ObjectsMapEntry.timeserial` is null or omitted, or the `serial` is lexicographically greater than `ObjectsMapEntry.timeserial`:
761+
- `(RTLM24e1c)` If the current `ObjectsMapEntry` is of type `LiveObject`, the parent reference recorded on existing `ObjectsMapEntry` MUST be removed:
762+
- `(RTLM24e1c1)` To check `ObjectsMapEntry` is of type `LiveObject`, validate `ObjectsMapEntry.data` has a `objectId` field, retrieve corresponding `LiveObject` from `ObjectsPool` using given `objectId`
763+
- `(RTLM24e1c2)` If `LiveObject` exists, call its `removeParentReference(parent, key)` method per [RTLO4h](#RTLO4h), passing this `LiveMap` as `parent` and the iterated entry key as `key`
737764
- `(RTLM24e1a)` Remove the entry from the internal `data` map. The entry is not retained as a tombstone.
738765
- `(RTLM24e1b)` Record the key for the `LiveMapUpdate` as `removed`
739766
- `(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`
@@ -1106,6 +1133,8 @@ Types and their properties/methods are public and exposed to users by default. A
11061133
parentReferences: Dict<String, Set<String>> // RTLO3f
11071134
canApplyOperation(ObjectMessage) -> Boolean // RTLO4a
11081135
tombstone(ObjectMessage) -> LiveObjectUpdate // RTLO4e
1136+
addParentReference(parent, key) // RTLO4g
1137+
removeParentReference(parent, key) // RTLO4h
11091138
subscribe((LiveObjectUpdate) ->) -> Subscription // RTLO4b
11101139

11111140
interface LiveObjectUpdate: // RTLO4b4, internal

0 commit comments

Comments
 (0)