From 806e76df03a3b77c3f2403de42d36efc306882b8 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 10 Mar 2026 12:13:22 +0000 Subject: [PATCH 1/3] Add LiveObjects REST SDK usage docs Largely based on the existing usage doc for the REST API [1]. Added REST SDK docs as a separate page for two reasons: 1. There is no language selector for the shell/curl/HTTP "language", nor do I think it would look or feel nice UX-wise. It would only exist for this single page and may not be obvious to readers that they can switch it at the top to see SDK/API docs. 2. The SDK docs differ in many minor details throughout, and it would be a nightmare to maintain different versions on the same page using if-lang blocks. Where large sections are unchanged (like the procedure to generate the client-generated object ID), the doc references the corresponding REST API section instead of duplicating it. Resolves AIT-319 [1] https://ably.com/docs/liveobjects/rest-api-usage --- src/data/nav/liveobjects.ts | 4 + src/pages/docs/api/rest-sdk/channels.mdx | 6 + src/pages/docs/liveobjects/rest-api-usage.mdx | 6 +- src/pages/docs/liveobjects/rest-sdk-usage.mdx | 962 ++++++++++++++++++ 4 files changed, 975 insertions(+), 3 deletions(-) create mode 100644 src/pages/docs/liveobjects/rest-sdk-usage.mdx diff --git a/src/data/nav/liveobjects.ts b/src/data/nav/liveobjects.ts index 11e38a69e2..87732773d5 100644 --- a/src/data/nav/liveobjects.ts +++ b/src/data/nav/liveobjects.ts @@ -98,6 +98,10 @@ export default { name: 'Object storage', link: '/docs/liveobjects/storage', }, + { + name: 'Using the REST SDK', + link: '/docs/liveobjects/rest-sdk-usage', + }, { name: 'Using the REST API', link: '/docs/liveobjects/rest-api-usage', diff --git a/src/pages/docs/api/rest-sdk/channels.mdx b/src/pages/docs/api/rest-sdk/channels.mdx index a368914e54..ef2fab59c0 100644 --- a/src/pages/docs/api/rest-sdk/channels.mdx +++ b/src/pages/docs/api/rest-sdk/channels.mdx @@ -82,6 +82,12 @@ Provides access to the [REST Presence](/docs/presence-occupancy/presence) object Provides access to the [PushChannel](/docs/api/realtime-sdk/push#push-channel) object for this channel which can be used to access members present on the channel, or participate in presence. + +#### object + +Provides access to the [RestObject](/docs/liveobjects/rest-sdk-usage) for this channel which can be used to read and modify LiveObjects on a channel using the REST SDK. + + ### Channel Methods #### publishPublish diff --git a/src/pages/docs/liveobjects/rest-api-usage.mdx b/src/pages/docs/liveobjects/rest-api-usage.mdx index 6a7c997148..1cf72680c1 100644 --- a/src/pages/docs/liveobjects/rest-api-usage.mdx +++ b/src/pages/docs/liveobjects/rest-api-usage.mdx @@ -632,7 +632,7 @@ There are additional operations for creating objects with client-generated IDs: { "objectId": "map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168", "mapCreateWithObjectId": { - "initialValue": "{\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}},\"age\":{\"data\":{\"number\":30}}}}", + "initialValue": "{\"semantics\":0,\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}},\"age\":{\"data\":{\"number\":30}}}}", "nonce": "random-nonce-abc123" } } @@ -677,7 +677,7 @@ For example: -d '{ "objectId": "map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168", "mapCreateWithObjectId": { - "initialValue": "{\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}},\"age\":{\"data\":{\"number\":30}}}}", + "initialValue": "{\"semantics\":0,\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}},\"age\":{\"data\":{\"number\":30}}}}", "nonce": "random-nonce-abc123" } }' @@ -697,7 +697,7 @@ Create a map and immediately link it to root in a single atomic operation: { "objectId": "map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168", "mapCreateWithObjectId": { - "initialValue": "{\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}}}}", + "initialValue": "{\"semantics\":0,\"entries\":{\"name\":{\"data\":{\"string\":\"Alice\"}}}}", "nonce": "nonce-1" } }, diff --git a/src/pages/docs/liveobjects/rest-sdk-usage.mdx b/src/pages/docs/liveobjects/rest-sdk-usage.mdx new file mode 100644 index 0000000000..0a0cf74d59 --- /dev/null +++ b/src/pages/docs/liveobjects/rest-sdk-usage.mdx @@ -0,0 +1,962 @@ +--- +title: Using the REST SDK +meta_description: "Learn how to work with Ably LiveObjects using the REST SDK" +--- + + + +LiveObjects provides a JavaScript REST SDK that enables you to directly work with objects without using a realtime connection. + +## Setup + +Create an [`Ably.Rest`](/docs/api/rest-sdk) client instance with the `LiveObjects` plugin: + + +```javascript +import { LiveObjects } from 'ably/liveobjects'; + +const rest = new Ably.Rest({ plugins: { LiveObjects }, /* other ClientOptions */ }); +``` + + +The LiveObjects REST SDK is then available on a channel via `channel.object`: + + +```javascript +const channel = rest.channels.get('my-channel'); +channel.object // LiveObjects REST SDK +``` + + +## Authentication + +Authentication is configured when instantiating the REST client. Pass an API key or use [token authentication](/docs/auth/token) via the [`ClientOptions`](/docs/api/rest-sdk#client-options). See the [REST SDK authentication](/docs/api/rest-sdk/authentication) documentation for details. + +To use LiveObjects, an API key must have at least the `object-subscribe` capability. With only this capability, clients will have read-only access, preventing them from publishing operations. + +In order to create or update objects, make sure your API key includes both `object-subscribe` and `object-publish` [capabilities](/docs/auth/capabilities) to allow full read and write access. + +## Fetch objects + +### Use `channel.object.get` + +`get(RestObjectGetParams params?): Promise` + +Reads object data from the channel. Uses the channel's root object as the entrypoint when no `objectId` is provided. Makes a request to the [`GET /channels/{channelId}/object`](/docs/liveobjects/rest-api-usage#fetch-channel-object) REST API endpoint. The return type depends on the `compact` parameter: when `compact` is `true` (default), returns a [`RestObjectGetCompactResult`](#rest-object-get-compact-result); when `compact` is `false`, returns a [`RestObjectGetFullResult`](#rest-object-get-full-result). + +| Parameter | Description | Type | +|-----------|-------------|------| +| params | An optional object containing the query parameters | [`RestObjectGetParams`](#rest-object-get-params) | + +Objects can be fetched in two formats: + +| Format | Parameter | Description | +|--------|----------------|-------------| +| **Compact** (default) | `compact: true` | Values-only representation without metadata. Ideal for reading data values. | +| **Non-compact** | `compact: false` | Full structure including object IDs and type metadata. Useful for debugging. | + +**Compact format** returns the logical structure of your data as a JSON-like value. [LiveMap](/docs/liveobjects/map) instances appear as JSON objects with their entries, and [LiveCounter](/docs/liveobjects/counter) instances appear as numbers. `bytes`-typed values are returned as `ArrayBuffer``Buffer` when using the binary protocol, or as base64-encoded strings when using the JSON protocol. `json`-typed values remain as their JSON-encoded string representation, as the client SDK cannot distinguish between a regular string and a JSON-encoded string in the compact view. + +**Non-compact format** includes additional [metadata](/docs/liveobjects/concepts/objects#metadata) for each object: +- Object IDs for each instance +- Object type metadata (map semantics, counter values) +- Complete object hierarchy + +Since each value in the non-compact format carries explicit type information, the SDK always decodes `bytes` values to `ArrayBuffer``Buffer` and `json` values to native objects or arrays. + +See [Data values reference](#data-values) for more details on value types and encoding. + +### Fetch the channel object + +Fetch the entire channel object: + + +```javascript +const data = await channel.object.get(); +``` + + +Example compact result: + + +```json +{ + "votes": { + "down": 5, + "up": 10 + } +} +``` + + +This example shows a `LiveMap` stored on the "votes" key of the channel object, which contains two `LiveCounter` instances on the "down" and "up" keys. + +Set `compact: false` to include object metadata: + + +```javascript +const data = await channel.object.get({ compact: false }); +``` + + + +```json +{ + "objectId": "root", + "map": { + "semantics": "lww", + "entries": { + "votes": { + "data": { + "objectId": "map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692", + "map": { + "semantics": "lww", + "entries": { + "down": { + "data": { + "objectId": "counter:Yj1F_aEX3T2rRkTkra7Aifmlr8PxUWSR3kO3MzxtQto@1760448653393", + "counter": { + "data": { + "number": 5 + } + } + } + }, + "up": { + "data": { + "objectId": "counter:ibxddWpDjH8R3cvXWWacfe4IVd3DxT_oqkAafhaS68s@1760448646413", + "counter": { + "data": { + "number": 10 + } + } + } + } + } + } + } + } + } + } +} +``` + + +### Fetch by path + +Return a subset of the channel object by specifying the `path` parameter. For example, to return only the `votes` `LiveMap` instance from the channel object: + + +```javascript +const data = await channel.object.get({ path: 'votes' }); +``` + + +Example result: + + +```json +{ + "down": 5, + "up": 10 +} +``` + + +### Fetch by object ID + +Fetch a specific [object instance](/docs/liveobjects/concepts/instance) by specifying its `objectId`: + + +```javascript +const data = await channel.object.get({ + objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692' +}); +``` + + +Example compact result: + + +```json +{ + "down": 5, + "up": 10 +} +``` + + +Set `compact: false` to include object metadata: + + +```javascript +const data = await channel.object.get({ + objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692', + compact: false +}); +``` + + + +```json +{ + "objectId": "map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692", + "map": { + "semantics": "lww", + "entries": { + "down": { + "data": { + "objectId": "counter:Yj1F_aEX3T2rRkTkra7Aifmlr8PxUWSR3kO3MzxtQto@1760448653393", + "counter": { + "data": { + "number": 5 + } + } + } + }, + "up": { + "data": { + "objectId": "counter:ibxddWpDjH8R3cvXWWacfe4IVd3DxT_oqkAafhaS68s@1760448646413", + "counter": { + "data": { + "number": 10 + } + } + } + } + } + } +} +``` + + +You can also specify a `path` parameter alongside `objectId`. The path is evaluated relative to the specified object instance: + + +```javascript +const data = await channel.object.get({ + objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692', + path: 'down' +}); +``` + + + +```json +5 +``` + + +## Publish operations + +### Use `channel.object.publish` + +`publish(RestObjectOperation operation): Promise` + +`publish(RestObjectOperation[] operations): Promise` + +Publishes one or more operations to modify objects on the channel. Makes a request to the [`POST /channels/{channelId}/object`](/docs/liveobjects/rest-api-usage#publishing-operations) REST API endpoint. When an array is provided, all operations are published as an atomic [batch](#batch-operations). + +| Parameter | Description | Type | +|-----------|-------------|------| +| operation | The operation or array of operations to publish | [`RestObjectOperation`](#rest-object-operation) or [`RestObjectOperation[]`](#rest-object-operation) | + +Returns a [`RestObjectPublishResult`](#rest-object-publish-result) containing the message ID and affected object IDs. + +Each operation includes: +1. A reference to an object using either `objectId` or `path`. Create operations (`mapCreate`, `counterCreate`) can omit both to create a [standalone object](#create-standalone). +2. An **operation-specific field** (`mapSet`, `counterInc`, etc.) containing the operation parameters + +Operations can target objects using `objectId`, `path`, or neither (for create operations that create [standalone objects](#create-standalone)): +- `objectId` (string): The unique identifier of the object instance to create or update +- `path` (string): The path to the object instance within the channel object + +Use dot-separated notation for paths (for example `votes.up`), relative to the channel object. An empty path `""` refers to the channel object itself. Paths can contain wildcards (`*`) to target multiple objects. + + + + + +### Available operations + +LiveObjects supports the following operations: + +| Operation | Description | +| --------- | ----------- | +| `mapSet` | Sets a key/value pair in a `LiveMap`. | +| `mapRemove` | Removes a key from a `LiveMap`. | +| `counterInc` | Increments or decrements a `LiveCounter`. | +| `mapCreate` | Creates a new `LiveMap` instance. | +| `counterCreate` | Creates a new `LiveCounter` instance. | +| `mapCreateWithObjectId` | Creates a new `LiveMap` with a [client-generated ID](#client-generated-ids). | +| `counterCreateWithObjectId` | Creates a new `LiveCounter` with a [client-generated ID](#client-generated-ids). | + +To create an object, see [Create objects](#create-objects). + +Each operation has specific required and optional fields: + +#### mapSet + + +```javascript +await channel.object.publish({ + path: 'user', + mapSet: { + key: 'username', + value: { string: 'alice' } + } +}); +``` + + +Map values can be any of the supported [data value types](#data-values), including references to other objects. + +#### mapRemove + + +```javascript +await channel.object.publish({ + path: 'user', + mapRemove: { + key: 'username' + } +}); +``` + + +#### counterInc + + +```javascript +await channel.object.publish({ + path: 'votes.up', + counterInc: { + number: 5 + } +}); +``` + + + + +#### mapCreate + +Optionally omit the `path` or `objectId` fields when creating an object with `mapCreate`. +For the object to be [reachable](/docs/liveobjects/concepts/objects#reachability) in the state tree, assign it to a key in a `LiveMap` that is reachable from the channel object. + + +```javascript +await channel.object.publish({ + path: 'posts.post1', + mapCreate: { + semantics: 'lww', + entries: { + title: { data: { string: 'LiveObjects is awesome' } }, + createdAt: { data: { number: 1745835181122 } }, + isPublished: { data: { boolean: true } } + } + } +}); +``` + + +#### counterCreate + +Optionally omit the `path` or `objectId` fields when creating an object with `counterCreate`. +For the object to be [reachable](/docs/liveobjects/concepts/objects#reachability) in the state tree, assign it to a key in a `LiveMap` that is reachable from the channel object. + + +```javascript +await channel.object.publish({ + path: 'visits', + counterCreate: { + count: 0 + } +}); +``` + + +### Update by object ID + +To perform operations on a specific object instance, provide its `objectId` in the operation: + + +```javascript +const result = await channel.object.publish({ + objectId: 'counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269', + counterInc: { + number: 1 + } +}); +``` + + +### Update by path + +Path operations target objects based on their location in the channel object. + +Paths are expressed relative to the structure of the object as defined by the [compact](#fetch-channel-object) view of the channel object. + +The following example increments the `LiveCounter` instance stored at the `up` key on the `votes` `LiveMap` object: + + +```javascript +const result = await channel.object.publish({ + path: 'votes.up', + counterInc: { + number: 1 + } +}); +``` + + +### Publish result + +The result includes the ID of the published operation message, the channel and a list of object IDs that were affected by the operation: + + +```json +{ + "messageId": "TJPWHhMTrF:0", + "channel": "my-channel", + "objectIds": ["counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269"] +} +``` + + +### Path operations + +Path operations provide flexibility when targeting objects. + +#### Path wildcards + +Use wildcards in paths to target multiple objects at once. To increment all `LiveCounter` instances in the `votes` `LiveMap` instance: + + +```javascript +const result = await channel.object.publish({ + path: 'votes.*', + counterInc: { + number: 1 + } +}); +``` + + +The result includes the IDs of each of the affected object instances: + + +```json +{ + "messageId": "0Q1w-LpA11:0", + "channel": "my-channel", + "objectIds": [ + "counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269", + "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" + ] +} +``` + + +Wildcards match exactly one level in the channel object and can appear at the end or middle of paths. For example, given the following compact view of the channel object: + + +```json +{ + "posts": { + "post1": { + "votes": { + "down": 5, + "up": 10 + } + }, + "post2": { + "votes": { + "down": 5, + "up": 10 + } + } + } +} +``` + + +The following example increments the upvote `LiveCounter` instances for all posts in the `posts` `LiveMap` instance: + + +```javascript +const result = await channel.object.publish({ + path: 'posts.*.votes.up', + counterInc: { + number: 1 + } +}); +``` + + +#### Escape special characters + +If your `LiveMap` keys contain periods, escape them with a backslash. The following example increments the upvote `LiveCounter` instance for a post with the key `post.123`: + + +```javascript +const result = await channel.object.publish({ + path: 'posts.post\\.123.votes.up', + counterInc: { + number: 1 + } +}); +``` + + +### Remove objects + +Remove an object from the channel using `mapRemove` to delete the key referencing it: + + +```javascript +await channel.object.publish({ + objectId: 'root', + mapRemove: { + key: 'posts' + } +}); +``` + + +Map keys can be removed by issuing a `mapRemove` operation targeting either an `objectId` or a `path`. + +If no other references to the object exist, it becomes unreachable and is eligible for [garbage collection](#object-reachability). + + + +## Create objects + +Use `mapCreate` and `counterCreate` operations to create new LiveObjects [instances](/docs/liveobjects/concepts/instance). + +### Create objects with paths + +The simplest way to create an object is to specify a `path` where it should be created. The server automatically creates the object and assigns it to that path in a single atomic operation. + +The following example creates a new `LiveMap` instance and assigns it to the `posts` `LiveMap` instance on the channel object under the key `post1`: + + +```javascript +const result = await channel.object.publish({ + path: 'posts.post1', + mapCreate: { + semantics: 'lww', + entries: { + title: { data: { string: 'LiveObjects is awesome' } }, + createdAt: { data: { number: 1745835181122 } }, + isPublished: { data: { boolean: true } } + } + } +}); +``` + + +When using `path` with a create operation, the server constructs two operations published as a [batch](#batch-operations): + +1. A `mapCreate` or `counterCreate` operation to create the new object +2. A `mapSet` operation to assign the new object to the parent `LiveMap` at the specified path + +This ensures the new object is immediately [reachable](#object-reachability) from the root. + +The result will include the object IDs of all objects affected by the resulting set of operations. +The newly created object's ID will be the first item in the list: + + +```json +{ + "messageId": "mkfjWU2jju:0", + "channel": "my-channel", + "objectIds": [ + "map:cRCKx-eev7Tl66jGfl1SkZh_uEMo6F5jyV0B7mUn4Zs@1745835549101", + "map:a_oQqPYUGxi95_Cn0pWcsoeBlHZZtVW5xKIw0hnJCZs@1745835547258" + ] +} +``` + + +### Create standalone objects + +Create objects without immediately assigning them by omitting both `objectId` and `path` from the create operation. + + +```javascript +const result = await channel.object.publish({ + mapCreate: { + semantics: 'lww', + entries: { + name: { data: { string: 'Alice' } } + } + } +}); +``` + + +The result includes the generated object ID: + + +```json +{ + "messageId": "TJPWHhMTrF:0", + "channel": "my-channel", + "objectIds": ["map:abc123def456...@1745835549101"] +} +``` + + + + +### Client-generated object IDs + +Client-generated object IDs enable atomic batch operations with cross-references between newly created objects. See the [REST API documentation](/docs/liveobjects/rest-api-usage#client-generated-ids) for details on how to generate object IDs. + +#### Publish with client-generated IDs + +Use `mapCreateWithObjectId` or `counterCreateWithObjectId` to create an object with a pre-computed ID: + + +```javascript +const result = await channel.object.publish({ + objectId: 'map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168', + mapCreateWithObjectId: { + initialValue: '{"semantics":0,"entries":{"name":{"data":{"string":"Alice"}},"age":{"data":{"number":30}}}}', + nonce: 'random-nonce-abc123' + } +}); +``` + + +#### Atomic batch with cross-references + +Create a map and immediately link it to root in a single atomic batch: + + +```javascript +const result = await channel.object.publish([ + { + objectId: 'map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168', + mapCreateWithObjectId: { + initialValue: '{"semantics":0,"entries":{"name":{"data":{"string":"Alice"}}}}', + nonce: 'nonce-1' + } + }, + { + objectId: 'root', + mapSet: { + key: 'alice', + value: { objectId: 'map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168' } + } + } +]); +``` + + +Both operations execute atomically. The second operation references the object created in the first because you pre-computed the ID. + + + +## Cyclic references + +For both the full object and the compact formats, cyclic references in the channel object are included as a reference to the object ID rather than including the same object instance in the result more than once. + +For example, if you create a cycle in the channel object by adding a reference to the channel object in the `votes` `LiveMap` instance with the following operation: + + +```javascript +await channel.object.publish({ + objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692', + mapSet: { + key: 'myRoot', + value: { objectId: 'root' } + } +}); +``` + + +The result will handle the cyclic reference by including the `myRoot` key as a reference to the object ID of the channel object: + + +```javascript +const data = await channel.object.get(); +``` + + + +```json +{ + "votes": { + "down": 5, + "up": 10, + "myRoot": { + "objectId": "root" + } + } +} +``` + + +## Object reachability and garbage collection + +Objects that are not reachable from the channel object are automatically garbage collected. See the [REST API documentation](/docs/liveobjects/rest-api-usage#object-reachability) and [reachability and object lifecycle](/docs/liveobjects/concepts/objects#reachability) for details. + +## Batch operations + +Group multiple operations into a single call by passing an array of operations to `publish()`. All operations are published as a single message and processed as a single atomic unit. Learn more about [batch operations](/docs/liveobjects/batch). + +The following example increments two distinct `LiveCounter` instances in a single batch operation: + + +```javascript +await channel.object.publish([ + { + objectId: 'counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269', + counterInc: { + number: 1 + } + }, + { + objectId: 'counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669', + counterInc: { + number: 1 + } + } +]); +``` + + +## Idempotent operations + +Publish operations idempotently by specifying an `id` for the operation, using the same approach as [idempotent message publishing](/docs/api/liveobjects-rest#idempotent-publish): + + +```javascript +await channel.object.publish({ + id: 'my-idempotency-key', + objectId: 'counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269', + counterInc: { + number: 1 + } +}); +``` + + +For batch operations, use the format `:` where the index is the zero-based index of the operation in the array: + + +```javascript +await channel.object.publish([ + { + id: 'my-idempotency-key:0', + objectId: 'counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269', + counterInc: { + number: 1 + } + }, + { + id: 'my-idempotency-key:1', + objectId: 'counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669', + counterInc: { + number: 1 + } + } +]); +``` + + +## Data values reference + +When working with objects via the REST SDK, [primitive types](/docs/liveobjects/concepts/objects#primitive-types) and [object references](/docs/liveobjects/concepts/objects#composability) are represented as typed value objects. + +The key in the value object indicates the type of the value. Examples of data value formats: + + +```javascript +{ number: 42 } +{ string: 'LiveObjects is awesome' } +{ boolean: true } +{ bytes: new Uint8Array([76, 105, 118, 101]) } +{ objectId: 'counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669' } +{ json: { someKey: 'someValue' } } +``` + + + + +## Related types + +### RestObjectGetParams + +Parameters for the [`get()`](#get) method: + +| Property | Description | Type | +|----------|-------------|------| +| objectId | The unique identifier of the [object instance](/docs/liveobjects/concepts/instance) to fetch. If omitted, fetches from the channel's root object | `String` (optional) | +| path | A dot-separated path to return a subset of the object. Evaluated relative to the root or the specified `objectId` | `String` (optional) | +| compact | When `true` (default), returns a values-only representation. When `false`, includes object IDs and type metadata | `Boolean` (optional) | + +### RestObjectOperation + +An operation passed to the [`publish()`](#publish) method. Each operation contains an optional `id` for [idempotent publishing](#idempotent-operations), an optional target (`objectId` or `path`), and exactly one operation-specific field containing the operation parameters: + +| Property | Description | Type | +|----------|-------------|------| +| id | Identifier for [idempotent publishing](#idempotent-operations) | `String` (optional) | +| objectId | The unique identifier of the [object instance](/docs/liveobjects/concepts/instance) to target | `String` (optional) | +| path | The dot-separated path to the object instance within the channel object. Evaluated relative to the root | `String` (optional) | +| mapSet | Parameters for setting a key to a value in a `LiveMap` | [`RestObjectOperationMapSet`](#rest-object-operation-map-set) (optional) | +| mapRemove | Parameters for removing a key from a `LiveMap` | [`RestObjectOperationMapRemove`](#rest-object-operation-map-remove) (optional) | +| counterInc | Parameters for incrementing a `LiveCounter` | [`RestObjectOperationCounterInc`](#rest-object-operation-counter-inc) (optional) | +| mapCreate | Parameters for creating a new `LiveMap` | [`RestObjectOperationMapCreate`](#rest-object-operation-map-create) (optional) | +| counterCreate | Parameters for creating a new `LiveCounter` | [`RestObjectOperationCounterCreate`](#rest-object-operation-counter-create) (optional) | +| mapCreateWithObjectId | Parameters for creating a new `LiveMap` with a [client-generated ID](#client-generated-ids) | [`RestObjectOperationMapCreateWithObjectId`](#rest-object-operation-map-create-with-object-id) (optional) | +| counterCreateWithObjectId | Parameters for creating a new `LiveCounter` with a [client-generated ID](#client-generated-ids) | [`RestObjectOperationCounterCreateWithObjectId`](#rest-object-operation-counter-create-with-object-id) (optional) | + +### RestObjectOperationMapSet + +| Property | Description | Type | +|----------|-------------|------| +| key | The key to set | `String` | +| value | The value to assign to the key | [`PublishObjectData`](#publish-object-data) | + +### RestObjectOperationMapRemove + +| Property | Description | Type | +|----------|-------------|------| +| key | The key to remove | `String` | + +### RestObjectOperationCounterInc + +| Property | Description | Type | +|----------|-------------|------| +| number | The amount to increment by. Use a negative value to decrement | `Number` | + +### RestObjectOperationMapCreate + +| Property | Description | Type | +|----------|-------------|------| +| semantics | The conflict-resolution semantics for the map. One of: `'lww'` | `String` | +| entries | Initial key-value pairs, keyed by string | `Record` | + +### RestObjectOperationCounterCreate + +| Property | Description | Type | +|----------|-------------|------| +| count | The initial value of the counter | `Number` | + +### RestObjectOperationMapCreateWithObjectId + +| Property | Description | Type | +|----------|-------------|------| +| initialValue | JSON-encoded string of the [`mapCreate`](#rest-object-operation-map-create) object | `String` | +| nonce | Random string used to generate the object ID | `String` | + +### RestObjectOperationCounterCreateWithObjectId + +| Property | Description | Type | +|----------|-------------|------| +| initialValue | JSON-encoded string of the [`counterCreate`](#rest-object-operation-counter-create) object | `String` | +| nonce | Random string used to generate the object ID | `String` | + +### RestObjectGetCompactResult + +Returned by [`get()`](#get) when `compact` is `true` (default). The result is a recursive type: `null`, `boolean`, `number`, `string`, binary data (`ArrayBuffer``Buffer` when using the binary protocol, or a base64-encoded string when using the JSON protocol), or a JSON object whose values are themselves `RestObjectGetCompactResult`. [LiveMap](/docs/liveobjects/map) instances appear as JSON objects with their entries, and [LiveCounter](/docs/liveobjects/counter) instances appear as numbers. + +### RestObjectGetFullResult + +Returned by [`get()`](#get) when `compact` is `false`. Can be a [`RestLiveObject`](#rest-live-object) (full object with metadata) or a [`RestObjectData`](#rest-object-data) (leaf value when the path resolves to a primitive entry in a map). + +### RestLiveObject + +A full object structure including object IDs and type metadata. Can be a [`RestLiveMap`](#rest-live-map), [`RestLiveCounter`](#rest-live-counter), or a generic object with an `objectId` property. + +#### RestLiveMap + +| Property | Description | Type | +|----------|-------------|------| +| objectId | The ID of the map object | `String` | +| map | The map data | [`RestLiveMapValue`](#rest-live-map-value) | + +#### RestLiveMapValue + +| Property | Description | Type | +|----------|-------------|------| +| semantics | The conflict-resolution semantics. One of: `'lww'` | `String` | +| entries | The map entries, indexed by key. Each entry is either a [`RestObjectDataMapEntry`](#rest-object-data-map-entry) (leaf value) or a [`RestLiveObjectMapEntry`](#rest-live-object-map-entry) (nested object) | `Record` | + +### RestObjectDataMapEntry + +| Property | Description | Type | +|----------|-------------|------| +| data | The value for this entry | [`RestObjectData`](#rest-object-data) | + +#### RestLiveObjectMapEntry + +| Property | Description | Type | +|----------|-------------|------| +| data | A nested object | [`RestLiveObject`](#rest-live-object) | + +#### RestLiveCounter + +| Property | Description | Type | +|----------|-------------|------| +| objectId | The ID of the counter object | `String` | +| counter | The counter data | [`RestLiveCounterValue`](#rest-live-counter-value) | + +#### RestLiveCounterValue + +| Property | Description | Type | +|----------|-------------|------| +| data | Holds the counter value | `{ number: Number }` | + +### RestObjectData + +Represents a leaf data value for an object. Either a primitive value or a reference to another object. Exactly one property is set, indicating the type: + +| Property | Description | Type | +|----------|-------------|------| +| string | A string value | `String` | +| number | A numeric value | `Number` | +| boolean | A boolean value | `Boolean` | +| bytes | A binary value | `ArrayBuffer``Buffer` | +| json | A JSON value (array or object) | `Array` / `Object` | +| objectId | A reference to another object by its ID | `String` | + +### PublishObjectData + +Same properties as [`RestObjectData`](#rest-object-data). Used when publishing operations via [`publish()`](#publish). Exactly one property must be set. + +### RestObjectPublishResult + +Result returned by the [`publish()`](#publish) method: + +| Property | Description | Type | +|----------|-------------|------| +| messageId | The ID of the message containing the published operations | `String` | +| channel | The name of the channel the operations were published to | `String` | +| objectIds | Array of object IDs affected by the operations. May include multiple IDs for wildcard paths and batch operations | `String[]` | From 6fda2c31a165d73af4b3fd45731ea4976ac404e1 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 17 Mar 2026 15:26:31 +0000 Subject: [PATCH 2/3] fixup: address feedback --- src/pages/docs/liveobjects/rest-api-usage.mdx | 20 ++-- src/pages/docs/liveobjects/rest-sdk-usage.mdx | 105 +++++------------- 2 files changed, 39 insertions(+), 86 deletions(-) diff --git a/src/pages/docs/liveobjects/rest-api-usage.mdx b/src/pages/docs/liveobjects/rest-api-usage.mdx index 1cf72680c1..b99a0004ed 100644 --- a/src/pages/docs/liveobjects/rest-api-usage.mdx +++ b/src/pages/docs/liveobjects/rest-api-usage.mdx @@ -15,9 +15,9 @@ LiveObjects provides a comprehensive REST API that enables you to directly work View the REST API [authentication](/docs/api/liveobjects-rest#authentication) documentation for details on how to authenticate your requests. -To use LiveObjects, an API key must have at least the `object-subscribe` capability. With only this capability, clients will have read-only access, preventing them from publishing operations. +To read objects on a channel, an API key must have the `object-subscribe` [capability](/docs/auth/capabilities). With only this capability, clients have read-only access, preventing them from publishing operations. -In order to create or update objects, make sure your API key includes both `object-subscribe` and `object-publish` [capabilities](/docs/auth/capabilities) to allow full read and write access. +To create or update objects, the API key must have the `object-publish` capability. Include both `object-subscribe` and `object-publish` for full read and write access. ## Fetching objects @@ -26,11 +26,11 @@ The REST API returns objects in two formats: | Format | Query parameter | Description | |--------|----------------|-------------| | **Compact** (default) | None | Values-only representation without metadata. Ideal for reading data values. | -| **Non-compact** | `compact=false` | Full structure including object IDs and type metadata. Useful for debugging. | +| **Full** | `compact=false` | Full structure including object IDs and type metadata. Useful for debugging. | **Compact format** returns the logical structure of your data as a JSON object. [LiveMap](/docs/liveobjects/map) instances appear as JSON objects with their entries, and [LiveCounter](/docs/liveobjects/counter) instances appear as numbers. -**Non-compact format** includes additional [metadata](/docs/liveobjects/concepts/objects#metadata) for each object: +**Full format** includes additional [metadata](/docs/liveobjects/concepts/objects#metadata) for each object: - Object IDs for each instance - Object type metadata (map semantics, counter values) - Complete object hierarchy @@ -238,7 +238,7 @@ When using path operations, the server resolves object IDs at the time it receiv ### Available operations @@ -546,7 +546,7 @@ When using `path` with a create operation, the server constructs two operations 1. A `mapCreate` or `counterCreate` operation to create the new object 2. A `mapSet` operation to assign the new object to the parent `LiveMap` at the specified path -This ensures the new object is immediately [reachable](#object-reachability) from the root. +This ensures the new object is immediately [reachable](#object-reachability) from the channel object. The response will include the object IDs of all objects affected by the resulting set of operations. The newly created object's ID will be the first item in the list: @@ -686,7 +686,7 @@ For example: #### Atomic batch with cross-references -Create a map and immediately link it to root in a single atomic operation: +Create a map and immediately assign it to the channel object in a single atomic operation: ```shell @@ -733,14 +733,14 @@ For example, if you create a cycle in the channel object by adding a reference t -d '{ "objectId": "map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692", "mapSet": { - "key": "myRoot", + "key": "myObject", "value": {"objectId": "root"} } }' ``` -The response will handle the cyclic reference by including the `myRoot` key in the response as a reference to the object ID of the channel object: +The response will handle the cyclic reference by including the `myObject` key in the response as a reference to the object ID of the channel object: ```shell @@ -755,7 +755,7 @@ The response will handle the cyclic reference by including the `myRoot` key in t "votes": { "down": 5, "up": 10, - "myRoot": { + "myObject": { "objectId": "root" } } diff --git a/src/pages/docs/liveobjects/rest-sdk-usage.mdx b/src/pages/docs/liveobjects/rest-sdk-usage.mdx index 0a0cf74d59..a8b926d381 100644 --- a/src/pages/docs/liveobjects/rest-sdk-usage.mdx +++ b/src/pages/docs/liveobjects/rest-sdk-usage.mdx @@ -36,9 +36,9 @@ channel.object // LiveObjects REST SDK Authentication is configured when instantiating the REST client. Pass an API key or use [token authentication](/docs/auth/token) via the [`ClientOptions`](/docs/api/rest-sdk#client-options). See the [REST SDK authentication](/docs/api/rest-sdk/authentication) documentation for details. -To use LiveObjects, an API key must have at least the `object-subscribe` capability. With only this capability, clients will have read-only access, preventing them from publishing operations. +To read objects on a channel, an API key must have the `object-subscribe` [capability](/docs/auth/capabilities). With only this capability, clients have read-only access, preventing them from publishing operations. -In order to create or update objects, make sure your API key includes both `object-subscribe` and `object-publish` [capabilities](/docs/auth/capabilities) to allow full read and write access. +To create or update objects, the API key must have the `object-publish` capability. Include both `object-subscribe` and `object-publish` for full read and write access. ## Fetch objects @@ -46,7 +46,7 @@ In order to create or update objects, make sure your API key includes both `obje `get(RestObjectGetParams params?): Promise` -Reads object data from the channel. Uses the channel's root object as the entrypoint when no `objectId` is provided. Makes a request to the [`GET /channels/{channelId}/object`](/docs/liveobjects/rest-api-usage#fetch-channel-object) REST API endpoint. The return type depends on the `compact` parameter: when `compact` is `true` (default), returns a [`RestObjectGetCompactResult`](#rest-object-get-compact-result); when `compact` is `false`, returns a [`RestObjectGetFullResult`](#rest-object-get-full-result). +Reads object data from the channel. If no `objectId` is provided then the entire channel object is returned. Makes a request to the [`GET /channels/{channelId}/object`](/docs/liveobjects/rest-api-usage#fetch-channel-object) REST API endpoint. The return type depends on the `compact` parameter: when `compact` is `true` (default), returns a [`RestObjectGetCompactResult`](#rest-object-get-compact-result); when `compact` is `false`, returns a [`RestObjectGetFullResult`](#rest-object-get-full-result). | Parameter | Description | Type | |-----------|-------------|------| @@ -57,16 +57,16 @@ Objects can be fetched in two formats: | Format | Parameter | Description | |--------|----------------|-------------| | **Compact** (default) | `compact: true` | Values-only representation without metadata. Ideal for reading data values. | -| **Non-compact** | `compact: false` | Full structure including object IDs and type metadata. Useful for debugging. | +| **Full** | `compact: false` | Full structure including object IDs and type metadata. Useful for debugging. | -**Compact format** returns the logical structure of your data as a JSON-like value. [LiveMap](/docs/liveobjects/map) instances appear as JSON objects with their entries, and [LiveCounter](/docs/liveobjects/counter) instances appear as numbers. `bytes`-typed values are returned as `ArrayBuffer``Buffer` when using the binary protocol, or as base64-encoded strings when using the JSON protocol. `json`-typed values remain as their JSON-encoded string representation, as the client SDK cannot distinguish between a regular string and a JSON-encoded string in the compact view. +**Compact format** returns the logical structure of your data as a JSON-like value. [LiveMap](/docs/liveobjects/map) instances appear as JSON objects with their entries, and [LiveCounter](/docs/liveobjects/counter) instances appear as numbers. `bytes`-typed values are returned as `ArrayBuffer``Buffer` when using the binary protocol, or as base64-encoded strings when using the JSON protocol. `json`-typed values remain as JSON-encoded strings. -**Non-compact format** includes additional [metadata](/docs/liveobjects/concepts/objects#metadata) for each object: +**Full format** includes additional [metadata](/docs/liveobjects/concepts/objects#metadata) for each object: - Object IDs for each instance - Object type metadata (map semantics, counter values) - Complete object hierarchy -Since each value in the non-compact format carries explicit type information, the SDK always decodes `bytes` values to `ArrayBuffer``Buffer` and `json` values to native objects or arrays. +Since each value in the full format carries explicit type information, the SDK always decodes `bytes` values to `ArrayBuffer``Buffer` and `json` values to native objects or arrays. See [Data values reference](#data-values) for more details on value types and encoding. @@ -268,10 +268,10 @@ Publishes one or more operations to modify objects on the channel. Makes a reque Returns a [`RestObjectPublishResult`](#rest-object-publish-result) containing the message ID and affected object IDs. Each operation includes: -1. A reference to an object using either `objectId` or `path`. Create operations (`mapCreate`, `counterCreate`) can omit both to create a [standalone object](#create-standalone). +1. A reference to an object using either `objectId` or `path`. 2. An **operation-specific field** (`mapSet`, `counterInc`, etc.) containing the operation parameters -Operations can target objects using `objectId`, `path`, or neither (for create operations that create [standalone objects](#create-standalone)): +Operations can target objects using `objectId` or `path`: - `objectId` (string): The unique identifier of the object instance to create or update - `path` (string): The path to the object instance within the channel object @@ -282,7 +282,7 @@ When using path operations, the server resolves object IDs at the time it receiv ### Available operations @@ -573,7 +573,7 @@ When using `path` with a create operation, the server constructs two operations 1. A `mapCreate` or `counterCreate` operation to create the new object 2. A `mapSet` operation to assign the new object to the parent `LiveMap` at the specified path -This ensures the new object is immediately [reachable](#object-reachability) from the root. +This ensures the new object is immediately [reachable](#object-reachability) from the channel object. The result will include the object IDs of all objects affected by the resulting set of operations. The newly created object's ID will be the first item in the list: @@ -591,62 +591,15 @@ The newly created object's ID will be the first item in the list: ``` -### Create standalone objects - -Create objects without immediately assigning them by omitting both `objectId` and `path` from the create operation. - - -```javascript -const result = await channel.object.publish({ - mapCreate: { - semantics: 'lww', - entries: { - name: { data: { string: 'Alice' } } - } - } -}); -``` - +### Client-generated object IDs -The result includes the generated object ID: - - -```json -{ - "messageId": "TJPWHhMTrF:0", - "channel": "my-channel", - "objectIds": ["map:abc123def456...@1745835549101"] -} -``` - +Client-generated object IDs enable atomic batch operations with cross-references between newly created objects. Use `mapCreateWithObjectId` or `counterCreateWithObjectId` to create an object with a pre-computed ID. See the [REST API documentation](/docs/liveobjects/rest-api-usage#client-generated-ids) for details on how to generate object IDs. -### Client-generated object IDs - -Client-generated object IDs enable atomic batch operations with cross-references between newly created objects. See the [REST API documentation](/docs/liveobjects/rest-api-usage#client-generated-ids) for details on how to generate object IDs. - -#### Publish with client-generated IDs - -Use `mapCreateWithObjectId` or `counterCreateWithObjectId` to create an object with a pre-computed ID: - - -```javascript -const result = await channel.object.publish({ - objectId: 'map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168', - mapCreateWithObjectId: { - initialValue: '{"semantics":0,"entries":{"name":{"data":{"string":"Alice"}},"age":{"data":{"number":30}}}}', - nonce: 'random-nonce-abc123' - } -}); -``` - - -#### Atomic batch with cross-references - -Create a map and immediately link it to root in a single atomic batch: +The following example creates a map and immediately assigns it to the channel object in a single atomic batch: ```javascript @@ -672,7 +625,7 @@ const result = await channel.object.publish([ Both operations execute atomically. The second operation references the object created in the first because you pre-computed the ID. ## Cyclic references @@ -686,14 +639,14 @@ For example, if you create a cycle in the channel object by adding a reference t await channel.object.publish({ objectId: 'map:qZXBk8xaGqf4kwLSlR7Tj0Eqhd48WDFfb3gTAbj194k@1760448597692', mapSet: { - key: 'myRoot', + key: 'myObject', value: { objectId: 'root' } } }); ``` -The result will handle the cyclic reference by including the `myRoot` key as a reference to the object ID of the channel object: +The result will handle the cyclic reference by including the `myObject` key as a reference to the object ID of the channel object: ```javascript @@ -707,7 +660,7 @@ const data = await channel.object.get(); "votes": { "down": 5, "up": 10, - "myRoot": { + "myObject": { "objectId": "root" } } @@ -717,7 +670,7 @@ const data = await channel.object.get(); ## Object reachability and garbage collection -Objects that are not reachable from the channel object are automatically garbage collected. See the [REST API documentation](/docs/liveobjects/rest-api-usage#object-reachability) and [reachability and object lifecycle](/docs/liveobjects/concepts/objects#reachability) for details. +Objects that are not reachable from the channel object will be automatically garbage collected. See the [REST API documentation](/docs/liveobjects/rest-api-usage#object-reachability) and [reachability and object lifecycle](/docs/liveobjects/concepts/objects#reachability) for details. ## Batch operations @@ -805,7 +758,7 @@ Over the wire, `bytes` values are base64-encoded and `json` values are JSON-stri When fetching with `compact: false`, the SDK decodes `bytes` to `ArrayBuffer``Buffer` and `json` to native objects or arrays. -When fetching with `compact: true` (default), values lack type metadata so the SDK cannot distinguish a regular string from a JSON-encoded string. `json`-typed values remain as JSON-encoded strings and are not parsed into objects or arrays. `bytes`-typed values are returned as `ArrayBuffer``Buffer` when using the binary protocol, or as base64-encoded strings when using the JSON protocol. +When fetching with `compact: true` (default), the SDK returns `bytes`-typed values as `ArrayBuffer``Buffer` when using the binary protocol, or as base64-encoded strings when using the JSON protocol. `json`-typed values remain as JSON-encoded strings. ## Related types @@ -816,8 +769,8 @@ Parameters for the [`get()`](#get) method: | Property | Description | Type | |----------|-------------|------| -| objectId | The unique identifier of the [object instance](/docs/liveobjects/concepts/instance) to fetch. If omitted, fetches from the channel's root object | `String` (optional) | -| path | A dot-separated path to return a subset of the object. Evaluated relative to the root or the specified `objectId` | `String` (optional) | +| objectId | The unique identifier of the [object instance](/docs/liveobjects/concepts/instance) to fetch. If omitted, fetches from the channel object | `String` (optional) | +| path | A dot-separated path to return a subset of the object. Evaluated relative to the channel object or the specified `objectId` | `String` (optional) | | compact | When `true` (default), returns a values-only representation. When `false`, includes object IDs and type metadata | `Boolean` (optional) | ### RestObjectOperation @@ -828,7 +781,7 @@ An operation passed to the [`publish()`](#publish) method. Each operation contai |----------|-------------|------| | id | Identifier for [idempotent publishing](#idempotent-operations) | `String` (optional) | | objectId | The unique identifier of the [object instance](/docs/liveobjects/concepts/instance) to target | `String` (optional) | -| path | The dot-separated path to the object instance within the channel object. Evaluated relative to the root | `String` (optional) | +| path | The dot-separated path to the object instance within the channel object. Evaluated relative to the channel object | `String` (optional) | | mapSet | Parameters for setting a key to a value in a `LiveMap` | [`RestObjectOperationMapSet`](#rest-object-operation-map-set) (optional) | | mapRemove | Parameters for removing a key from a `LiveMap` | [`RestObjectOperationMapRemove`](#rest-object-operation-map-remove) (optional) | | counterInc | Parameters for incrementing a `LiveCounter` | [`RestObjectOperationCounterInc`](#rest-object-operation-counter-inc) (optional) | @@ -895,14 +848,14 @@ Returned by [`get()`](#get) when `compact` is `false`. Can be a [`RestLiveObject A full object structure including object IDs and type metadata. Can be a [`RestLiveMap`](#rest-live-map), [`RestLiveCounter`](#rest-live-counter), or a generic object with an `objectId` property. -#### RestLiveMap +### RestLiveMap | Property | Description | Type | |----------|-------------|------| | objectId | The ID of the map object | `String` | | map | The map data | [`RestLiveMapValue`](#rest-live-map-value) | -#### RestLiveMapValue +### RestLiveMapValue | Property | Description | Type | |----------|-------------|------| @@ -915,20 +868,20 @@ A full object structure including object IDs and type metadata. Can be a [`RestL |----------|-------------|------| | data | The value for this entry | [`RestObjectData`](#rest-object-data) | -#### RestLiveObjectMapEntry +### RestLiveObjectMapEntry | Property | Description | Type | |----------|-------------|------| | data | A nested object | [`RestLiveObject`](#rest-live-object) | -#### RestLiveCounter +### RestLiveCounter | Property | Description | Type | |----------|-------------|------| | objectId | The ID of the counter object | `String` | | counter | The counter data | [`RestLiveCounterValue`](#rest-live-counter-value) | -#### RestLiveCounterValue +### RestLiveCounterValue | Property | Description | Type | |----------|-------------|------| From 65f2a71faac2ff284da3960bd73cc1b5f85214b3 Mon Sep 17 00:00:00 2001 From: Andrew Bulat Date: Tue, 17 Mar 2026 16:35:32 +0000 Subject: [PATCH 3/3] fixup: Document generateObjectId() helper for client-generated object IDs As suggested in [1] the REST SDK now exposes a generateObjectId() method that handles the ID generation internally, removing the need for developers to manually hash initial values and nonces. Update the client-generated IDs section to use this helper and add the related types. See ably-js implementation in [2] for reference. [1] https://github.com/ably/docs/pull/3258#issuecomment-4053558952 [2] https://github.com/ably/ably-js/pull/2109/commits/2d973c6cf7b2db82ed79d3544a2323c053ddac38 --- src/pages/docs/liveobjects/rest-sdk-usage.mdx | 66 +++++++++++++++---- 1 file changed, 53 insertions(+), 13 deletions(-) diff --git a/src/pages/docs/liveobjects/rest-sdk-usage.mdx b/src/pages/docs/liveobjects/rest-sdk-usage.mdx index a8b926d381..fd5ece3268 100644 --- a/src/pages/docs/liveobjects/rest-sdk-usage.mdx +++ b/src/pages/docs/liveobjects/rest-sdk-usage.mdx @@ -593,36 +593,50 @@ The newly created object's ID will be the first item in the list: ### Client-generated object IDs -Client-generated object IDs enable atomic batch operations with cross-references between newly created objects. Use `mapCreateWithObjectId` or `counterCreateWithObjectId` to create an object with a pre-computed ID. See the [REST API documentation](/docs/liveobjects/rest-api-usage#client-generated-ids) for details on how to generate object IDs. +Client-generated object IDs enable atomic batch operations with cross-references between newly created objects. Use `channel.object.generateObjectId` to generate an object ID, then use `mapCreateWithObjectId` or `counterCreateWithObjectId` to create the object with that ID. See the [REST API documentation](/docs/liveobjects/rest-api-usage#client-generated-ids) for details on the ID generation algorithm. - +#### Use `channel.object.generateObjectId` + +`generateObjectId(RestObjectOperationMapCreateBody | RestObjectOperationCounterCreateBody createBody): Promise` -The following example creates a map and immediately assigns it to the channel object in a single atomic batch: +Generates an object ID for a create operation. Pass a [`RestObjectOperationMapCreateBody`](#rest-object-operation-map-create-body) or [`RestObjectOperationCounterCreateBody`](#rest-object-operation-counter-create-body) to specify the object type and initial value. Returns a [`RestObjectGenerateIdResult`](#rest-object-generate-id-result) containing the generated `objectId`, `nonce`, and `initialValue` needed to construct a `mapCreateWithObjectId` or `counterCreateWithObjectId` operation. + +The following example generates an object ID for a new map, then creates and assigns it to the channel object in a single atomic batch: ```javascript +// Generate an object ID for a new map +const { objectId, nonce, initialValue } = await channel.object.generateObjectId({ + mapCreate: { + semantics: 'lww', + entries: { + name: { data: { string: 'Alice' } } + } + } +}); + +// Create the map and assign it to the channel object atomically const result = await channel.object.publish([ { - objectId: 'map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168', - mapCreateWithObjectId: { - initialValue: '{"semantics":0,"entries":{"name":{"data":{"string":"Alice"}}}}', - nonce: 'nonce-1' - } + objectId, + mapCreateWithObjectId: { initialValue, nonce } }, { objectId: 'root', mapSet: { key: 'alice', - value: { objectId: 'map:Qj2kkvprTybCY5mkNMcm31hhNKZCDWqcz45LjYvCABs@1769079911168' } + value: { objectId } } } ]); ``` -Both operations execute atomically. The second operation references the object created in the first because you pre-computed the ID. +Both operations execute atomically. The second operation references the object created in the first because the ID was pre-computed with `generateObjectId`. + +