[AIT-31] LiveObjects Path-based API product docs for JavaScript SDK#3019
[AIT-31] LiveObjects Path-based API product docs for JavaScript SDK#3019mschristensen merged 1 commit intomainfrom
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the ✨ Finishing touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
41ef613 to
19a241e
Compare
|
One concern I have regarding describing one of the benefits of Instance subscriptions as tracking objects "as they move" through the object tree, e.g. here:
"Issue": There's actually currently no way to "move" an object via the Realtime API - no Options:
Thoughts? @lawrence-forooghian @mschristensen |
lawrence-forooghian
left a comment
There was a problem hiding this comment.
This is a big PR, nice work 😅 I've had a look through all of it and left some comments, nothing major though
src/pages/docs/liveobjects/index.mdx
Outdated
| Ably LiveObjects provides the following key features: | ||
|
|
||
| * [Object types](#object-types) | ||
| <If lang="javascript">* [Path-based API](#path-based-api)</If> |
There was a problem hiding this comment.
Might it be worth explicitly mentioning, in the places where we talk about the path-based API, that it's only available in JS at the moment and that Swift and Kotlin still use an older API?
There was a problem hiding this comment.
Also, there is no language selector in this page (presumably because there are no code snippets?) so it's unclear to me that these conditional blocks are really doing anything
There was a problem hiding this comment.
If-ed the whole bullet list to make sure it renders correctly and added languages list for this page in nav configs - not sure what's a condition for it to appear automatically, but now it works as expected.
Might it be worth explicitly mentioning, in the places where we talk about the path-based API, that it's only available in JS at the moment and that Swift and Kotlin still use an older API?
I believe most places will have Ifs to handle that, but I added a not for new PathObject and Instance pages to indicate they're JS specific
| </Code> | ||
|
|
||
| The subscription callback receives an event object with: | ||
| - `object`: A `PathObject` representing the updated path |
There was a problem hiding this comment.
not sure that "updated path" makes sense (the path hasn't changed; its contents have)
| - `object`: A `PathObject` representing the updated path | |
| - `object`: A `PathObject` representing the path at which there was an object change |
|
|
||
| // Access via path | ||
| const settings = myObject.get('settings'); | ||
| await settings.set('fontSize', 14); |
There was a problem hiding this comment.
Forgive my ignorance here but what happens if the user tries setting fontSize before the client receives the map created in myObject.set('settings')? Does the client end up potentially updating the map (if any) that was previously at the settings entry instead, thus causing the fontSize change to be lost?
There was a problem hiding this comment.
Yes ,this can happen as we still don't have apply operation on ACK implemented. This behavior is documented for all publish operation and subscription methods.
That said, I think this code example is simple enough to just drop an "access" part (as its main focus is to show how to create an object, just like java/swift examples below) bit so we can avoid the need to clarify this behavior here.
| // Increment the counter | ||
| await visitsCounter.increment(1); | ||
| // Access via path | ||
| const visits = myObject.get('visits'); |
There was a problem hiding this comment.
I have a similar question here to above; in this scenario, is it possible that the counter that gets incremented here is not the one that they created in the myObject.set('visits', LiveCounter.create(0)) call?
| * Serves as the [entry point](#reachability) for accessing all other objects on a channel | ||
|
|
||
| Access the root object using the `getRoot()` function: | ||
| Access the root object using `channel.object.get()`: |
There was a problem hiding this comment.
I think that this needs to be language-<If>-d?
|
|
||
| ### entries() method <a id="entries"/> | ||
|
|
||
| The `entries()` method returns an iterator of `[key, PathObject]` pairs: |
There was a problem hiding this comment.
I am curious why we chose to return an iterator and not just an array? Is it a common thing to do in JS? I'd get it if perhaps we wanted to do some sort of lazy streaming of results or something, but otherwise it seems it's something that's harder to log to the console and which some developers might not be familiar with.
src/data/nav/liveobjects.ts
Outdated
| link: '/docs/liveobjects/concepts/path-object', | ||
| }, | ||
| { | ||
| name: 'Object Instance', |
There was a problem hiding this comment.
I think just Instance would be OK? else lowercase the "Instance"
There was a problem hiding this comment.
I agree it should be Instance
|
|
||
| <Code> | ||
| ```javascript | ||
| myObject.get('visits').subscribe(({ object, message }) => { |
There was a problem hiding this comment.
Something I've been wondering whilst looking at this API — are the subscriptions added at a path level or at a PathObject instance level? That, is, if I write
const pathObject = myObject.get('visits').subscribe(() => {
console.log("pathObject listener fired")
});
// pathObject presumably gets garbage collected shortly after since the user has no reference to it?
— does the listener still get called when events happen at that path even though the PathObject instance through which they subscribed no longer exists?
|
|
||
| // Remove a key | ||
| await map.remove('username'); | ||
| // Using PathObject |
There was a problem hiding this comment.
I think that if you wish to show both the PathObject and Instance APIs here (which I'm not sure you need to do since it's not that important for explaining the semantics of the operations) then it might be better as two separate code blocks to make it clear they're not related
|
|
||
| // Set a nested map | ||
| await myObject.get('settings').set('advanced', LiveMap.create({ | ||
| debugMode: false, |
There was a problem hiding this comment.
minor: maybe we could pick some other example values? this confused me at first because it looked like they were keys that control the behaviour of LiveMap.create
I think that whatever it is we do, we should handle the mention of cyclic references in the same way (either not mention them or mention them and explain the limited circumstances in which they might occur); I'd lean towards the latter. |
mschristensen
left a comment
There was a problem hiding this comment.
Submitting my review with initial comments - we caught up on a call and I will take this further forward based on our discussions :)
src/pages/docs/liveobjects/index.mdx
Outdated
| Ably LiveObjects provides the following key features: | ||
|
|
||
| * [Object types](#object-types) | ||
| <If lang="javascript">* [Path-based API](#path-based-api)</If> |
There was a problem hiding this comment.
Also, there is no language selector in this page (presumably because there are no code snippets?) so it's unclear to me that these conditional blocks are really doing anything
| reactionsMap.subscribe(() => { | ||
| // Subscribe to map updates | ||
| reactionsMap.subscribe(({ object }) => { | ||
| console.log('Reactions map updated:', [...reactionsMap.entries()]); |
There was a problem hiding this comment.
I was going to suggest:
console.log('Reactions map updated:', [...object.entries()]);
but then I realised that's wrong, as object might be some nested object that changed.
I think we need to add some context here explaining this, at least at the surface level, as on initial read the user immediately wonders why the counter example uses the object but the map example doesn't
| ## Update objects <a id="step-7"/> | ||
|
|
||
| Update objects using mutation methods. All subscribers (including you) will be notified of the changes when you update an object: | ||
| Update objects using mutation methods on `PathObject`. All subscribers (including you) will be notified of the changes: |
There was a problem hiding this comment.
| Update objects using mutation methods on `PathObject`. All subscribers (including you) will be notified of the changes: | |
| Update objects using mutation methods on `PathObject`. All subscribers, including the client that made the update, will be notified of the changes: |
| console.log('Visits counter updated:', visitsCounter.value()); | ||
| // Subscribe to counter updates | ||
| visitsCounter.subscribe(({ object }) => { | ||
| console.log('Visits updated:', object.value()); |
There was a problem hiding this comment.
To align this log with the examples that follow:
| console.log('Visits updated:', object.value()); | |
| console.log('Visits counter updated:', object.value()); |
There was a problem hiding this comment.
We should update this - we call it the "channel object" in the index page, we should use that framing here too
| | `path()` | Get the string representation of the path | All paths | | ||
| | `value()` | Get the current value | Primitives, counters | | ||
| | `instance()` | Get the underlying [Instance](/docs/liveobjects/concepts/instance) | Maps, counters | | ||
| | `compact()` | Get a JSON representation of the data | All paths | |
There was a problem hiding this comment.
Not quite true, should be e.g. "Get a JavaScript object representation of the data"
|
|
||
| #### Binary values <a id="compact-binary"/> | ||
|
|
||
| Binary values (such as `ArrayBuffer`) are returned as base64-encoded strings: |
There was a problem hiding this comment.
I think we need to implement what we discussed the other day - i.e. the compact method should either return a JSON serializable value which is identical to that returned by REST, or it should return a JS object with cyclic references and ArrayBuffer binary values.
Let me know if we need to sync on this. Changing the behaviour would be a breaking change post release, so I would suggest we tag the method experimental in the release (and label it as such here) before we do so.
| ``` | ||
| </Code> | ||
|
|
||
| ### Subscription options <a id="subscription-options"/> |
There was a problem hiding this comment.
This should appear below subscribeIterator() method heading, and include an example of providing options for both styles of subscription API
|
|
||
| Since PathObject resolves paths at runtime, it's important to understand what happens when a path doesn't resolve as expected. | ||
|
|
||
| ### Access methods <a id="access-errors"/> |
There was a problem hiding this comment.
Should this include an example of using .instance() with a instanceof LiveMapInstance (or similar) to assert on the expected type at runtime?
src/data/nav/liveobjects.ts
Outdated
| link: '/docs/liveobjects/concepts/path-object', | ||
| }, | ||
| { | ||
| name: 'Object Instance', |
There was a problem hiding this comment.
I agree it should be Instance
e340a07 to
8f2ca12
Compare
|
|
||
| #### ObjectData | ||
|
|
||
| The `data` field in `mapOp` is an `ObjectData` object that represents the value assigned to a map key. It has the following properties: |
There was a problem hiding this comment.
see above, ObjectData also represents the value for the intial entries in operation.map.entries
There was a problem hiding this comment.
Yeah let's leave it for now, as we need to document subscriptions with the sync messages, if you could please create a ticket that would be grand
611b43a to
b6022a1
Compare
0be35bf to
67b27a4
Compare
67b27a4 to
1e40f50
Compare
Updates the LiveObjects product docs for the new path based API in AblyJS, which is in public preview. Swift, Java and REST API remain in Experimental. Resolves AIT-31
1e40f50 to
b15caef
Compare

Description
Product docs for LiveObjects Path-based API for JavaScript SDK.
See:
Resolves AIT-31
Checklist