[AIT-30] LiveObjects Path-based API spec#427
Conversation
13dee45 to
1e518c6
Compare
1fa3eeb to
46261f4
Compare
49f0364 to
47a9d51
Compare
46261f4 to
3608895
Compare
3608895 to
b4ad764
Compare
7738c92 to
fa2a54e
Compare
b4ad764 to
1eb4dd8
Compare
1eb4dd8 to
035aef9
Compare
|
|
||
| ### LiveCounterValueType | ||
|
|
||
| A `LiveCounterValueType` is an immutable blueprint for creating a new `LiveCounter` object. It stores the desired initial count value and is consumed when passed to a mutation method such as `LiveMap#set` ([RTLM20](#RTLM20)) or as an entry value in `LiveMap.create` ([RTLMV3](#RTLMV3)). |
There was a problem hiding this comment.
an immutable blueprint
I kind of prefer LiveCounterBlueprint — it indicates that it's not a LiveCounter but something else, whereas ValueType doesn't communicate very much (it's… what, a LiveCounter that is a value type?)
There was a problem hiding this comment.
or LiveCounterTemplate ("template" is a bit overloaded in some languages, but "blueprint" is perhaps a bit flowery)
There was a problem hiding this comment.
I asked Claude to have a think
My top picks if I had to shortlist five:
- LiveCounterBlueprint — clear metaphor, distinctive, distinct from anything
in software vocab.- LiveCounterSeed — short, evocative, fits the "grows into" semantic.
- LiveCounterRecipe — pleasant metaphor, captures "follow these instructions
to produce".- LiveCounterIntent — most honest about the actual semantic; mirrors RTLCV1.
- LiveCounterTemplate — most conventional; baggage but familiar.
Of these, Blueprint and Seed feel the most distinctive and unambiguous to me.
Seed has the bonus of being short.
There was a problem hiding this comment.
Isn't it what we call "CreateParams" in other SDKs (Chat AFAIK)? LiveCounterCreateParams is recognizable by everyone I guess.
There was a problem hiding this comment.
Yeah that works too
| - `(RTPO5)` `PathObject#get` function: | ||
| - `(RTPO5a)` Expects the following arguments: | ||
| - `(RTPO5a1)` `key` `String` - the key to navigate to | ||
| - `(RTPO5b)` If `key` is not of type `String`, the library must throw an `ErrorInfo` error with `statusCode` 400 and `code` 40003, indicating that the key must be a `String` |
There was a problem hiding this comment.
I think I've expressed it before but I think that these sorts of spec points are starting to pollute the spec a bit (there's quite a lot of them); it feels like there could, somewhere, be a single generic spec point saying that if a language without compiler-guaranteed argument types wishes to perform validation of the input types then if the validation fails it should throw an error with statusCode 400 and code 40003.
There was a problem hiding this comment.
That said, we do seem to have a distinction between 40003 ("invalid parameter value") and 40013 ("Invalid message data or encoding") but to me it feels like they both represent the same class of error when thrown client-side (user passed an invalid argument to an SDK method)
| - `(RTLMV4c)` If any of the values in the internal `entries` are not of an expected type, the library should throw an `ErrorInfo` error with `statusCode` 400 and `code` 40013, indicating that such data type is unsupported | ||
| - `(RTLMV4d)` Build entries for the `MapCreate` object. For each key-value pair in the internal `entries` (if present), create an `ObjectsMapEntry` for the value: | ||
| - `(RTLMV4d1)` If the value is of type `LiveCounterValueType`, consume it per [RTLCV4](#RTLCV4) to generate a `COUNTER_CREATE` `ObjectMessage`. Collect the generated `ObjectMessage` and set `ObjectsMapEntry.data.objectId` to the `objectId` from the `ObjectMessage` | ||
| - `(RTLMV4d2)` If the value is of type `LiveMapValueType`, recursively consume it per [RTLMV4](#RTLMV4) to generate `ObjectMessages`. Collect all generated `ObjectMessages` and set `ObjectsMapEntry.data.objectId` to the `objectId` from the outermost `MAP_CREATE` `ObjectMessage` |
There was a problem hiding this comment.
same comment as https://github.com/ably/specification/pull/427/changes#r3259042315 re "outermost"
| - `(RTO22b)` `CHANNEL` - an operation received over a Realtime 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: |
There was a problem hiding this comment.
Presumably also we want to exclude updates with noOp set to true? would be good if we could keep the "don't do anything on noOp events" logic in a single place instead of having to repeat it here though
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that a subscription never emits two events for the same LiveMapUpdate. One particular case in which this would otherwise happen is the scenario when, after the user subscribes for path ["map"] which contains a map, this map then emits an update for some key "myKey", which without this exclusion mechanism would emit two events to that subscription: one for "map" and one for "map.key". If that _is_ the only reason that this mechanism exists, then it seems like it might conceptually simpler to gather all the paths that we consider a given LiveObjectUpdate to have touched and then for each subscription look at these paths and pick zero or one of these paths to emit an event for, with a shortest-match tiebreaker rule. This makes the "one event per subscription" rule clearer, and I _think_ is simpler to specify. So I'm proposing this change as a way of generating discussion. Perhaps I've misunderstood the intent of `bubbles`, or perhaps there are ways in which it might be extended in the future that this approach doesn't cover, or perhaps what I'm proposing is not in other people's opinions conceptually simpler. Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that a subscription never emits two events for the same LiveMapUpdate. One particular case in which this would otherwise happen is the scenario when, after the user subscribes for path ["map"] which contains a map, this map then emits an update for some key "myKey", which without this exclusion mechanism would emit two events to that subscription: one for "map" and one for "map.key". If that _is_ the only reason that this mechanism exists, then it seems like it might be conceptually simpler to gather all the paths that we consider a given LiveObjectUpdate to have touched and then for each subscription look at these paths and pick zero or one of these paths to emit an event for, with a shortest-match tiebreaker rule. This makes the "one event per subscription" rule clearer, and I _think_ is simpler to specify. So I'm proposing this change as a way of generating discussion. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps this proposed approach doesn't allow for additional dispatch mechanisms that we might need in the future, or perhaps others don't find what I'm proposing conceptually simpler. Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that a subscription never emits two events for the same LiveMapUpdate. One particular case in which this would otherwise happen is the scenario when, after the user subscribes for path ["map"] which contains a map, this map then emits an update for some key "myKey", which without this exclusion mechanism would emit two events to that subscription: one for "map" and one for "map.key". If that _is_ the only reason that this mechanism exists, then it seems like it might be conceptually simpler to gather all the paths that we consider a given LiveObjectUpdate to have touched and then for each subscription look at these paths and pick zero or one of these paths to emit an event for, with a shortest-match tiebreaker rule. This makes the "one event per subscription" rule clearer, and I _think_ is simpler to specify. So I'm proposing this change as a way of generating discussion. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps this proposed approach doesn't allow for additional dispatch mechanisms that we might need in the future, or perhaps others don't find what I'm proposing conceptually simpler. Alternatively, perhaps `bubbles` just needs better documentation that explains its motivation. Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-root - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing conceptually simpler (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-root - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing conceptually simpler (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-root - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing clearer (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-root - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing clearer (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-root - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing clearer (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-root - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing clearer (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-root - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing clearer (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After noticing that the path-based API spec PR [1] didn't include map-entry events nor the bubbling-exclusion mechanism that's implemented in ably-js, I wanted to get a better understanding of this exclusion mechanism and why it exists. From what I can tell, it exists to make sure that if there's a map at path "myMap", and this map emits a LiveMapUpdate having key "myKey", then a subscription that covers the path "myMap" will only receive one event (for "myMap"), as opposed to two (for "myMap" and "myMap.myKey"). If that _is_ the only reason that this mechanism exists, then I don't think it's very obvious currently; the intended behaviour isn't documented. I think the implementation could be clearer if it makes the rules explicit: - for a given LiveObjectUpdate, a given subscription receives at most one event per path-to-object - in the case where a subscription covers both the "myMap" and "myMap.myKey" paths, "myMap" wins That's what I've tried to do here. Perhaps I've misunderstood the intent of `bubbles` (which, given that the tests still pass, would suggest a test gap), or perhaps others don't find what I'm proposing clearer (in which case, `bubbles` needs better documentation that explains its motivation). Thoughts, please. [1] ably/specification#427 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `LiveCounterUpdate` and `LiveMapUpdate` subtypes already have the `internal` marker; the parent `LiveObjectUpdate` interface was missing it. Add it for consistency, reflecting that `LiveObject#subscribe` and its emitted update objects are not part of the public API (confirmed against ably-js, where these types live only in the plugin internals and are not exported from `liveobjects.d.ts`). Addresses [1]. [1] #427 (comment) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The `entries`, `keys`, and `values` methods on `PathObject` and `Instance` were previously described as returning iterators (`Iterator<...>` in the IDL, "iterator yielding..." in the prose). "Iterator" is not a term we use elsewhere in the spec, and the array form is easier to reason about and consistent with how `LiveMap#entries` ([RTLM11](#RTLM11)) etc. are already specified. SDKs remain free to use a platform-idiomatic equivalent in their public surface (e.g. a JS iterator). Addresses [1]. [1] #427 (comment) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Several PathObject and Instance methods previously described their
behaviour by restating the underlying LiveMap or LiveCounter semantics
("returns the number of non-tombstoned entries, equivalent to
LiveMap#size") or by chaining through a sibling method ("behaves
identically to PathObject#entries except the array contains only the
keys"). Both forms duplicate or obscure the fact that the SDK is just
delegating to the LiveMap/LiveCounter spec point.
Rewrite each as an explicit delegation, so the LiveMap/LiveCounter
spec point remains the single source of truth for the semantics
(tombstone handling, ordering, etc.):
- RTPO7b, RTINS4a: PathObject#value / Instance#value for LiveCounter
delegate to LiveCounter#value (consistency fold-in, no specific
comment).
- RTPO9 (PathObject#entries): collapse the previous b/c/d into b
(delegate to LiveMap#keys and build [key, PathObject] pairs) plus
c (empty array on failure). LiveMap#keys rather than #entries is
correct because the PathObject is lazy and does not need resolved
values.
- RTPO10, RTPO11 (PathObject#keys / #values): resolve and delegate
directly to LiveMap#keys instead of chaining through
PathObject#entries. RTPO11 still wraps each key in a PathObject.
- RTPO12b, RTINS9a (#size): delegate to LiveMap#size.
- RTINS6a (Instance#entries): rephrase to lead with "delegates to
LiveMap#entries" and wrap each value in an Instance.
- RTINS7, RTINS8 (Instance#keys / #values): delegate directly to
LiveMap#keys / #values; RTINS8 wraps each value in an Instance.
compact / compactJson (RTPO13/14, RTINS10/11) are not touched here;
they will be addressed separately because they require a new
LiveMap#compact spec point.
Addresses [1], [2], [3] (and reply [4]), [5].
[1] #427 (comment)
[2] #427 (comment)
[3] #427 (comment)
[4] #427 (comment)
[5] #427 (comment)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lue types "Consume" suggested a one-shot procedure, which is misleading for an immutable value type that could in principle be evaluated multiple times (e.g. passed to several mutation calls). "Evaluate" captures the deferred-validation semantics of the value type (per RTLCV3c / RTLMV3c, validation is deferred to this procedure, much like evaluating a lazy expression) without the one-shot implication. The unrelated "consuming subscription events as a stream" usages in RTPO21 and RTINS18 are left alone — that is the standard consumer-of-stream sense, which doesn't have the same problem. Addresses [1]. [1] #427 (comment) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
"Once `unsubscribe` called" was missing the verb. Reads as "Once `unsubscribe` is called" now. Addresses [1]. [1] #427 (comment) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
RTINS16d1 previously read "the `Instance` representing the updated object", which was vague. Reword to "an `Instance` wrapping the underlying `LiveObject`", reusing the "underlying `LiveObject`" terminology already used in RTINS16c. The new wording deliberately uses "an `Instance`" rather than "the `Instance` on which `#subscribe` was called", so that the spec does not mandate strict reference identity between the subscribed `Instance` and the one delivered in the event. ably-js currently reuses the subscriber's own `Instance` (`instance.ts:218-223` passes `object: this`), which has the side effect of pinning that `Instance` for the lifetime of the subscription. Implementations are free to make that trade-off either way: pin for identity, or allocate a fresh `Instance` per event so the subscriber's reference can be collected independently. Addresses [1] and its follow-up [2]. [1] #427 (comment) [2] #427 (comment) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Note: This PR is based on #470; please review that one first.
Resolves AIT-30.