diff --git a/content/liveobjects/batch.textile b/content/liveobjects/batch.textile
deleted file mode 100644
index 209c49032e..0000000000
--- a/content/liveobjects/batch.textile
+++ /dev/null
@@ -1,153 +0,0 @@
----
-title: Batch operations
-meta_description: "Group multiple objects operations into a single channel message to apply grouped operations atomically and improve performance."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-The Batching API in LiveObjects enables multiple updates to be grouped into a single channel message and applied atomically. It ensures that all operations in a batch either succeed together or are discarded entirely. Batching operations is essential when multiple related updates to channel objects must be applied as a single atomic unit, for example, when application logic depends on multiple objects being updated simultaneously. Without batching, if one operation succeeds while another fails, your application state could become inconsistent.
-
-Note that this differs from "Message batching":/docs/messages/batch, the native Pub/Sub messages feature. The LiveObjects Batching API is a separate API specifically designed to enable you to group object operations into a single channel message, ensuring that the Ably system guarantees the atomicity of the applied changes.
-
-h2(#create). Create batch context
-
-blang[javascript].
-
- To batch object operations together, use the @channel.objects.batch()@ method. This method accepts a callback function, which is provided with a batch context object. The batch context object provides a synchronous API to work with objects on a channel that stores operations inside the batch instead of applying them immediately.
-
- Using the batch context ensures that operations are grouped and sent in a single channel message after the batch callback function has run. This guarantees that all changes are applied atomically by both the server and all clients.
-
-
-
-blang[javascript].
-
- ```[javascript]
- await channel.objects.batch((ctx) => {
- const root = ctx.getRoot();
-
- root.set('foo', 'bar');
- root.set('baz', 42);
-
- const counter = root.get('counter');
- counter.increment(5);
-
- // Batched operations are sent to the Ably system when the batch callback has run.
- });
- ```
-
-If an error occurs within the batch, all operations are discarded, preventing partial updates and ensuring atomicity.
-
-h3(#context). Batch context object
-
-blang[javascript].
-
- The batch context provides a synchronous API for objects operations inside the batch callback. It mirrors the asynchronous API found on @channel.objects@, including "LiveCounter":/docs/liveobjects/counter and "LiveMap":/docs/liveobjects/map.
-
- To access the batch API, call @BatchContext.getRoot()@, which synchronously returns a wrapper around the "root":/docs/liveobjects/concepts/objects#root-object object instance. This wrapper enables you to access and modify objects within a batch.
-
-
-
-blang[javascript].
-
- ```[javascript]
- await channel.objects.batch((ctx) => {
- // Note: .getRoot() call on a batch context is synchronous.
- // The returned root object is a special wrapper around a regular LiveMap instance,
- // providing a synchronous mutation API.
- const root = ctx.getRoot();
-
- // Mutation operations like LiveMap.set and LiveCounter.increment
- // are synchronous inside the batch and queue operations instead of applying them immediately.
- root.set('foo', 'bar');
- root.remove('baz');
-
- // Access other objects through the root object from the BatchContext.getRoot() method.
- const counter = root.get('counter');
- counter.increment(5);
- });
- ```
-
-You cannot create new objects using the batch context. If you need to create new objects and add them to the channel as part of an atomic batch operation to guarantee atomicity, you must first create them using the regular @channel.objects@ API. Once the objects have been created, you can then assign them to the object tree inside a batch function.
-
-blang[javascript].
-
- ```[javascript]
- // First, create new objects outside the batch context
- const counter = await channel.objects.createCounter();
- const map = await channel.objects.createMap();
-
- // Then, use a batch to assign them atomically to the channel objects
- await channel.objects.batch((ctx) => {
- const root = ctx.getRoot();
- root.set('counter', counter);
- root.set('map', map);
- });
- ```
-
-h3(#use-cases). When to batch operations
-
-Usually, you don't need to use batching for objects operations. It is only useful in situations where a group of operations must be applied together to maintain consistency in application state, or when there are multiple mutation operations that you might want to apply at the same time to improve the UI experience.
-
-For example, in a task dashboard application, you might want to remove all tasks on a board in a single operation to prevent excessive UI updates that the user would otherwise experience.
-
-blang[javascript].
-
- ```[javascript]
- await channel.objects.batch((ctx) => {
- const root = ctx.getRoot();
- const tasks = root.get('tasks');
-
- for (const key of reactions.keys()) {
- reactions.remove(key);
- }
- });
- ```
-
-h3(#cancel). Cancel batch operation
-
-To explicitly cancel a batch before it is applied, throw an error inside the batch function. This prevents any queued operations from being applied.
-
-blang[javascript].
-
- ```[javascript]
- await channel.objects.batch((ctx) => {
- const root = ctx.getRoot();
- root.set('foo', 'bar');
-
- // Throwing an error prevents any queued operations from being applied.
- throw new Error('Cancel batch');
- });
- ```
-
-blang[javascript].
-
- h3(#closed). Batch API cannot be used outside the callback function
-
- The Batch API provided by the batch context object cannot be used outside the callback function. Attempting to do so results in an error. This applies both to @BatchContext.getRoot()@ and any object instances retrieved from it.
-
-blang[javascript].
-
- ```[javascript]
- let root;
- await channel.objects.batch((ctx) => {
- root = ctx.getRoot();
- });
-
- // Calling any Batch API methods outside the batch callback
- // will throw an Error: Batch is closed.
- root.set('foo', 'bar');
- ```
diff --git a/content/liveobjects/concepts/objects.textile b/content/liveobjects/concepts/objects.textile
deleted file mode 100644
index 391219d4e9..0000000000
--- a/content/liveobjects/concepts/objects.textile
+++ /dev/null
@@ -1,259 +0,0 @@
----
-title: Objects
-meta_description: "Learn how data is represented as objects in Ably LiveObjects"
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-LiveObjects enables you to store shared data as "objects" on a channel, allowing your application data to be synchronized across multiple users and devices in realtime. This document explains the key concepts you need to know when working with objects.
-
-h2(#object-types). Object Types
-
-LiveObjects provides specialized object types to model your application state. These object types are designed to be conflict-free and eventually consistent, meaning that all operations on them are commutative and converge to the same state across all clients.
-
-h3(#livemap). LiveMap Object
-
-"LiveMap":/docs/liveobjects/map is a key/value data structure similar to a dictionary or JavaScript "Map":https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map :
-
-* Keys must be strings
-* Values can be primitive types or "references":#composability to other objects
-* Supports @set@ and @remove@ operations
-* Concurrent updates to the same key are resolved using last-write-wins (LWW) semantics
-
-blang[javascript].
-
- ```[javascript]
- // Create a LiveMap
- const userSettings = await channel.objects.createMap();
-
- // Set primitive values
- await userSettings.set('theme', 'dark');
- await userSettings.set('notifications', true);
- ```
-
-h4(#primitive-types). Primitive Types
-
-"LiveMap":/docs/liveobjects/map supports the following primitive types as values:
-
-* @string@
-* @number@
-* @boolean@
-* @bytes@
-
-
-
-h3(#livecounter). LiveCounter Object
-
-"LiveCounter":/docs/liveobjects/counter is a numeric counter type:
-
-* The value is a double-precision floating-point number
-* Supports @increment@ and @decrement@ operations
-
-blang[javascript].
-
- ```[javascript]
- // Create a LiveCounter
- const visitsCounter = await channel.objects.createCounter();
-
- // Increment the counter
- await visitsCounter.increment(1);
- ```
-
-h3(#root-object). Root Object
-
-The root object is a special @LiveMap@ instance which:
-
-* Implicitly exists on a channel and does not need to be created explicitly
-* Has the special "objectId":#object-ids of @root@
-* Cannot be deleted
-* Serves as the "entry point":#reachability for accessing all other objects on a channel
-
-Access the root object using the @getRoot()@ function:
-
-blang[javascript].
-
- ```[javascript]
- // Get the Root Object
- const root = await channel.objects.getRoot();
-
- // Use it like any other LiveMap
- await root.set('app-version', '1.0.0');
- ```
-
-h2(#reachability). Reachability
-
-All objects must be reachable from the root object (directly or indirectly). Objects that cannot be reached from the root object will eventually "be deleted":/docs/liveobjects/lifecycle#objects-deleted .
-
-When an object has been deleted, it is no longer usable and calling any methods on the object will fail.
-
-In the example below, the only reference to the @counterOld@ object is replaced on the @root@. This makes @counterOld@ unreachable and it will eventually be deleted.
-
-blang[javascript].
-
- ```[javascript]
- // Create a counter and reference it from the root
- const counterOld = await channel.objects.createCounter();
- await root.set('myCounter', counterOld);
-
- // counterOld will eventually be deleted
- counterOld.on('deleted', () => {
- console.log('counterOld has been deleted and can no longer be used');
- });
-
- // Create a new counter and replace the old one referenced from the root
- const counterNew = await channel.objects.createCounter();
- await root.set('myCounter', counterNew);
- ```
-
-
-
-h2(#composability). Composability
-
-LiveObjects enables you to build complex, hierarchical data structures through composability.
-
-Specifically, a "LiveMap":/docs/liveobjects/map can store references to other @LiveMap@ or @LiveCounter@ object instances as values. This allows you to create nested hierarchies of data.
-
-blang[javascript].
-
- ```[javascript]
- // Create LiveObjects
- const profileMap = await channel.objects.createMap();
- const preferencesMap = await channel.objects.createMap();
- const activityCounter = await channel.objects.createCounter();
-
- // Build a composite structure
- await preferencesMap.set('theme', 'dark');
- await profileMap.set('preferences', preferencesMap);
- await profileMap.set('activity', activityCounter);
- await root.set('profile', profileMap);
-
- // Resulting structure:
- // root (LiveMap)
- // └── profile (LiveMap)
- // ├── preferences (LiveMap)
- // │ └── theme: "dark" (string)
- // └── activity (LiveCounter)
- ```
-
-
-
-It is possible for the same object instance to be accessed from multiple places in your object tree:
-
-blang[javascript].
-
- ```[javascript]
- // Create a counter
- const counter = await channel.objects.createCounter();
-
- // Create two different maps
- const mapA = await channel.objects.createMap();
- const mapB = await channel.objects.createMap();
- await root.set('a', mapA);
- await root.set('b', mapB);
-
- // Reference the same counter from both maps
- await mapA.set('count', counter);
- await mapB.set('count', counter);
-
- // The counter referenced from each location shows the same
- // value, since they refer to the same underlying counter
- mapA.get('count').subscribe(() => {
- console.log(mapA.get('count').value()); // 1
- });
- mapB.get('count').subscribe(() => {
- console.log(mapB.get('count').value()); // 1
- });
-
- // Increment the counter
- await counter.increment(1);
- ```
-
-It is also possible that object references form a cycle:
-
-blang[javascript].
-
- ```[javascript]
- // Create two different maps
- const mapA = await channel.objects.createMap();
- const mapB = await channel.objects.createMap();
-
- // Set up a circular reference
- await mapA.set('ref', mapB);
- await mapB.set('ref', mapA);
-
- // Add one map to root (both are now reachable)
- await root.set('a', mapA);
-
- // We can traverse the cycle
- root.get('a') // mapA
- .get('ref') // mapB
- .get('ref'); // mapA
- ```
-
-
-h2(#metadata). Metadata
-
-Objects include metadata that helps with synchronization, conflict resolution and managing the object lifecycle.
-
-
-
-h3(#object-ids). Object IDs
-
-Every object has a unique identifier that distinguishes it from all other objects.
-
-Object IDs follow a specific format:
-
-bc[text]. type:hash@timestamp
-
-For example:
-
-bc[text]. counter:J7x6mAF8X5Ha60VBZb6GtXSgnKJQagNLgadUlgICjkk@1734628392000
-
-This format has been specifically designed to ensure uniqueness in a globally distributed system and includes:
-
-* **type**: the object type (either @map@ or @counter@)
-* **hash**: a base64 string encoded hash derived from the initial value of the object and a random nonce
-* **timestamp**: a Unix millisecond timestamp denoting the creation time of the object
-
-
-
-h3(#tombstones). Tombstones
-
-Tombstones are markers indicating an object or map entry has been deleted.
-
-* A tombstone is created for an object when it becomes "unreachable":/docs/liveobjects/concepts/objects#reachability from the root object.
-* A tombstone is created for a map entry when it is "removed":/docs/liveobjects/map#remove
-
-Tombstones protect against lagging clients from re-introducing a deleted value, ensuring all clients eventually converge on the same state. They are eventually garbage collected after a safe period of time.
-
-h3(#timeserials). Timeserials
-
-When an operation message is published it is assigned a unique logical timestamp called a "timeserial".
-
-This timeserial is stored on map entries in order to implement last-write-wins conflict resolution semantics.
-
-Additionally, all objects store the timeserial of the last operation that was applied to the object. Since Ably operates fully independent data centers, these timeserials are stored on a per-site basis.
-
-Timeserial metadata is used for internal purposes and is not directly exposed in client libraries. However, it can be viewed using the "REST API":/docs/liveobjects/rest-api-usage .
diff --git a/content/liveobjects/concepts/operations.textile b/content/liveobjects/concepts/operations.textile
deleted file mode 100644
index ce6ecb6e49..0000000000
--- a/content/liveobjects/concepts/operations.textile
+++ /dev/null
@@ -1,138 +0,0 @@
----
-title: Operations
-meta_description: "Learn how objects are updated by operations in Ably LiveObjects."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-LiveObjects operations define how object data is updated and synchronized across multiple clients.
-
-When you create or update an object, the change is expressed as an _operation_ that is sent as an "object message":http://localhost:8000/docs/metadata-stats/stats#messages on the channel. The operation is then applied to the object instance on all clients that are subscribed to the channel.
-
-This document explains the key concepts you need to know when working with operations.
-
-h2(#operation-types). Operation Types
-
-Each object type supports specific operations that modify the object's data.
-
-h3(#livemap). LiveMap Operations
-
-"LiveMap":/docs/liveobjects/map supports the following operations:
-
-* @set@: Set a value for a key
-* @remove@: Remove a key and its value
-
-The value of an entry in a @LiveMap@ instance can be a "primitive type":/docs/liveobjects/concepts/objects#primitive-types or a "reference":/docs/liveobjects/concepts/objects#composability to another object.
-
-blang[javascript].
-
- ```[javascript]
- // Set a value for a key
- await map.set('username', 'alice');
-
- // Remove a key
- await map.remove('username');
- ```
-
-h3(#livecounter). LiveCounter Operations
-
-"LiveCounter":/docs/liveobjects/counter supports the following operations:
-
-* @increment@: Increment the counter by a specified amount
-* @decrement@: Decrement the counter by a specified amount
-
-The amount is a double-precision floating-point number, which is the same as underlying type of a "LiveCounter":/docs/liveobjects/concepts/objects#livecounter value.
-
-
-
-blang[javascript].
-
- ```[javascript]
- // Increment counter by 5
- await counter.increment(5);
-
- // Decrement counter by 2
- await counter.decrement(2);
- ```
-
-h3(#create-operations). Create Operations
-
-Create operations are used to instantiate new objects of a given type.
-
-A create operation can optionally specify an initial value for the object.
-
-blang[javascript].
-
- ```[javascript]
- // Create a map with initial values
- const userMap = await channel.objects.createMap({
- username: 'alice',
- status: 'online'
- });
-
- // Create a counter with initial value
- const scoreCounter = await channel.objects.createCounter(100);
- ```
-
-When a create operation is processed, an "object ID":/docs/liveobjects/concepts/objects#object-ids for the new object instance is automatically generated for the object.
-
-
-
-h2(#object-ids). Object IDs
-
-Every operation is expressed relative to a specific object instance, identified by its "object ID":/docs/liveobjects/concepts/objects#object-ids , which determines which object the operation is applied to.
-
-When using a client library object IDs are handled automatically, allowing you work directly with object references:
-
-blang[javascript].
-
- ```[javascript]
- // The published operation targets the object ID of the `userMap` object instance
- await userMap.set('username', 'alice');
- ```
-
-Therefore it is important that you obtain an up-to-date object instance before performing operations on an object. For example, you can "subscribe":/docs/liveobjects/map#subscribe-data to a @LiveMap@ instance to ensure you always have an up-to-date reference to any child objects in the map:
-
-blang[javascript].
-
- ```[javascript]
- const root = await channel.objects.getRoot();
-
- let myCounter = root.get('myCounter');
- root.subscribe(() => { myCounter = root.get('myCounter'); });
-
- // before incrementing, ensure we have an up-to-date object reference if
- // the counter instance at the 'myCounter' key in the root map changes
- await myCounter.increment(1);
- ```
-
-In the "REST API":/docs/liveobjects/rest-api-usage#updating-objects-by-id , this relationship is made explicit:
-
-bc[sh]. curl -X POST https://rest.ably.io/channels/my-channel/objects \
- -u "{{API_KEY}}"
- -H "Content-Type: application/json" \
- --data \
-'{
- "operation": "MAP_SET",
- "objectId": "root",
- "data": {"key": "username", "value": {"string": "alice"}}
-}'
-
-h2(#batch-operations). Batch Operations
-
-"Batch operations":/docs/liveobjects/batch can be used to batch a set of operations together:
-
-* Multiple operations are grouped into a single atomic unit
-* All operations in the batch either succeed together or fail together
-* Operations in a batch are sent as a single message
-* No operations from other clients can be interleaved within a batch
diff --git a/content/liveobjects/counter.textile b/content/liveobjects/counter.textile
deleted file mode 100644
index cb2e1cd037..0000000000
--- a/content/liveobjects/counter.textile
+++ /dev/null
@@ -1,191 +0,0 @@
----
-title: LiveCounter
-meta_description: "Create, update and receive updates for a numerical counter that synchronizes state across clients in realtime."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-LiveCounter is a synchronized numerical counter that supports increment and decrement operations. It ensures that all updates are correctly applied and synchronized across users in realtime, preventing inconsistencies when multiple users modify the counter value simultaneously.
-
-h2(#create). Create LiveCounter
-
-A @LiveCounter@ instance can be created using the @channel.objects.createCounter()@ method. It must be stored inside a @LiveMap@ object that is reachable from the "root object":/docs/liveobjects/concepts/objects#root-object .
-
-blang[javascript].
-
- @channel.objects.createCounter()@ is asynchronous, as the client sends the create operation to the Ably system and waits for an acknowledgment of the successful counter creation.
-
-
-
-blang[javascript].
-
- ```[javascript]
- const counter = await channel.objects.createCounter();
- await root.set('counter', counter);
- ```
-
-Optionally, you can specify an initial value when creating the counter:
-
-blang[javascript].
-
- ```[javascript]
- const counter = await channel.objects.createCounter(100); // Counter starts at 100
- ```
-
-h2(#value). Get counter value
-
-Get the current value of a counter using the @LiveCounter.value()@ method:
-
-blang[javascript].
-
- ```[javascript]
- console.log('Counter value:', counter.value());
- ```
-
-h2(#subscribe-data). Subscribe to data updates
-
-You can subscribe to data updates on a counter to receive realtime changes made by you or other clients.
-
-
-
-Subscribe to data updates on a counter using the @LiveCounter.subscribe()@ method:
-
-blang[javascript].
-
- ```[javascript]
- counter.subscribe((update) => {
- console.log('Counter updated:', counter.value());
- console.log('Update details:', update);
- });
- ```
-
-The update object provides details about the change, such as the amount by which the counter value was changed.
-
-Example structure of an update object when the counter was incremented by 5:
-
-```[json]
-{
- {
- "amount": 5
- }
-}
-```
-
-Or decremented by 10:
-
-```[json]
-{
- {
- "amount": -10
- }
-}
-```
-
-h3(#unsubscribe-data). Unsubscribe from data updates
-
-Use the @unsubscribe()@ function returned in the @subscribe()@ response to remove a counter update listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const { unsubscribe } = counter.subscribe(() => console.log(counter.value()));
- // To remove the listener
- unsubscribe();
- ```
-
-Use the @LiveCounter.unsubscribe()@ method to deregister a provided listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const listener = () => console.log(counter.value());
- counter.subscribe(listener);
- // To remove the listener
- counter.unsubscribe(listener);
- ```
-
-Use the @LiveCounter.unsubscribeAll()@ method to deregister all counter update listeners:
-
-blang[javascript].
-
- ```[javascript]
- counter.unsubscribeAll();
- ```
-
-h2(#update). Update LiveCounter
-
-Update the counter value by calling @LiveCounter.increment()@ or @LiveCounter.decrement()@. These operations are synchronized across all clients and trigger data subscription callbacks for the counter, including on the client making the request.
-
-blang[javascript].
-
- These operations are asynchronous, as the client sends the corresponding update operation to the Ably system and waits for acknowledgment of the successful counter update.
-
-blang[javascript].
-
- ```[javascript]
- await counter.increment(5); // Increase value by 5
- await counter.decrement(2); // Decrease value by 2
- ```
-
-h2(#subscribe-lifecycle). Subscribe to lifecycle events
-
-Subscribe to lifecycle events on a counter using the @LiveCounter.on()@ method:
-
-blang[javascript].
-
- ```[javascript]
- counter.on('deleted', () => {
- console.log('Counter has been deleted');
- });
- ```
-
-Read more about "objects lifecycle events":/docs/liveobjects/lifecycle#objects.
-
-h3(#unsubscribe-lifecycle). Unsubscribe from lifecycle events
-
-Use the @off()@ function returned in the @on()@ response to remove a lifecycle event listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const { off } = counter.on(('deleted') => console.log('Counter deleted'));
- // To remove the listener
- off();
- ```
-
-Use the @LiveCounter.off()@ method to deregister a provided lifecycle event listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const listener = () => console.log('Counter deleted');
- counter.on('deleted', listener);
- // To remove the listener
- counter.off('deleted', listener);
- ```
-
-Use the @LiveCounter.offAll()@ method to deregister all lifecycle event listeners:
-
-blang[javascript].
-
- ```[javascript]
- counter.offAll();
- ```
diff --git a/content/liveobjects/inband-objects.textile b/content/liveobjects/inband-objects.textile
deleted file mode 100644
index a09228ba3e..0000000000
--- a/content/liveobjects/inband-objects.textile
+++ /dev/null
@@ -1,73 +0,0 @@
----
-title: Inband Objects
-meta_description: "Subscribe to LiveObjects updates from Pub/Sub SDKs."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-Inband objects enables clients to subscribe to LiveObjects updates in realtime, even on platforms that don't yet have a native LiveObjects Realtime client implementation.
-
-
-
-Inband objects works by delivering changes to channel objects as regular channel messages, similar to "inband occupancy":/docs/channels/options#occupancy .
-
-h2(#inband-objects-enable). Enable Inband Objects
-
-To enable inband objects, use the @objects@ "channel parameter":/docs/channels/options#objects when getting a channel:
-
-blang[javascript].
-
- ```[javascript]
- // When getting a channel instance
- const channelOpts = { params: { objects: 'objects' } };
- const channel = realtime.channels.get('my-channel', channelOpts);
-
- // Or using setOptions on an existing channel
- await channel.setOptions({ params: { objects: 'objects' } });
- ```
-
-
-
-h2(#inband-objects-subscribe). Subscribe to updates
-
-When using inband objects, the client will receive special @[meta]objects@ messages whenever the objects on the channel are updated. These messages provide a snapshot of the current set of objects on the channel.
-
-
-
-"Subscribe":/docs/api/realtime-sdk/channels#subscribe to @[meta]objects@ messages like you would any other message on the channel. For convenience, use a message name filter to only receive messages with the name @[meta]objects@ in your listener:
-
-blang[javascript].
-
- ```[javascript]
- // Subscribe to [meta]objects messages
- channel.subscribe('[meta]objects', (message) => {
- const { syncId, nextCursor, object } = message.data;
- console.log("Received inband objects message:", syncId, nextCursor, JSON.stringify(message.data));
- });
- ```
-
-h2(#inband-objects-message-format). Message Format
-
-Inband objects messages are sent as a sequence of messages, where each message contains a snapshot of a single object on the channel. Taken together, a set of messages belonging to the same sequence describes the complete set of objects on the channel.
-
-Each inband objects message has a message @name@ of @[meta]objects@.
-
-The message @data@ is a JSON object with the following top-level properties:
-
-* @syncId@: A unique ID for this sequence. All messages with the same @syncId@ are part of the same sequence of messages which describes the complete set of the objects on the channel.
-* @nextCursor@: A cursor for the next message in the sequence, or @undefined@ if this is the last message in the sequence.
-* @object@: A JSON representation of the object included in the message.
-
-The shape of the @object@ is the same as the response format of an object when listing them via the "REST API":/docs/liveobjects/rest-api-usage?lang=javascript#fetching-objects-list-values .
diff --git a/content/liveobjects/index.textile b/content/liveobjects/index.textile
deleted file mode 100644
index 5e289dffef..0000000000
--- a/content/liveobjects/index.textile
+++ /dev/null
@@ -1,76 +0,0 @@
----
-title: About LiveObjects
-meta_description: "Learn about Ably LiveObjects, its features, use cases, and how it simplifies realtime state synchronization."
-product: liveobjects
----
-
-
-
-Ably LiveObjects provides a serverless, durable, and scalable way to create, update, and synchronize shared state across large numbers of connected clients at any scale.
-
-LiveObjects provides a global, durable, and conflict-free shared data layer built on Ably's "global platform":/docs/platform/architecture, so your application state stays perfectly synchronized in realtime without the need to build or manage complex infrastructure yourself.
-
-
-
-LiveObjects enables you to store shared data as “objects” on a "channel":/docs/channels . When an object is updated, changes are automatically propagated to all subscribed clients in realtime, ensuring everyone always sees the latest state.
-
-LiveObjects provides a simple, purpose-built API that handles realtime synchronization, persistence, and convergence behind the scenes. The result is a single logical view of your data - distributed to the edge, updated in real time, and always in sync - no matter how many users are connected or where they are in the world.
-
-h2(#use-cases). Use cases
-
-You can use LiveObjects to build all sorts of powerful functionality in your applications that require realtime updates to shared data. It is useful when your application has data that:
-
-* Is shared by multiple users or devices
-* Needs to be synchronized in realtime
-* Can be updated concurrently from multiple places
-
-Use Ably LiveObjects to build scalable realtime applications such as:
-
-* Voting and polling systems: Platforms that need the ability to count and display votes in realtime, such as audience engagement tools, quizzes, and decision-making applications.
-* Collaborative applications: Tools like shared whiteboards or content and product management applications where multiple users edit shared content simultaneously.
-* Live leaderboards: Multiplayer games or competition-based applications that require up-to-date rankings and scoreboards.
-* Game state: Applications that present dynamic in-game statistics or game state in realtime, such as player health, scores, and inventory changes.
-* Shared configuration, settings or controls: Systems where configuration parameters are shared or updated across multiple users or devices.
-
-h2(#features). Features
-
-Ably LiveObjects provides the following key features:
-
-* "Object types":#object-types
-* "Composability":#composability
-* "Batch operations":#batch-operations
-* "Inband objects":#inband-objects
-* "Object storage":#object-storage
-
-h3(#object-types). Object types
-
-LiveObjects provides specialized object types to model your application state. These object types are designed to be conflict-free and eventually consistent, meaning that all operations on them are commutative and converge to the same state across all clients.
-
-h4(#counter). LiveCounter
-
-"LiveCounter":/docs/liveobjects/counter is a numerical counter that supports increment and decrement operations. It ensures that all updates are correctly applied and synchronized across users in realtime, preventing inconsistencies when multiple users modify the counter value simultaneously.
-
-h4(#map). LiveMap
-
-"LiveMap":/docs/liveobjects/map is a key/value data structure that synchronizes its state across users in realtime. It enables you to store primitive values, such as numbers, strings, booleans and buffers, as well as other objects, enabling "composable data structures":#composability.
-
-h3(#composability). Composability
-
-LiveObjects enables you to build complex, hierarchical data structures through "composability":/docs/liveobjects/concepts/objects#composability .
-
-h3(#batch-operations). Batch operations
-
-"Batch operations":/docs/liveobjects/batch enables multiple operations to be grouped into a single channel message, ensuring atomic application of grouped operations. This prevents partial updates of your data and ensures consistency across all users.
-
-h3(#inband-objects). Inband objects
-
-"Inband objects":/docs/liveobjects/inband-objects enables clients to subscribe to LiveObjects updates in realtime, even on platforms that don't yet have a native LiveObjects Realtime client implementation.
-
-h3(#object-storage). Object storage
-
-LiveObjects "durably stores":/docs/liveobjects/storage all objects on a channel for 90 days by default.
diff --git a/content/liveobjects/lifecycle.textile b/content/liveobjects/lifecycle.textile
deleted file mode 100644
index 18c4df8a8d..0000000000
--- a/content/liveobjects/lifecycle.textile
+++ /dev/null
@@ -1,71 +0,0 @@
----
-title: Lifecycle events
-meta_description: "Understand lifecycle events for Objects, LiveMap and LiveCounter to track synchronization events and object deletions."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-h2(#synchronization). Objects synchronization events
-
-The "@channel.objects@":/docs/api/realtime-sdk/channels#objects instance emits synchronization events that indicate when the local state on the client is being synchronized with the Ably service. These events can be useful for displaying loading indicators, preventing user interactions during synchronization, or triggering application logic when data is first loaded.
-
-* @syncing@ - Emitted when the local copy of objects on a channel begins synchronizing with the Ably service.
-* @synced@ - Emitted when the local copy of objects on a channel has been synchronized with the Ably service.
-
-blang[javascript].
-
- ```[javascript]
- channel.objects.on('syncing', () => {
- console.log('Objects are syncing...');
- // Show a loading indicator and disable edits in the application
- });
-
- channel.objects.on('synced', () => {
- console.log('Objects have been synced.');
- // Hide loading indicator
- });
- ```
-
-
-
-h2(#objects-lifecycle). LiveMap/LiveCounter lifecycle events
-
-Lifecycle events enable you to monitor changes in an object's lifecycle.
-
-Currently, only the @deleted@ event can be emitted. Understanding the conditions under which this event is emitted and handling it properly ensures that your application maintains expected behavior.
-
-h3(#objects-deleted). deleted event
-
-Objects that were created on a channel can become orphaned when they were never assigned to the object tree, or because their reference was removed using "@LiveMap.remove()@":/docs/liveobjects/map#remove and never reassigned. Orphaned objects will be garbage collected by Ably, typically after 24 hours. When this happens, a @deleted@ event is broadcast for the affected object. Once deleted, an object can no longer be interacted with, and any operations performed on it will result in an error.
-
-While the LiveObjects feature internally manages object deletions and removes them from its internal state, your application may still hold references to these deleted objects in separate data structures. The @deleted@ event provides a way to react accordingly by removing references to deleted objects and preventing potential errors.
-
-In most cases, subscribing to @deleted@ events is unnecessary. Your application should have already reacted to object removal when a corresponding "@LiveMap.remove()@":/docs/liveobjects/map#remove operation was received. However, if your application separately stores references to object instances and does not properly clear them when objects are orphaned, any later interactions with those objects after they are deleted will result in an error. In such cases, subscribing to @deleted@ events helps ensure that those references are cleaned up and runtime errors are avoided.
-
-
-
-blang[javascript].
-
- ```[javascript]
- const { off } = counter.on('deleted', () => {
- console.log('LiveCounter has been deleted.');
- // Remove references to this object from your application
- // as it can no longer be interacted with
- });
- ```
-
-Read more about subscribing to object lifecycle events for "LiveCounter":/docs/liveobjects/counter#subscribe-lifecycle and "LiveMap":/docs/liveobjects/map#subscribe-lifecycle.
diff --git a/content/liveobjects/map.textile b/content/liveobjects/map.textile
deleted file mode 100644
index 913efbad28..0000000000
--- a/content/liveobjects/map.textile
+++ /dev/null
@@ -1,251 +0,0 @@
----
-title: LiveMap
-meta_description: "Create, update and receive updates for a key/value data structure that synchronizes state across clients in realtime."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-LiveMap is a key/value data structure that synchronizes its state across users in realtime. It enables you to store primitive values, such as numbers, strings, booleans and buffers, as well as other objects, "enabling you to build complex, hierarchical object structure":#composability.
-
-Conflicts in a LiveMap are automatically resolved with last-write-wins (LWW) semantics. The latest received operation on a key will be applied to the LiveMap and broadcast to all clients.
-
-h2(#create). Create LiveMap
-
-A @LiveMap@ instance can be created using the @channel.objects.createMap()@ method. It must be stored inside another @LiveMap@ object that is reachable from the "root object":/docs/liveobjects/concepts/objects#root-object .
-
-blang[javascript].
-
- @channel.objects.createMap()@ is asynchronous, as the client sends the create operation to the Ably system and waits for an acknowledgment of the successful map creation.
-
-
-
-blang[javascript].
-
- ```[javascript]
- const map = await channel.objects.createMap();
- await root.set('myMap', map);
- ```
-
-Optionally, you can specify an initial key/value structure when creating the map:
-
-blang[javascript].
-
- ```[javascript]
- // Pass a regular JavaScript object reflecting the initial state
- const map = await channel.objects.createMap({ foo: 'bar', baz: 42 });
- // You can also pass other objects as values for keys
- await channel.objects.createMap({ nestedMap: map });
- ```
-
-h2(#get). Get value for a key
-
-Get the current value for a key in a map using the @LiveMap.get()@ method:
-
-blang[javascript].
-
- ```[javascript]
- console.log('Value for my-key:', map.get('my-key'));
- ```
-
-h2(#subscribe-data). Subscribe to data updates
-
-You can subscribe to data updates on a map to receive realtime changes made by you or other clients.
-
-
-
-Subscribe to data updates on a map using the @LiveMap.subscribe()@ method:
-
-blang[javascript].
-
- ```[javascript]
- map.subscribe((update) => {
- console.log('Map updated:', [...map.entries()]);
- console.log('Update details:', update);
- });
- ```
-
-The update object provides details about the change, listing the keys that were changed and indicating whether they were updated (value changed) or removed from the map.
-
-Example structure of an update object when the key @foo@ is updated and the key @bar@ is removed:
-
-```[json]
-{
- {
- "foo": "updated",
- "bar": "removed"
- }
-}
-```
-
-h3(#unsubscribe-data). Unsubscribe from data updates
-
-Use the @unsubscribe()@ function returned in the @subscribe()@ response to remove a map update listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const { unsubscribe } = map.subscribe(() => console.log('Map updated'));
- // To remove the listener
- unsubscribe();
- ```
-
-Use the @LiveMap.unsubscribe()@ method to deregister a provided listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const listener = () => console.log('Map updated');
- map.subscribe(listener);
- // To remove the listener
- map.unsubscribe(listener);
- ```
-
-Use the @LiveMap.unsubscribeAll()@ method to deregister all map update listeners:
-
-blang[javascript].
-
- ```[javascript]
- map.unsubscribeAll();
- ```
-
-h2(#set). Set keys in a LiveMap
-
-Set a value for a key in a map by calling @LiveMap.set()@. This operation is synchronized across all clients and triggers data subscription callbacks for the map, including on the client making the request.
-
-Keys in a map can contain numbers, strings, booleans and buffers, as well as other @LiveMap@ and @LiveCounter@ objects.
-
-blang[javascript].
-
- This operation is asynchronous, as the client sends the corresponding update operation to the Ably system and waits for acknowledgment of the successful map key update.
-
-blang[javascript].
-
- ```[javascript]
- await map.set('foo', 'bar');
- await map.set('baz', 42);
-
- // Can also set a reference to another object
- const counter = await channel.objects.createCounter();
- await map.set('counter', counter);
- ```
-
-h2(#remove). Remove a key from a LiveMap
-
-Remove a key from a map by calling @LiveMap.remove()@. This operation is synchronized across all clients and triggers data subscription callbacks for the map, including on the client making the request.
-
-blang[javascript].
-
- This operation is asynchronous, as the client sends the corresponding remove operation to the Ably system and waits for acknowledgment of the successful map key removal.
-
-blang[javascript].
-
- ```[javascript]
- await map.remove('foo');
- ```
-
-h2(#iterate). Iterate over key/value pairs
-
-blang[javascript].
-
- Iterate over key/value pairs, keys or values using the @LiveMap.entries()@, @LiveMap.keys()@ and @LiveMap.values()@ methods respectively.
-
- These methods return a "map iterator":https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator object for convenient traversal. Note that contrary to JavaScript's "Map":https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map counterpart, these methods do not guarantee that entries are returned in insertion order.
-
-blang[javascript].
-
- ```[javascript]
- for (const [key, value] of map.entries()) {
- console.log(`Key: ${key}, Value: ${value}`);
- }
-
- for (const key of map.keys()) {
- console.log(`Key: ${key}`);
- }
-
- for (const value of map.values()) {
- console.log(`Value: ${value}`);
- }
- ```
-
-h2(#subscribe-lifecycle). Subscribe to lifecycle events
-
-Subscribe to lifecycle events on a map using the @LiveMap.on()@ method:
-
-blang[javascript].
-
- ```[javascript]
- map.on('deleted', () => {
- console.log('Map has been deleted');
- });
- ```
-
-Read more about "objects lifecycle events":/docs/liveobjects/lifecycle#objects.
-
-h3(#unsubscribe-lifecycle). Unsubscribe from lifecycle events
-
-Use the @off()@ function returned in the @on()@ response to remove a lifecycle event listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const { off } = map.on(('deleted') => console.log('Map deleted'));
- // To remove the listener
- off();
- ```
-
-Use the @LiveMap.off()@ method to deregister a provided lifecycle event listener:
-
-blang[javascript].
-
- ```[javascript]
- // Initial subscription
- const listener = () => console.log('Map deleted');
- map.on('deleted', listener);
- // To remove the listener
- map.off('deleted', listener);
- ```
-
-Use the @LiveMap.offAll()@ method to deregister all lifecycle event listeners:
-
-blang[javascript].
-
- ```[javascript]
- map.offAll();
- ```
-
-h2(#composability). Composability
-
-A @LiveMap@ can store other @LiveMap@ or @LiveCounter@ objects as values for its keys, enabling you to build complex, hierarchical object structure. This enables you to represent complex data models in your applications while ensuring realtime synchronization across clients.
-
-blang[javascript].
-
- ```[javascript]
- // Create a hierarchy of objects using LiveMap
- const counter = await channel.objects.createCounter();
- const map = await channel.objects.createMap({ nestedCounter: counter });
- const outerMap = await channel.objects.createMap({ nestedMap: map });
- await root.set('outerMap', outerMap);
-
- // resulting structure:
- // root (LiveMap)
- // └── outerMap (LiveMap)
- // └── nestedMap (LiveMap)
- // └── nestedCounter (LiveCounter)
- ```
diff --git a/content/liveobjects/quickstart.textile b/content/liveobjects/quickstart.textile
deleted file mode 100644
index bce91ee5f1..0000000000
--- a/content/liveobjects/quickstart.textile
+++ /dev/null
@@ -1,204 +0,0 @@
----
-title: Quickstart
-meta_description: "A quickstart guide to learn the basics of integrating the Ably LiveObjects product into your application."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-This guide shows how to integrate Ably LiveObjects into your application.
-
-You will learn how to:
-
-* Create an Ably account and get an API key for authentication.
-* Install the Ably Pub/Sub SDK.
-* Create a channel with LiveObjects functionality enabled.
-* Create, update and subscribe to changes on LiveObjects data structures: "LiveMap":/docs/liveobjects/map and "LiveCounter":/docs/liveobjects/counter.
-
-h2(#step-0). Authentication
-
-An "API key":/docs/auth#api-keys is required to authenticate with Ably. API keys are used either to authenticate directly with Ably using "basic authentication":/docs/auth/basic, or to generate tokens for untrusted clients using "token authentication":/docs/auth/token.
-
-
-
-"Sign up":https://ably.com/sign-up for a free account and create your own API key in the "dashboard":https://ably.com/dashboard or use the "Control API":/docs/account/control-api to create an API key programmatically.
-
-API keys and tokens have a set of "capabilities":/docs/auth/capabilities assigned to them that specify which operations can be performed on which resources. The following capabilities are available for LiveObjects:
-
-* @object-subscribe@ - grants clients read access to LiveObjects, allowing them to get the root object and subscribe to updates.
-* @object-publish@ - grants clients write access to LiveObjects, allowing them to perform mutation operations on objects.
-
-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 calling mutation methods on LiveObjects.
-
-For the purposes of this guide, make sure your API key includes both @object-subscribe@ and @object-publish@ "capabilities":/docs/auth/capabilities to allow full read and write access.
-
-h2(#step-1). Install Ably Pub/Sub SDK
-
-blang[javascript].
-
- LiveObjects is available as part of the Ably Pub/Sub SDK via the dedicated Objects plugin.
-
-blang[javascript].
-
- h3(#npm). NPM
-
- Install the Ably Pub/Sub SDK as an "NPM module":https://www.npmjs.com/package/ably:
-
- ```[sh]
- npm install ably
- ```
-
- Import the SDK and the Objects plugin into your project:
-
- ```[javascript]
- import * as Ably from 'ably';
- import Objects from 'ably/objects';
- ```
-
-blang[javascript].
-
- h3(#cdn). CDN
-
- Reference the Ably Pub/Sub SDK and the Objects plugin within your HTML file:
-
- ```[html]
-
-
-
- ```
-
-h2(#step-2). Instantiate a client
-
-blang[javascript].
-
- Instantiate an Ably Realtime client from the Pub/Sub SDK, providing the Objects plugin:
-
- ```[javascript]
- const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', plugins: { Objects } });
- ```
-
-blang[javascript].
-
- A "@ClientOptions@":/docs/api/realtime-sdk#client-options object may be passed to the Pub/Sub SDK instance to further customize the connection, however at a minimum you must set an API key and provide an @Objects@ plugin so that the client can use LiveObjects functionality.
-
-h2(#step-3). Create a channel
-
-LiveObjects is managed and persisted on "channels":/docs/channels. To use LiveObjects, you must first create a channel with the correct "channel mode flags":/docs/channels/options#modes :
-
-* @OBJECT_SUBSCRIBE@ - required to access objects on a channel.
-* @OBJECT_PUBLISH@ - required to create and modify objects on a channel.
-
-
-
-blang[javascript].
-
- ```[javascript]
- const channelOptions = { modes: ['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH'] };
- const channel = realtimeClient.channels.get('my_liveobjects_channel', channelOptions);
- ```
-
-Next, you need to "attach to the channel":/docs/channels/states. Attaching to a channel starts an initial synchronization sequence where the objects on the channel are sent to the client.
-
-blang[javascript].
-
- ```[javascript]
- await channel.attach();
- ```
-
-h2(#step-4). Get root object
-
-The "@channel.objects@":/docs/api/realtime-sdk/channels#objects property gives access to the LiveObjects API for a channel.
-
-Use it to get the root object, which is the entry point for accessing and persisting objects on a channel. The root object is a "@LiveMap@":/docs/liveobjects/map instance that always exists on a channel and acts as the top-level node in your object tree. You can get the root object using the @getRoot()@ function of LiveObjects:
-
-blang[javascript].
-
- ```[javascript]
- // The promise resolves once the LiveObjects state is synchronized with the Ably system
- const root = await channel.objects.getRoot();
- ```
-
-h2(#step-5). Create objects
-
-You can create new objects using dedicated functions of the LiveObjects API at "@channel.objects@":/docs/api/realtime-sdk/channels#objects. To persist them on a channel and share them between clients, you must assign objects to a parent @LiveMap@ instance connected to the root object. The root object itself is a @LiveMap@ instance, so you can assign objects to the root and start building your object tree from there.
-
-
-
-blang[javascript].
-
- ```[javascript]
- const visitsCounter = await channel.objects.createCounter();
- const reactionsMap = await channel.objects.createMap();
-
- await root.set('visits', visitsCounter);
- await root.set('reactions', reactionsMap);
- ```
-
-h2(#step-6). Subscribe to updates
-
-Subscribe to realtime updates to objects on a channel. You will be notified when an object is updated by other clients or by you.
-
-blang[javascript].
-
- ```[javascript]
- visitsCounter.subscribe(() => {
- console.log('Visits counter updated:', visitsCounter.value());
- });
-
- reactionsMap.subscribe(() => {
- console.log('Reactions map updated:', [...reactionsMap.entries()]);
- });
- ```
-
-h2(#step-7). Update objects
-
-Update objects using mutation methods. All subscribers (including you) will be notified of the changes when you update an object:
-
-blang[javascript].
-
- ```[javascript]
- await visitsCounter.increment(5);
- // console: "Visits counter updated: 5"
- await visitsCounter.decrement(2);
- // console: "Visits counter updated: 3"
-
- await reactionsMap.set('like', 10);
- // console: "Reactions map updated: [['like',10]]"
- await reactionsMap.set('love', 5);
- // console: "Reactions map updated: [['like',10],['love',5]]"
- await reactionsMap.remove('like');
- // console: "Reactions map updated: [['love',5]]"
- ```
-
-
-
-h2(#step-8). Next steps
-
-This quickstart introduced the basic concepts of LiveObjects and demonstrated how it works. The next steps are to:
-
-* Read more about "LiveCounter":/docs/liveobjects/counter and "LiveMap":/docs/liveobjects/map.
-* Learn about "Batching Operations":/docs/liveobjects/batch.
-* Learn about "Objects Lifecycle Events":/docs/liveobjects/lifecycle.
-* Add "Typings":/docs/liveobjects/typing for your LiveObjects.
diff --git a/content/liveobjects/storage.textile b/content/liveobjects/storage.textile
deleted file mode 100644
index 773e57fd40..0000000000
--- a/content/liveobjects/storage.textile
+++ /dev/null
@@ -1,63 +0,0 @@
----
-title: Object storage
-meta_description: "Learn about LiveObjects object storage."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-h2(#default-persistence). Default object storage - 90 days
-
-Ably durably stores all objects on a channel for a retention period that is configured to 90 days by default. If the data is not updated within the retention period, it will automatically expire. After expiry, the channel is reset to its initial state and only includes an empty "root object":/docs/liveobjects/concepts/objects#root-object.
-
-
-
-h2(#outside-persistence). Store objects outside of Ably
-
-You can store your objects outside of Ably by obtaining the channel objects via the "REST API":/docs/liveobjects/rest-api-usage#fetching-objects and storing them in your own database. This is useful if you want to keep a permanent record of the objects on a channel or if you want to perform analytics on the data.
-
-In order to receive notifications when the objects on a channel are updated, use "inband objects":/docs/liveobjects/inband-objects to receive updates as regular channel messages.
-
-
-
-h2(#operation-persistence). Operation storage
-
-When you update an object, the change is expressed as an "operation":/docs/liveobjects/concepts/operations that is sent as an "object message":/docs/metadata-stats/stats#messages on the channel. Like all messages, Ably stores object messages for 2 minutes by default.
-
-This means that if a client disconnects from Ably for a short period of time, it can automatically retrieve any operations it may have missed when it reconnects. If a client disconnects for longer than 2 minutes, the client will be sent the latest state of the objects on the channel (which are durably stored for 90 days) when it reconnects, ensuring the client remains fully synchronized.
-
-Operations themselves are not included in the "history":https://ably.com/docs/storage-history/history or "rewind":/docs/channels/options/rewind backlog of a channel. Instead, you should interact with objects directly via the client library.
-
-h2(#limits). Object count and size limits
-
-There is a maximum number of objects that can be stored on a "channel":/docs/pricing/limits#channel, which is configured to 100 objects by default.
-
-
-
-A @LiveCounter@ is a double-precision floating-point number and has a size of 8 bytes.
-
-The size of a @LiveMap@ object is calculated as the sum of the length of all keys plus the size of all values where:
-
-* @string@ values are the length of the string
-* @number@ values are 8 bytes
-* @boolean@ values are 1 byte
-* @bytes@ values are the length of the byte array
-
-The maximum allowed size of a single @LiveMap@ object is the same as the "message size limit":/docs/pricing/limits#message for your package. This is because objects are "synchronized":/docs/liveobjects/concepts/synchronization#client-objects to the client as channel messages.
-
-
-
-For more information, see "limits":/docs/pricing/limits .
diff --git a/content/liveobjects/typing.textile b/content/liveobjects/typing.textile
deleted file mode 100644
index 9dc26c6d97..0000000000
--- a/content/liveobjects/typing.textile
+++ /dev/null
@@ -1,92 +0,0 @@
----
-title: Typing
-meta_description: "Type objects on a channel for type safety and code autocompletion."
-product: liveobjects
-languages:
- - javascript
----
-
-
-
-blang[javascript].
-
- If you are using TypeScript in your project, you can leverage LiveObjects' built-in TypeScript support to ensure type safety and enable autocompletion when working with objects on a channel.
-
- h2(#global). Global AblyObjectsTypes interface
-
- You can type objects on all your channels by defining a global @AblyObjectsTypes@ interface. If you only want to type the root object for a specific channel, see the "Typing channel.objects.getRoot()":#getroot section below.
-
- Define the @AblyObjectsTypes@ interface in a type declaration file. You can create a file named @ably.config.d.ts@ in the root of your application:
-
-blang[javascript].
-
- ```[javascript]
- // file: ably.config.d.ts
- import { LiveCounter, LiveMap } from 'ably';
-
- // Define dedicated types and export them for reuse in your application
- export type MyCustomRoot = {
- reactions: LiveMap<{
- hearts: LiveCounter;
- likes: LiveCounter;
- }>;
- };
-
- declare global {
- export interface AblyObjectsTypes {
- root: MyCustomRoot;
- }
- }
- ```
-
-blang[javascript].
-
- This enables TypeScript to infer the correct types when accessing and mutating LiveObjects:
-
-blang[javascript].
-
- ```[javascript]
- // LiveMap<{ reactions: LiveMap<{ hearts: LiveCounter; likes: LiveCounter }> }>
- const root = await channel.objects.getRoot();
-
- // LiveMap<{ hearts: LiveCounter; likes: LiveCounter }>
- const reactions = root.get('reactions');
-
- // LiveCounter
- const likes = reactions.get('likes');
-
- reactions.set('hearts', 1); // Error: Argument of type 'number' is not assignable to parameter of type 'LiveCounter'.ts(2345)
- ```
-
-blang[javascript].
-
- h2(#getroot). Typing channel.objects.getRoot()
-
- You can pass a type parameter directly to the @channel.objects.getRoot()@ method call to type the root object for a channel explicitly:
-
-blang[javascript].
-
- ```[javascript]
- // Define types for different root objects
- type ReactionsRoot = {
- hearts: LiveCounter;
- likes: LiveCounter;
- };
-
- type PollsRoot = {
- currentPoll: LiveMap;
- };
-
- // LiveMap<{ hearts: LiveCounter; likes: LiveCounter }>
- const reactionsRoot = await reactionsChannel.objects.getRoot();
-
- // LiveMap<{ currentPoll: LiveMap }>
- const pollsRoot = await pollsChannel.objects.getRoot();
- ```
-
-blang[javascript].
-
- Typing @channel.objects.getRoot()@ is particularly useful when your application uses multiple channels, each with a different object structure.
diff --git a/src/data/nav/liveobjects.ts b/src/data/nav/liveobjects.ts
index 9c5a2821b5..60c16b57f3 100644
--- a/src/data/nav/liveobjects.ts
+++ b/src/data/nav/liveobjects.ts
@@ -75,6 +75,7 @@ export default {
{
name: 'Using the REST API',
link: '/docs/liveobjects/rest-api-usage',
+ languages: ['javascript'],
},
{
name: 'Inband objects',
diff --git a/src/pages/docs/liveobjects/batch.mdx b/src/pages/docs/liveobjects/batch.mdx
new file mode 100644
index 0000000000..960b21ec25
--- /dev/null
+++ b/src/pages/docs/liveobjects/batch.mdx
@@ -0,0 +1,141 @@
+---
+title: Batch operations
+meta_description: "Group multiple objects operations into a single channel message to apply grouped operations atomically and improve performance."
+---
+
+
+
+The Batching API in LiveObjects enables multiple updates to be grouped into a single channel message and applied atomically. It ensures that all operations in a batch either succeed together or are discarded entirely. Batching operations is essential when multiple related updates to channel objects must be applied as a single atomic unit, for example, when application logic depends on multiple objects being updated simultaneously. Without batching, if one operation succeeds while another fails, your application state could become inconsistent.
+
+Note that this differs from ["Message batching"](/docs/messages/batch), the native Pub/Sub messages feature. The LiveObjects Batching API is a separate API specifically designed to enable you to group object operations into a single channel message, ensuring that the Ably system guarantees the atomicity of the applied changes.
+
+## Create batch context
+
+To batch object operations together, use the `channel.objects.batch()` method. This method accepts a callback function, which is provided with a batch context object. The batch context object provides a synchronous API to work with objects on a channel that stores operations inside the batch instead of applying them immediately.
+
+Using the batch context ensures that operations are grouped and sent in a single channel message after the batch callback function has run. This guarantees that all changes are applied atomically by both the server and all clients.
+
+
+
+
+```javascript
+await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+
+ root.set('foo', 'bar');
+ root.set('baz', 42);
+
+ const counter = root.get('counter');
+ counter.increment(5);
+
+ // Batched operations are sent to the Ably system when the batch callback has run.
+});
+```
+
+
+If an error occurs within the batch, all operations are discarded, preventing partial updates and ensuring atomicity.
+
+### Batch context object
+
+The batch context provides a synchronous API for objects operations inside the batch callback. It mirrors the asynchronous API found on `channel.objects`, including [LiveCounter](/docs/liveobjects/counter) and [LiveMap](/docs/liveobjects/map).
+
+To access the batch API, call `BatchContext.getRoot()`, which synchronously returns a wrapper around the [root](/docs/liveobjects/concepts/objects#root-object) object instance. This wrapper enables you to access and modify objects within a batch.
+
+
+
+
+```javascript
+await channel.objects.batch((ctx) => {
+ // Note: .getRoot() call on a batch context is synchronous.
+ // The returned root object is a special wrapper around a regular LiveMap instance,
+ // providing a synchronous mutation API.
+ const root = ctx.getRoot();
+
+ // Mutation operations like LiveMap.set and LiveCounter.increment
+ // are synchronous inside the batch and queue operations instead of applying them immediately.
+ root.set('foo', 'bar');
+ root.remove('baz');
+
+ // Access other objects through the root object from the BatchContext.getRoot() method.
+ const counter = root.get('counter');
+ counter.increment(5);
+});
+```
+
+
+You cannot create new objects using the batch context. If you need to create new objects and add them to the channel as part of an atomic batch operation to guarantee atomicity, you must first create them using the regular `channel.objects` API. Once the objects have been created, you can then assign them to the object tree inside a batch function.
+
+
+```javascript
+// First, create new objects outside the batch context
+const counter = await channel.objects.createCounter();
+const map = await channel.objects.createMap();
+
+// Then, use a batch to assign them atomically to the channel objects
+await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+ root.set('counter', counter);
+ root.set('map', map);
+});
+```
+
+
+### When to batch operations
+
+Usually, you don't need to use batching for objects operations. It is only useful in situations where a group of operations must be applied together to maintain consistency in application state, or when there are multiple mutation operations that you might want to apply at the same time to improve the UI experience.
+
+For example, in a task dashboard application, you might want to remove all tasks on a board in a single operation to prevent excessive UI updates that the user would otherwise experience.
+
+
+```javascript
+await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+ const tasks = root.get('tasks');
+
+ for (const key of reactions.keys()) {
+ reactions.remove(key);
+ }
+});
+```
+
+
+## Cancel batch operation
+
+To explicitly cancel a batch before it is applied, throw an error inside the batch function. This prevents any queued operations from being applied.
+
+
+```javascript
+await channel.objects.batch((ctx) => {
+ const root = ctx.getRoot();
+ root.set('foo', 'bar');
+
+ // Throwing an error prevents any queued operations from being applied.
+ throw new Error('Cancel batch');
+});
+```
+
+
+### Batch API cannot be used outside the callback function
+
+The Batch API provided by the batch context object cannot be used outside the callback function. Attempting to do so results in an error. This applies both to `BatchContext.getRoot()` and any object instances retrieved from it.
+
+
+```javascript
+let root;
+await channel.objects.batch((ctx) => {
+ root = ctx.getRoot();
+});
+
+// Calling any Batch API methods outside the batch callback
+// will throw an Error: Batch is closed.
+root.set('foo', 'bar');
+```
+
diff --git a/src/pages/docs/liveobjects/concepts/objects.mdx b/src/pages/docs/liveobjects/concepts/objects.mdx
new file mode 100644
index 0000000000..5063fdc1d7
--- /dev/null
+++ b/src/pages/docs/liveobjects/concepts/objects.mdx
@@ -0,0 +1,260 @@
+---
+title: Objects
+meta_description: "Learn how data is represented as objects in Ably LiveObjects"
+---
+
+
+
+LiveObjects enables you to store shared data as "objects" on a channel, allowing your application data to be synchronized across multiple users and devices in realtime. This document explains the key concepts you need to know when working with objects.
+
+## Object Types
+
+LiveObjects provides specialized object types to model your application state. These object types are designed to be conflict-free and eventually consistent, meaning that all operations on them are commutative and converge to the same state across all clients.
+
+### LiveMap Object
+
+[LiveMap](/docs/liveobjects/map) is a key/value data structure similar to a dictionary or JavaScript [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map):
+
+* Keys must be strings
+* Values can be primitive types or [references](#composability) to other objects
+* Supports `set` and `remove` operations
+* Concurrent updates to the same key are resolved using last-write-wins (LWW) semantics
+
+
+```javascript
+// Create a LiveMap
+const userSettings = await channel.objects.createMap();
+
+// Set primitive values
+await userSettings.set('theme', 'dark');
+await userSettings.set('notifications', true);
+```
+
+
+#### Primitive Types
+
+[LiveMap](/docs/liveobjects/map) supports the following primitive types as values:
+
+* `string`
+* `number`
+* `boolean`
+* `bytes`
+
+
+
+### LiveCounter Object
+
+[LiveCounter](/docs/liveobjects/counter) is a numeric counter type:
+
+* The value is a double-precision floating-point number
+* Supports `increment` and `decrement` operations
+
+
+```javascript
+// Create a LiveCounter
+const visitsCounter = await channel.objects.createCounter();
+
+// Increment the counter
+await visitsCounter.increment(1);
+```
+
+
+### Root Object
+
+The root object is a special `LiveMap` instance which:
+
+* Implicitly exists on a channel and does not need to be created explicitly
+* Has the special [objectId](#object-ids) of `root`
+* Cannot be deleted
+* Serves as the [entry point](#reachability) for accessing all other objects on a channel
+
+Access the root object using the `getRoot()` function:
+
+
+```javascript
+// Get the Root Object
+const root = await channel.objects.getRoot();
+
+// Use it like any other LiveMap
+await root.set('app-version', '1.0.0');
+```
+
+
+## Reachability
+
+All objects must be reachable from the root object (directly or indirectly). Objects that cannot be reached from the root object will eventually [be deleted](/docs/liveobjects/lifecycle#objects-deleted).
+
+When an object has been deleted, it is no longer usable and calling any methods on the object will fail.
+
+In the example below, the only reference to the `counterOld` object is replaced on the `root`. This makes `counterOld` unreachable and it will eventually be deleted.
+
+
+```javascript
+// Create a counter and reference it from the root
+const counterOld = await channel.objects.createCounter();
+await root.set('myCounter', counterOld);
+
+// counterOld will eventually be deleted
+counterOld.on('deleted', () => {
+ console.log('counterOld has been deleted and can no longer be used');
+});
+
+// Create a new counter and replace the old one referenced from the root
+const counterNew = await channel.objects.createCounter();
+await root.set('myCounter', counterNew);
+```
+
+
+
+
+## Composability
+
+LiveObjects enables you to build complex, hierarchical data structures through composability.
+
+Specifically, a [LiveMap](/docs/liveobjects/map) can store references to other `LiveMap` or `LiveCounter` object instances as values. This allows you to create nested hierarchies of data.
+
+
+```javascript
+// Create LiveObjects
+const profileMap = await channel.objects.createMap();
+const preferencesMap = await channel.objects.createMap();
+const activityCounter = await channel.objects.createCounter();
+
+// Build a composite structure
+await preferencesMap.set('theme', 'dark');
+await profileMap.set('preferences', preferencesMap);
+await profileMap.set('activity', activityCounter);
+await root.set('profile', profileMap);
+
+// Resulting structure:
+// root (LiveMap)
+// └── profile (LiveMap)
+// ├── preferences (LiveMap)
+// │ └── theme: "dark" (string)
+// └── activity (LiveCounter)
+```
+
+
+
+
+It is possible for the same object instance to be accessed from multiple places in your object tree:
+
+
+```javascript
+// Create a counter
+const counter = await channel.objects.createCounter();
+
+// Create two different maps
+const mapA = await channel.objects.createMap();
+const mapB = await channel.objects.createMap();
+await root.set('a', mapA);
+await root.set('b', mapB);
+
+// Reference the same counter from both maps
+await mapA.set('count', counter);
+await mapB.set('count', counter);
+
+// The counter referenced from each location shows the same
+// value, since they refer to the same underlying counter
+mapA.get('count').subscribe(() => {
+ console.log(mapA.get('count').value()); // 1
+});
+mapB.get('count').subscribe(() => {
+ console.log(mapB.get('count').value()); // 1
+});
+
+// Increment the counter
+await counter.increment(1);
+```
+
+
+It is also possible that object references form a cycle:
+
+
+```javascript
+// Create two different maps
+const mapA = await channel.objects.createMap();
+const mapB = await channel.objects.createMap();
+
+// Set up a circular reference
+await mapA.set('ref', mapB);
+await mapB.set('ref', mapA);
+
+// Add one map to root (both are now reachable)
+await root.set('a', mapA);
+
+// We can traverse the cycle
+root.get('a') // mapA
+ .get('ref') // mapB
+ .get('ref'); // mapA
+```
+
+
+## Metadata
+
+Objects include metadata that helps with synchronization, conflict resolution and managing the object lifecycle.
+
+
+
+### Object IDs
+
+Every object has a unique identifier that distinguishes it from all other objects.
+
+Object IDs follow a specific format:
+
+
+```javascript
+type:hash@timestamp
+```
+
+
+For example:
+
+
+```javascript
+counter:J7x6mAF8X5Ha60VBZb6GtXSgnKJQagNLgadUlgICjkk@1734628392000
+```
+
+
+This format has been specifically designed to ensure uniqueness in a globally distributed system and includes:
+
+| Part | Description |
+| ---- | ----------- |
+| type | The object type (either `map` or `counter`). |
+| hash | A base64 string encoded hash derived from the initial value of the object and a random nonce. |
+| timestamp | A Unix millisecond timestamp denoting the creation time of the object. |
+
+
+
+### Tombstones
+
+Tombstones are markers indicating an object or map entry has been deleted.
+
+* A tombstone is created for an object when it becomes [unreachable](/docs/liveobjects/concepts/objects#reachability) from the root object.
+* A tombstone is created for a map entry when it is [removed](/docs/liveobjects/map#remove)
+
+Tombstones protect against lagging clients from re-introducing a deleted value, ensuring all clients eventually converge on the same state. They are eventually garbage collected after a safe period of time.
+
+### Timeserials
+
+When an operation message is published it is assigned a unique logical timestamp called a "timeserial".
+
+This timeserial is stored on map entries in order to implement last-write-wins conflict resolution semantics.
+
+Additionally, all objects store the timeserial of the last operation that was applied to the object. Since Ably operates fully independent data centers, these timeserials are stored on a per-site basis.
+
+Timeserial metadata is used for internal purposes and is not directly exposed in client libraries. However, it can be viewed using the [REST API](/docs/liveobjects/rest-api-usage).
diff --git a/src/pages/docs/liveobjects/concepts/operations.mdx b/src/pages/docs/liveobjects/concepts/operations.mdx
new file mode 100644
index 0000000000..0ed44f76ff
--- /dev/null
+++ b/src/pages/docs/liveobjects/concepts/operations.mdx
@@ -0,0 +1,140 @@
+---
+title: Operations
+meta_description: "Learn how objects are updated by operations in Ably LiveObjects."
+---
+
+
+
+LiveObjects operations define how object data is updated and synchronized across multiple clients.
+
+When you create or update an object, the change is expressed as an _operation_ that is sent as an [object message](/docs/metadata-stats/stats#messages) on the channel. The operation is then applied to the object instance on all clients that are subscribed to the channel.
+
+This document explains the key concepts you need to know when working with operations.
+
+## Operation Types
+
+Each object type supports specific operations that modify the object's data.
+
+### LiveMap Operations
+
+[LiveMap](/docs/liveobjects/map) supports the following operations:
+
+* `set`: Set a value for a key
+* `remove`: Remove a key and its value
+
+The value of an entry in a `LiveMap` instance can be a [primitive type](/docs/liveobjects/concepts/objects#primitive-types) or a [reference](/docs/liveobjects/concepts/objects#composability) to another object.
+
+
+```javascript
+// Set a value for a key
+await map.set('username', 'alice');
+
+// Remove a key
+await map.remove('username');
+```
+
+
+### LiveCounter Operations
+
+[LiveCounter](/docs/liveobjects/counter) supports the following operations:
+
+* `increment`: Increment the counter by a specified amount
+* `decrement`: Decrement the counter by a specified amount
+
+The amount is a double-precision floating-point number, which is the same as underlying type of a [LiveCounter](/docs/liveobjects/concepts/objects#livecounter) value.
+
+
+
+
+```javascript
+// Increment counter by 5
+await counter.increment(5);
+
+// Decrement counter by 2
+await counter.decrement(2);
+```
+
+
+### Create Operations
+
+Create operations are used to instantiate new objects of a given type.
+
+A create operation can optionally specify an initial value for the object.
+
+
+```javascript
+// Create a map with initial values
+const userMap = await channel.objects.createMap({
+ username: 'alice',
+ status: 'online'
+});
+
+// Create a counter with initial value
+const scoreCounter = await channel.objects.createCounter(100);
+```
+
+
+When a create operation is processed, an [object ID](/docs/liveobjects/concepts/objects#object-ids) for the new object instance is automatically generated for the object.
+
+
+
+## Object IDs
+
+Every operation is expressed relative to a specific object instance, identified by its [object ID](/docs/liveobjects/concepts/objects#object-ids), which determines which object the operation is applied to.
+
+When using a client library object IDs are handled automatically, allowing you work directly with object references:
+
+
+```javascript
+// The published operation targets the object ID of the `userMap` object instance
+await userMap.set('username', 'alice');
+```
+
+
+Therefore it is important that you obtain an up-to-date object instance before performing operations on an object. For example, you can [subscribe](/docs/liveobjects/map#subscribe-data) to a `LiveMap` instance to ensure you always have an up-to-date reference to any child objects in the map:
+
+
+```javascript
+const root = await channel.objects.getRoot();
+
+let myCounter = root.get('myCounter');
+root.subscribe(() => { myCounter = root.get('myCounter'); });
+
+// before incrementing, ensure we have an up-to-date object reference if
+// the counter instance at the 'myCounter' key in the root map changes
+await myCounter.increment(1);
+```
+
+
+In the [REST API](/docs/liveobjects/rest-api-usage#updating-objects-by-id), this relationship is made explicit:
+
+
+```shell
+curl -X POST https://rest.ably.io/channels/my-channel/objects \
+ -u "{{API_KEY}}"
+ -H "Content-Type: application/json" \
+ --data \
+'{
+ "operation": "MAP_SET",
+ "objectId": "root",
+ "data": {"key": "username", "value": {"string": "alice"}}
+}'
+```
+
+
+## Batch Operations
+
+[Batch operations](/docs/liveobjects/batch) can be used to batch a set of operations together:
+
+* Multiple operations are grouped into a single atomic unit
+* All operations in the batch either succeed together or fail together
+* Operations in a batch are sent as a single message
+* No operations from other clients can be interleaved within a batch
diff --git a/content/liveobjects/concepts/synchronization.textile b/src/pages/docs/liveobjects/concepts/synchronization.mdx
similarity index 58%
rename from content/liveobjects/concepts/synchronization.textile
rename to src/pages/docs/liveobjects/concepts/synchronization.mdx
index 1b651b785d..ffe50ba02e 100644
--- a/content/liveobjects/concepts/synchronization.textile
+++ b/src/pages/docs/liveobjects/concepts/synchronization.mdx
@@ -1,38 +1,36 @@
---
title: Synchronization
meta_description: "Learn how data is synchronized between clients."
-product: liveobjects
-languages:
- - javascript
---
-
+
LiveObjects provides a powerful synchronization mechanism to ensure that all clients see the same data. This document explains how synchronization works in LiveObjects.
-h2(#channel-objects). Channel Objects
+## Channel Objects
Ably maintains the authoritative state of all objects on each channel across its distributed infrastructure.
-Each object instance is identified by a unique "object ID":/docs/liveobjects/concepts/objects#object-ids . The channel holds the complete up-to-date data of all objects on the channel.
+Each object instance is identified by a unique [object ID](/docs/liveobjects/concepts/objects#object-ids). The channel holds the complete up-to-date data of all objects on the channel.
Ably stores the object data durably such that the data is available even after the channel becomes inactive. The data is stored in multiple regional datacenters and across multiple availability zones. This ensures that the data is available even if there is disruption in one or more datacenters.
When a channel first becomes active in a region, the channel loads the object data from durable storage into memory to facilitate low-latency operation processing.
-h2(#client-objects). Client Objects
+## Client Objects
While Ably maintains the source of truth on the channel, each connected client keeps a local representation of the objects on the channel.
-When the client first attaches to the channel, the state of the channel objects is streamed to the client. "Lifecycle events":/docs/liveobjects/lifecycle#synchronization allow your application to be notified when the local state is being synchronized with the Ably service.
+When the client first attaches to the channel, the state of the channel objects is streamed to the client. [Lifecycle events](/docs/liveobjects/lifecycle#synchronization) allow your application to be notified when the local state is being synchronized with the Ably service.
All object operations published to the channel are broadcast to subscribed clients, which apply the operations to their local client objects when they are received. This allows clients to maintain a consistent view of the channel objects in a bandwidth-efficient way, since only the operations (rather than the updated objects themselves) are sent over the client's connection.
-
+
-If there is a loss of continuity on the channel for any reason, such as the client becoming disconnected for more than two minutes and entering the "suspended state":/docs/connect/states#connection-states , the client objects will automatically be resynchronized when it reconnects.
+If there is a loss of continuity on the channel for any reason, such as the client becoming disconnected for more than two minutes and entering the [suspended state](/docs/connect/states#connection-states), the client objects will automatically be resynchronized when it reconnects.
diff --git a/src/pages/docs/liveobjects/counter.mdx b/src/pages/docs/liveobjects/counter.mdx
new file mode 100644
index 0000000000..aabe8ce149
--- /dev/null
+++ b/src/pages/docs/liveobjects/counter.mdx
@@ -0,0 +1,181 @@
+---
+title: LiveCounter
+meta_description: "Create, update and receive updates for a numerical counter that synchronizes state across clients in realtime."
+---
+
+
+
+LiveCounter is a synchronized numerical counter that supports increment and decrement operations. It ensures that all updates are correctly applied and synchronized across users in realtime, preventing inconsistencies when multiple users modify the counter value simultaneously.
+
+## Create LiveCounter
+
+A `LiveCounter` instance can be created using the `channel.objects.createCounter()` method. It must be stored inside a `LiveMap` object that is reachable from the [root object](/docs/liveobjects/concepts/objects#root-object).
+
+`channel.objects.createCounter()` is asynchronous, as the client sends the create operation to the Ably system and waits for an acknowledgment of the successful counter creation.
+
+
+
+
+```javascript
+const counter = await channel.objects.createCounter();
+await root.set('counter', counter);
+```
+
+
+Optionally, you can specify an initial value when creating the counter:
+
+
+```javascript
+const counter = await channel.objects.createCounter(100); // Counter starts at 100
+```
+
+
+## Get counter value
+
+Get the current value of a counter using the `LiveCounter.value()` method:
+
+
+```javascript
+console.log('Counter value:', counter.value());
+```
+
+
+## Subscribe to data updates
+
+You can subscribe to data updates on a counter to receive realtime changes made by you or other clients.
+
+
+
+Subscribe to data updates on a counter using the `LiveCounter.subscribe()` method:
+
+
+```javascript
+counter.subscribe((update) => {
+ console.log('Counter updated:', counter.value());
+ console.log('Update details:', update);
+});
+```
+
+
+The update object provides details about the change, such as the amount by which the counter value was changed.
+
+Example structure of an update object when the counter was incremented by 5:
+
+
+```json
+{
+ "amount": 5
+}
+```
+
+
+Or decremented by 10:
+
+
+```json
+{
+ "amount": -10
+}
+```
+
+
+### Unsubscribe from data updates
+
+Use the `unsubscribe()` function returned in the `subscribe()` response to remove a counter update listener:
+
+
+```javascript
+// Initial subscription
+const { unsubscribe } = counter.subscribe(() => console.log(counter.value()));
+// To remove the listener
+unsubscribe();
+```
+
+
+Use the `LiveCounter.unsubscribe()` method to deregister a provided listener:
+
+
+```javascript
+// Initial subscription
+const listener = () => console.log(counter.value());
+counter.subscribe(listener);
+// To remove the listener
+counter.unsubscribe(listener);
+```
+
+
+Use the `LiveCounter.unsubscribeAll()` method to deregister all counter update listeners:
+
+
+```javascript
+counter.unsubscribeAll();
+```
+
+
+## Update LiveCounter
+
+Update the counter value by calling `LiveCounter.increment()` or `LiveCounter.decrement()`. These operations are synchronized across all clients and trigger data subscription callbacks for the counter, including on the client making the request.
+
+These operations are asynchronous, as the client sends the corresponding update operation to the Ably system and waits for acknowledgment of the successful counter update.
+
+
+```javascript
+await counter.increment(5); // Increase value by 5
+await counter.decrement(2); // Decrease value by 2
+```
+
+
+## Subscribe to lifecycle events
+
+Subscribe to lifecycle events on a counter using the `LiveCounter.on()` method:
+
+
+```javascript
+counter.on('deleted', () => {
+ console.log('Counter has been deleted');
+});
+```
+
+
+Read more about [objects lifecycle events](/docs/liveobjects/lifecycle#objects).
+
+### Unsubscribe from lifecycle events
+
+Use the `off()` function returned in the `on()` response to remove a lifecycle event listener:
+
+
+```javascript
+// Initial subscription
+const { off } = counter.on('deleted', () => console.log('Counter deleted'));
+// To remove the listener
+off();
+```
+
+
+Use the `LiveCounter.off()` method to deregister a provided lifecycle event listener:
+
+
+```javascript
+// Initial subscription
+const listener = () => console.log('Counter deleted');
+counter.on('deleted', listener);
+// To remove the listener
+counter.off('deleted', listener);
+```
+
+
+Use the `LiveCounter.offAll()` method to deregister all lifecycle event listeners:
+
+
+```javascript
+counter.offAll();
+```
+
diff --git a/src/pages/docs/liveobjects/inband-objects.mdx b/src/pages/docs/liveobjects/inband-objects.mdx
new file mode 100644
index 0000000000..2372f5e6e0
--- /dev/null
+++ b/src/pages/docs/liveobjects/inband-objects.mdx
@@ -0,0 +1,73 @@
+---
+title: Inband Objects
+meta_description: "Subscribe to LiveObjects updates from Pub/Sub SDKs."
+---
+
+
+
+Inband objects enables clients to subscribe to LiveObjects updates in realtime, even on platforms that don't yet have a native LiveObjects Realtime client implementation.
+
+
+
+Inband objects works by delivering changes to channel objects as regular channel messages, similar to [inband occupancy](/docs/channels/options#occupancy).
+
+## Enable Inband Objects
+
+To enable inband objects, use the `objects` [channel parameter](/docs/channels/options#objects) when getting a channel:
+
+
+```javascript
+// When getting a channel instance
+const channelOpts = { params: { objects: 'objects' } };
+const channel = realtime.channels.get('my-channel', channelOpts);
+
+// Or using setOptions on an existing channel
+await channel.setOptions({ params: { objects: 'objects' } });
+```
+
+
+
+
+## Subscribe to updates
+
+When using inband objects, the client will receive special `[meta]objects` messages whenever the objects on the channel are updated. These messages provide a snapshot of the current set of objects on the channel.
+
+
+
+[Subscribe](/docs/api/realtime-sdk/channels#subscribe) to `[meta]objects` messages like you would any other message on the channel. For convenience, use a message name filter to only receive messages with the name `[meta]objects` in your listener:
+
+
+```javascript
+// Subscribe to [meta]objects messages
+channel.subscribe('[meta]objects', (message) => {
+ const { syncId, nextCursor, object } = message.data;
+ console.log("Received inband objects message:", syncId, nextCursor, JSON.stringify(message.data));
+});
+```
+
+
+## Message Format
+
+Inband objects messages are sent as a sequence of messages, where each message contains a snapshot of a single object on the channel. Taken together, a set of messages belonging to the same sequence describes the complete set of objects on the channel.
+
+Each inband objects message has a message `name` of `[meta]objects`.
+
+The message `data` is a JSON object with the following top-level properties:
+
+| Property | Description |
+| -------- | ----------- |
+| `syncId` | A unique ID for this sequence. All messages with the same `syncId` are part of the same sequence of messages which describes the complete set of the objects on the channel. |
+| `nextCursor` | A cursor for the next message in the sequence, or `undefined` if this is the last message in the sequence. |
+| `object` | A JSON representation of the object included in the message. |
+
+The shape of the `object` is the same as the response format of an object when listing them via the [REST API](/docs/liveobjects/rest-api-usage#fetching-objects-list-values).
diff --git a/src/pages/docs/liveobjects/index.mdx b/src/pages/docs/liveobjects/index.mdx
new file mode 100644
index 0000000000..fe06e36f44
--- /dev/null
+++ b/src/pages/docs/liveobjects/index.mdx
@@ -0,0 +1,76 @@
+---
+title: About LiveObjects
+meta_description: "Learn about Ably LiveObjects, its features, use cases, and how it simplifies realtime state synchronization."
+---
+
+
+
+Ably LiveObjects provides a serverless, durable, and scalable way to create, update, and synchronize shared state across large numbers of connected clients at any scale.
+
+LiveObjects provides a global, durable, and conflict-free shared data layer built on Ably's [global platform](/docs/platform/architecture), so your application state stays perfectly synchronized in realtime without the need to build or manage complex infrastructure yourself.
+
+
+
+LiveObjects enables you to store shared data as "objects" on a [channel](/docs/channels). When an object is updated, changes are automatically propagated to all subscribed clients in realtime, ensuring everyone always sees the latest state.
+
+LiveObjects provides a simple, purpose-built API that handles realtime synchronization, persistence, and convergence behind the scenes. The result is a single logical view of your data - distributed to the edge, updated in real time, and always in sync - no matter how many users are connected or where they are in the world.
+
+## Use cases
+
+You can use LiveObjects to build all sorts of powerful functionality in your applications that require realtime updates to shared data. It is useful when your application has data that:
+
+* Is shared by multiple users or devices
+* Needs to be synchronized in realtime
+* Can be updated concurrently from multiple places
+
+Use Ably LiveObjects to build scalable realtime applications such as:
+
+* Voting and polling systems: Platforms that need the ability to count and display votes in realtime, such as audience engagement tools, quizzes, and decision-making applications.
+* Collaborative applications: Tools like shared whiteboards or content and product management applications where multiple users edit shared content simultaneously.
+* Live leaderboards: Multiplayer games or competition-based applications that require up-to-date rankings and scoreboards.
+* Game state: Applications that present dynamic in-game statistics or game state in realtime, such as player health, scores, and inventory changes.
+* Shared configuration, settings or controls: Systems where configuration parameters are shared or updated across multiple users or devices.
+
+## Features
+
+Ably LiveObjects provides the following key features:
+
+* [Object types](#object-types)
+* [Composability](#composability)
+* [Batch operations](#batch-operations)
+* [Inband objects](#inband-objects)
+* [Object storage](#object-storage)
+
+### Object types
+
+LiveObjects provides specialized object types to model your application state. These object types are designed to be conflict-free and eventually consistent, meaning that all operations on them are commutative and converge to the same state across all clients.
+
+#### LiveCounter
+
+[LiveCounter](/docs/liveobjects/counter) is a numerical counter that supports increment and decrement operations. It ensures that all updates are correctly applied and synchronized across users in realtime, preventing inconsistencies when multiple users modify the counter value simultaneously.
+
+#### LiveMap
+
+[LiveMap](/docs/liveobjects/map) is a key/value data structure that synchronizes its state across users in realtime. It enables you to store primitive values, such as numbers, strings, booleans and buffers, as well as other objects, enabling [composable data structures](#composability).
+
+### Composability
+
+LiveObjects enables you to build complex, hierarchical data structures through [composability](/docs/liveobjects/concepts/objects#composability).
+
+### Batch operations
+
+[Batch operations](/docs/liveobjects/batch) enables multiple operations to be grouped into a single channel message, ensuring atomic application of grouped operations. This prevents partial updates of your data and ensures consistency across all users.
+
+### Inband objects
+
+[Inband objects](/docs/liveobjects/inband-objects) enables clients to subscribe to LiveObjects updates in realtime, even on platforms that don't yet have a native LiveObjects Realtime client implementation.
+
+### Object storage
+
+LiveObjects [durably stores](/docs/liveobjects/storage) all objects on a channel for 90 days by default.
diff --git a/src/pages/docs/liveobjects/lifecycle.mdx b/src/pages/docs/liveobjects/lifecycle.mdx
new file mode 100644
index 0000000000..c45446bc1b
--- /dev/null
+++ b/src/pages/docs/liveobjects/lifecycle.mdx
@@ -0,0 +1,67 @@
+---
+title: Lifecycle events
+meta_description: "Understand lifecycle events for Objects, LiveMap and LiveCounter to track synchronization events and object deletions."
+---
+
+
+
+## Objects synchronization events
+
+The [`channel.objects`](/docs/api/realtime-sdk/channels#objects) instance emits synchronization events that indicate when the local state on the client is being synchronized with the Ably service. These events can be useful for displaying loading indicators, preventing user interactions during synchronization, or triggering application logic when data is first loaded.
+
+| Event | Description |
+| ----- | ----------- |
+| syncing | Emitted when the local copy of objects on a channel begins synchronizing with the Ably service. |
+| synced | Emitted when the local copy of objects on a channel has been synchronized with the Ably service. |
+
+
+```javascript
+channel.objects.on('syncing', () => {
+ console.log('Objects are syncing...');
+ // Show a loading indicator and disable edits in the application
+});
+
+channel.objects.on('synced', () => {
+ console.log('Objects have been synced.');
+ // Hide loading indicator
+});
+```
+
+
+
+
+## LiveMap/LiveCounter lifecycle events
+
+Lifecycle events enable you to monitor changes in an object's lifecycle.
+
+Currently, only the `deleted` event can be emitted. Understanding the conditions under which this event is emitted and handling it properly ensures that your application maintains expected behavior.
+
+### deleted event
+
+Objects that were created on a channel can become orphaned when they were never assigned to the object tree, or because their reference was removed using [`LiveMap.remove()`](/docs/liveobjects/map#remove) and never reassigned. Orphaned objects will be garbage collected by Ably, typically after 24 hours. When this happens, a `deleted` event is broadcast for the affected object. Once deleted, an object can no longer be interacted with, and any operations performed on it will result in an error.
+
+While the LiveObjects feature internally manages object deletions and removes them from its internal state, your application may still hold references to these deleted objects in separate data structures. The `deleted` event provides a way to react accordingly by removing references to deleted objects and preventing potential errors.
+
+In most cases, subscribing to `deleted` events is unnecessary. Your application should have already reacted to object removal when a corresponding [`LiveMap.remove()`](/docs/liveobjects/map#remove) operation was received. However, if your application separately stores references to object instances and does not properly clear them when objects are orphaned, any later interactions with those objects after they are deleted will result in an error. In such cases, subscribing to `deleted` events helps ensure that those references are cleaned up and runtime errors are avoided.
+
+
+
+
+```javascript
+const { off } = counter.on('deleted', () => {
+ console.log('LiveCounter has been deleted.');
+ // Remove references to this object from your application
+ // as it can no longer be interacted with
+});
+```
+
+
+Read more about subscribing to object lifecycle events for [LiveCounter](/docs/liveobjects/counter#subscribe-lifecycle) and [LiveMap](/docs/liveobjects/map#subscribe-lifecycle).
diff --git a/src/pages/docs/liveobjects/map.mdx b/src/pages/docs/liveobjects/map.mdx
new file mode 100644
index 0000000000..1d048dc8fb
--- /dev/null
+++ b/src/pages/docs/liveobjects/map.mdx
@@ -0,0 +1,237 @@
+---
+title: LiveMap
+meta_description: "Create, update and receive updates for a key/value data structure that synchronizes state across clients in realtime."
+---
+
+
+
+LiveMap is a key/value data structure that synchronizes its state across users in realtime. It enables you to store primitive values, such as numbers, strings, booleans and buffers, as well as other objects, [enabling you to build complex, hierarchical object structure](#composability).
+
+Conflicts in a LiveMap are automatically resolved with last-write-wins (LWW) semantics. The latest received operation on a key will be applied to the LiveMap and broadcast to all clients.
+
+## Create LiveMap
+
+A `LiveMap` instance can be created using the `channel.objects.createMap()` method. It must be stored inside another `LiveMap` object that is reachable from the [root object](/docs/liveobjects/concepts/objects#root-object).
+
+`channel.objects.createMap()` is asynchronous, as the client sends the create operation to the Ably system and waits for an acknowledgment of the successful map creation.
+
+
+
+
+```javascript
+const map = await channel.objects.createMap();
+await root.set('myMap', map);
+```
+
+
+Optionally, you can specify an initial key/value structure when creating the map:
+
+
+```javascript
+// Pass a regular JavaScript object reflecting the initial state
+const map = await channel.objects.createMap({ foo: 'bar', baz: 42 });
+// You can also pass other objects as values for keys
+await channel.objects.createMap({ nestedMap: map });
+```
+
+
+## Get value for a key
+
+Get the current value for a key in a map using the `LiveMap.get()` method:
+
+
+```javascript
+console.log('Value for my-key:', map.get('my-key'));
+```
+
+
+## Subscribe to data updates
+
+You can subscribe to data updates on a map to receive realtime changes made by you or other clients.
+
+
+
+Subscribe to data updates on a map using the `LiveMap.subscribe()` method:
+
+
+```javascript
+map.subscribe((update) => {
+ console.log('Map updated:', [...map.entries()]);
+ console.log('Update details:', update);
+});
+```
+
+
+The update object provides details about the change, listing the keys that were changed and indicating whether they were updated (value changed) or removed from the map.
+
+Example structure of an update object when the key `foo` is updated and the key `bar` is removed:
+
+
+```json
+{
+ "foo": "updated",
+ "bar": "removed"
+}
+```
+
+
+### Unsubscribe from data updates
+
+Use the `unsubscribe()` function returned in the `subscribe()` response to remove a map update listener:
+
+
+```javascript
+// Initial subscription
+const { unsubscribe } = map.subscribe(() => console.log('Map updated'));
+// To remove the listener
+unsubscribe();
+```
+
+
+Use the `LiveMap.unsubscribe()` method to deregister a provided listener:
+
+
+```javascript
+// Initial subscription
+const listener = () => console.log('Map updated');
+map.subscribe(listener);
+// To remove the listener
+map.unsubscribe(listener);
+```
+
+
+Use the `LiveMap.unsubscribeAll()` method to deregister all map update listeners:
+
+
+```javascript
+map.unsubscribeAll();
+```
+
+
+## Set keys in a LiveMap
+
+Set a value for a key in a map by calling `LiveMap.set()`. This operation is synchronized across all clients and triggers data subscription callbacks for the map, including on the client making the request.
+
+Keys in a map can contain numbers, strings, booleans and buffers, as well as other `LiveMap` and `LiveCounter` objects.
+
+This operation is asynchronous, as the client sends the corresponding update operation to the Ably system and waits for acknowledgment of the successful map key update.
+
+
+```javascript
+await map.set('foo', 'bar');
+await map.set('baz', 42);
+
+// Can also set a reference to another object
+const counter = await channel.objects.createCounter();
+await map.set('counter', counter);
+```
+
+
+## Remove a key from a LiveMap
+
+Remove a key from a map by calling `LiveMap.remove()`. This operation is synchronized across all clients and triggers data subscription callbacks for the map, including on the client making the request.
+
+This operation is asynchronous, as the client sends the corresponding remove operation to the Ably system and waits for acknowledgment of the successful map key removal.
+
+
+```javascript
+await map.remove('foo');
+```
+
+
+## Iterate over key/value pairs
+
+Iterate over key/value pairs, keys or values using the `LiveMap.entries()`, `LiveMap.keys()` and `LiveMap.values()` methods respectively.
+
+These methods return a [map iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator) object for convenient traversal. Note that contrary to JavaScript's [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) counterpart, these methods do not guarantee that entries are returned in insertion order.
+
+
+```javascript
+for (const [key, value] of map.entries()) {
+ console.log(`Key: ${key}, Value: ${value}`);
+}
+
+for (const key of map.keys()) {
+ console.log(`Key: ${key}`);
+}
+
+for (const value of map.values()) {
+ console.log(`Value: ${value}`);
+}
+```
+
+
+## Subscribe to lifecycle events
+
+Subscribe to lifecycle events on a map using the `LiveMap.on()` method:
+
+
+```javascript
+map.on('deleted', () => {
+ console.log('Map has been deleted');
+});
+```
+
+
+Read more about [objects lifecycle events](/docs/liveobjects/lifecycle#objects).
+
+### Unsubscribe from lifecycle events
+
+Use the `off()` function returned in the `on()` response to remove a lifecycle event listener:
+
+
+```javascript
+// Initial subscription
+const { off } = map.on('deleted', () => console.log('Map deleted'));
+// To remove the listener
+off();
+```
+
+
+Use the `LiveMap.off()` method to deregister a provided lifecycle event listener:
+
+
+```javascript
+// Initial subscription
+const listener = () => console.log('Map deleted');
+map.on('deleted', listener);
+// To remove the listener
+map.off('deleted', listener);
+```
+
+
+Use the `LiveMap.offAll()` method to deregister all lifecycle event listeners:
+
+
+```javascript
+map.offAll();
+```
+
+
+## Composability
+
+A `LiveMap` can store other `LiveMap` or `LiveCounter` objects as values for its keys, enabling you to build complex, hierarchical object structure. This enables you to represent complex data models in your applications while ensuring realtime synchronization across clients.
+
+
+```javascript
+// Create a hierarchy of objects using LiveMap
+const counter = await channel.objects.createCounter();
+const map = await channel.objects.createMap({ nestedCounter: counter });
+const outerMap = await channel.objects.createMap({ nestedMap: map });
+await root.set('outerMap', outerMap);
+
+// resulting structure:
+// root (LiveMap)
+// └── outerMap (LiveMap)
+// └── nestedMap (LiveMap)
+// └── nestedCounter (LiveCounter)
+```
+
diff --git a/src/pages/docs/liveobjects/quickstart.mdx b/src/pages/docs/liveobjects/quickstart.mdx
new file mode 100644
index 0000000000..0baa04d68b
--- /dev/null
+++ b/src/pages/docs/liveobjects/quickstart.mdx
@@ -0,0 +1,194 @@
+---
+title: Quickstart
+meta_description: "A quickstart guide to learn the basics of integrating the Ably LiveObjects product into your application."
+---
+
+
+
+This guide shows how to integrate Ably LiveObjects into your application.
+
+You will learn how to:
+
+* Create an Ably account and get an API key for authentication.
+* Install the Ably Pub/Sub SDK.
+* Create a channel with LiveObjects functionality enabled.
+* Create, update and subscribe to changes on LiveObjects data structures: [LiveMap](/docs/liveobjects/map) and [LiveCounter](/docs/liveobjects/counter).
+
+## Authentication
+
+An [API key](/docs/auth#api-keys) is required to authenticate with Ably. API keys are used either to authenticate directly with Ably using [basic authentication](/docs/auth/basic), or to generate tokens for untrusted clients using [token authentication](/docs/auth/token).
+
+
+
+[Sign up](https://ably.com/sign-up) for a free account and create your own API key in the [dashboard](https://ably.com/dashboard) or use the [Control API](/docs/account/control-api) to create an API key programmatically.
+
+API keys and tokens have a set of [capabilities](/docs/auth/capabilities) assigned to them that specify which operations can be performed on which resources. The following capabilities are available for LiveObjects:
+
+* `object-subscribe` - grants clients read access to LiveObjects, allowing them to get the root object and subscribe to updates.
+* `object-publish` - grants clients write access to LiveObjects, allowing them to perform mutation operations on objects.
+
+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 calling mutation methods on LiveObjects.
+
+For the purposes of this guide, make sure your API key includes both `object-subscribe` and `object-publish` [capabilities](/docs/auth/capabilities) to allow full read and write access.
+
+## Install Ably Pub/Sub SDK
+
+LiveObjects is available as part of the Ably Pub/Sub SDK via the dedicated Objects plugin.
+
+### NPM
+
+Install the Ably Pub/Sub SDK as an [NPM module](https://www.npmjs.com/package/ably):
+
+
+```shell
+npm install ably
+```
+
+
+Import the SDK and the Objects plugin into your project:
+
+
+```javascript
+import * as Ably from 'ably';
+import Objects from 'ably/objects';
+```
+
+
+### CDN
+
+Reference the Ably Pub/Sub SDK and the Objects plugin within your HTML file:
+
+
+```javascript
+
+
+
+```
+
+
+## Instantiate a client
+
+Instantiate an Ably Realtime client from the Pub/Sub SDK, providing the Objects plugin:
+
+
+```javascript
+const realtimeClient = new Ably.Realtime({ key: '{{API_KEY}}', plugins: { Objects } });
+```
+
+
+A [`ClientOptions`](/docs/api/realtime-sdk#client-options) object may be passed to the Pub/Sub SDK instance to further customize the connection, however at a minimum you must set an API key and provide an `Objects` plugin so that the client can use LiveObjects functionality.
+
+## Create a channel
+
+LiveObjects is managed and persisted on [channels](/docs/channels). To use LiveObjects, you must first create a channel with the correct [channel mode flags](/docs/channels/options#modes):
+
+* `OBJECT_SUBSCRIBE` - required to access objects on a channel.
+* `OBJECT_PUBLISH` - required to create and modify objects on a channel.
+
+
+
+
+```javascript
+const channelOptions = { modes: ['OBJECT_SUBSCRIBE', 'OBJECT_PUBLISH'] };
+const channel = realtimeClient.channels.get('my_liveobjects_channel', channelOptions);
+```
+
+
+Next, you need to [attach to the channel](/docs/channels/states). Attaching to a channel starts an initial synchronization sequence where the objects on the channel are sent to the client.
+
+
+```javascript
+await channel.attach();
+```
+
+
+## Get root object
+
+The [`channel.objects`](/docs/api/realtime-sdk/channels#objects) property gives access to the LiveObjects API for a channel.
+
+Use it to get the root object, which is the entry point for accessing and persisting objects on a channel. The root object is a [`LiveMap`](/docs/liveobjects/map) instance that always exists on a channel and acts as the top-level node in your object tree. You can get the root object using the `getRoot()` function of LiveObjects:
+
+
+```javascript
+// The promise resolves once the LiveObjects state is synchronized with the Ably system
+const root = await channel.objects.getRoot();
+```
+
+
+## Create objects
+
+You can create new objects using dedicated functions of the LiveObjects API at [`channel.objects`](/docs/api/realtime-sdk/channels#objects). To persist them on a channel and share them between clients, you must assign objects to a parent `LiveMap` instance connected to the root object. The root object itself is a `LiveMap` instance, so you can assign objects to the root and start building your object tree from there.
+
+
+
+
+```javascript
+const visitsCounter = await channel.objects.createCounter();
+const reactionsMap = await channel.objects.createMap();
+
+await root.set('visits', visitsCounter);
+await root.set('reactions', reactionsMap);
+```
+
+
+## Subscribe to updates
+
+Subscribe to realtime updates to objects on a channel. You will be notified when an object is updated by other clients or by you.
+
+
+```javascript
+visitsCounter.subscribe(() => {
+ console.log('Visits counter updated:', visitsCounter.value());
+});
+
+reactionsMap.subscribe(() => {
+ console.log('Reactions map updated:', [...reactionsMap.entries()]);
+});
+```
+
+
+## Update objects
+
+Update objects using mutation methods. All subscribers (including you) will be notified of the changes when you update an object:
+
+
+```javascript
+await visitsCounter.increment(5);
+// console: "Visits counter updated: 5"
+await visitsCounter.decrement(2);
+// console: "Visits counter updated: 3"
+
+await reactionsMap.set('like', 10);
+// console: "Reactions map updated: [['like',10]]"
+await reactionsMap.set('love', 5);
+// console: "Reactions map updated: [['like',10],['love',5]]"
+await reactionsMap.remove('like');
+// console: "Reactions map updated: [['love',5]]"
+```
+
+
+
+
+## Next steps
+
+This quickstart introduced the basic concepts of LiveObjects and demonstrated how it works. The next steps are to:
+
+* Read more about [LiveCounter](/docs/liveobjects/counter) and [LiveMap](/docs/liveobjects/map).
+* Learn about [Batching Operations](/docs/liveobjects/batch).
+* Learn about [Objects Lifecycle Events](/docs/liveobjects/lifecycle).
+* Add [Typings](/docs/liveobjects/typing) for your LiveObjects.
diff --git a/content/liveobjects/rest-api-usage.textile b/src/pages/docs/liveobjects/rest-api-usage.mdx
similarity index 67%
rename from content/liveobjects/rest-api-usage.textile
rename to src/pages/docs/liveobjects/rest-api-usage.mdx
index e45e574fc5..2142c7cb23 100644
--- a/content/liveobjects/rest-api-usage.textile
+++ b/src/pages/docs/liveobjects/rest-api-usage.mdx
@@ -1,59 +1,65 @@
---
title: Using the REST API
meta_description: "Learn how to work with Ably LiveObjects using the REST API"
-product: liveobjects
---
-
+
LiveObjects provides a comprehensive REST API that allows you to directly work with objects without using a client SDK.
-h2(#authentication). Authentication
+## Authentication
-View the REST API "authentication":/docs/api/rest-api#authentication documentation for details on how to authenticate your requests.
+View the REST API [authentication](/docs/api/rest-api#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 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.
+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.
-h2(#data-values). Data values
+## Data values
-When working with objects via the REST API, "primitive types":/docs/liveobjects/concepts/objects#primitive-types and "object references":/docs/liveobjects/concepts/objects#composability are included in request and response bodies under @data@ fields.
+When working with objects via the REST API, [primitive types](/docs/liveobjects/concepts/objects#primitive-types) and [object references](/docs/liveobjects/concepts/objects#composability) are included in request and response bodies under `data` fields.
-The key in the @data@ object indicates the type of the value:
+The key in the `data` object indicates the type of the value:
-```[json]
+
+```json
{ "data": { "number" : 42 }}
{ "data": { "string" : "LiveObjects is awesome" }}
{ "data": { "boolean" : true }}
{ "data": { "bytes": "TGl2ZU9iamVjdHMgaXMgYXdlc29tZQo=" }}
{ "data": { "objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669" }}
```
+
+
+
-
+## Fetching objects
-h2(#fetching-objects). Fetching objects
+### List objects
-h3(#fetching-objects-list). List objects
+#### GET rest.ably.io/channels/``/objects
-h6. GET rest.ably.io/channels/@@/objects
Fetch a flat list of objects on the channel:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
The response includes the IDs of the objects on the channel:
-```[json]
+
+```json
[
"counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669"
"counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269",
@@ -61,18 +67,22 @@ The response includes the IDs of the objects on the channel:
"root",
]
```
+
-h4(#fetching-objects-list-values). Including values
+##### Including values
-To include values of the objects in the response, set the @values=true@ query parameter:
+To include values of the objects in the response, set the `values=true` query parameter:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects?values=true" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
[
{
"objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669",
@@ -121,18 +131,22 @@ To include values of the objects in the response, set the @values=true@ query pa
}
]
```
+
-h4(#fetching-objects-list-metadata). Including metadata
+##### Including metadata
-You can optionally include additional object "metadata":/docs/liveobjects/concepts/objects#metadata with the @metadata@ query option:
+You can optionally include additional object [metadata](/docs/liveobjects/concepts/objects#metadata) with the `metadata` query option:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects?values=true&metadata=true" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
[
{
"objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669",
@@ -205,24 +219,28 @@ You can optionally include additional object "metadata":/docs/liveobjects/concep
}
]
```
+
-
+
-h4(#fetching-objects-list-pagination). Pagination
+##### Pagination
-The response can be "paginated":/docs/api/rest-api#pagination with @cursor@ and @limit@ query params using relative links.
+The response can be [paginated](/docs/api/rest-api#pagination) with `cursor` and `limit` query params using relative links.
-Use the @limit@ query parameter to specify the maximum number of objects to include in the response:
+Use the `limit` query parameter to specify the maximum number of objects to include in the response:
-```[sh]
+
+```shell
curl -v -X GET "https://rest.ably.io/channels/my-channel/objects?values=true&limit=2" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
[
{
"objectId": "counter:JbZYiHnw0ORAyzzLSQahVik31iBDL_ehJNpTEF3qwg8@1745828651669",
@@ -242,24 +260,30 @@ Use the @limit@ query parameter to specify the maximum number of objects to incl
}
]
```
+
-The response includes @Link@ headers which provide relative links to the first, current and next pages of the response:
+The response includes `Link` headers which provide relative links to the first, current and next pages of the response:
-```
+
+```json
link: ; rel="first"
link: ; rel="current"
link: ; rel="next"
```
+
-The list objects endpoints returns objects ordered lexicographically by object ID. The object ID of the first object in the next page is used as the @cursor@ value for the next request:
+The list objects endpoints returns objects ordered lexicographically by object ID. The object ID of the first object in the next page is used as the `cursor` value for the next request:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects?cursor=map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519&limit=2&values=true" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
[
{
"objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519",
@@ -292,24 +316,28 @@ The list objects endpoints returns objects ordered lexicographically by object I
}
]
```
+
-h3(#fetching-objects-get). Get objects
+### Get objects
-h6. GET rest.ably.io/channels/@@/objects/@@
+#### GET rest.ably.io/channels/``/objects/``
-h4(#fetching-objects-get-single). Get a single object
+##### Get a single object
To fetch a single object on the channel, specify the object ID in the URL path:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects/root" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
The response contains a single object referencing any nested child objects by their object ID:
-```[json]
+
+```json
{
"objectId": "root",
"map": {
@@ -321,20 +349,24 @@ The response contains a single object referencing any nested child objects by th
}
}
```
+
-h4(#fetching-objects-get-children). Get an object and its children
+##### Get an object and its children
-To fetch the objects on the channel in a tree structure use the @children@ query parameter:
+To fetch the objects on the channel in a tree structure use the `children` query parameter:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
The response includes the object tree starting from the specified object ID. Nested child objects are resolved and their values are included in the response:
-```[json]
+
+```json
{
"objectId": "root",
"map": {
@@ -372,20 +404,24 @@ The response includes the object tree starting from the specified object ID. Nes
}
}
```
+
-Use @root@ as the object ID in the URL to get the full object tree, or any other object ID to fetch a subset of the tree using that object as the entrypoint.
+Use `root` as the object ID in the URL to get the full object tree, or any other object ID to fetch a subset of the tree using that object as the entrypoint.
-h4(#fetching-objects-get-metadata). Including metadata
+##### Including metadata
-You can optionally include additional object "metadata":/docs/liveobjects/concepts/objects#metadata for all objects included in the response with the @metadata@ query option:
+You can optionally include additional object [metadata](/docs/liveobjects/concepts/objects#metadata) for all objects included in the response with the `metadata` query option:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true&metadata=true" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
{
"objectId": "root",
"map": {
@@ -447,18 +483,24 @@ You can optionally include additional object "metadata":/docs/liveobjects/concep
"tombstone": false
}
```
+
-h4(#fetching-objects-get-pagination). Pagination
+##### Pagination
-The tree-structured response can be paginated using the @limit@ parameter to specify the maximum number of objects to include in the response. If a nested child object exists which cannot be included in the response because the limit has been reached, it will be included by reference to its object ID rather than its value:
+When fetching objects in a tree structure, the response can be paginated using the `limit` query parameter to specify the maximum number of objects to include in the response.
-```[sh]
+The tree-structured response can be paginated using the `limit` parameter to specify the maximum number of objects to include in the response. If a nested child object exists which cannot be included in the response because the limit has been reached, it will be included by reference to its object ID rather than its value:
+
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true&limit=1" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
{
"objectId": "root",
"map": {
@@ -472,16 +514,20 @@ The tree-structured response can be paginated using the @limit@ parameter to spe
}
}
```
+
To obtain the next page, make a subsequent query specifying the referenced object ID as the entrypoint:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects/map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519?children=true&limit=1" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
{
"objectId": "map:ja7cjMUib2LmJKTRdoGAG9pbBYCnkMObAVpmojCOmek@1745828596519",
"map": {
@@ -500,14 +546,18 @@ To obtain the next page, make a subsequent query specifying the referenced objec
}
}
```
+
+
+##### Cyclic references
-h4(#fetching-objects-get-cycles). Cyclic references
+When fetching objects in a tree structure, cyclic references in the object tree are handled by including a reference to the object ID rather than including the same object in the response more than once.
-When using the @children@ query parameter, cyclic references in the object tree will be included as a reference to the object ID rather than including the same object in the response more than once.
+When using the `children` query parameter, cyclic references in the object tree will be included as a reference to the object ID rather than including the same object in the response more than once.
-For example, if we created a cycle in the object tree by adding a reference to the root object in the @votes@ @LiveMap@ instance with the following operation:
+For example, if we created a cycle in the object tree by adding a reference to the root object in the `votes` `LiveMap` instance with the following operation:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -517,16 +567,20 @@ For example, if we created a cycle in the object tree by adding a reference to t
"data": {"key": "myRoot", "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 root object:
+The response will handle the cyclic reference by including the `myRoot` key in the response as a reference to the object ID of the root object:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects/root?children=true" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
-```[json]
+
+```json
{
"objectId": "root",
"map": {
@@ -569,22 +623,26 @@ The response will handle the cyclic reference by including the @myRoot@ key in t
}
}
```
+
-h3(#fetching-objects-compact). Get a compact view of objects
+### Get a compact view of objects
-h6. GET rest.ably.io/channels/@@/objects/@@/compact
+#### GET rest.ably.io/channels/``/objects/``/compact
To fetch the objects on the channel in a tree structure in a concise format:
-```[sh]
+
+```shell
curl -X GET "https://rest.ably.io/channels/my-channel/objects/root/compact" \
-u {{API_KEY}}
-H "Content-Type: application/json"
```
+
The response includes a compact representation of the object tree that is easy to read:
-```[json]
+
+```json
{
"votes": {
"up": 5,
@@ -592,15 +650,17 @@ The response includes a compact representation of the object tree that is easy t
}
}
```
+
When using the compact format:
-* @LiveMap@ instances will be represented as a JSON representation of its entries
-* @LiveCounter@ instances will be represented as its numeric value
+* `LiveMap` instances will be represented as a JSON representation of its entries
+* `LiveCounter` instances will be represented as its numeric value
-"Cyclic references":#fetching-objects-get-cycles are handled in the same way as for the tree-structured response. In the example below, the @myRoot@ key references the root object, which is already included in the response:
+[Cyclic references](#fetching-objects-get-cycles) are handled in the same way as for the tree-structured response. In the example below, the `myRoot` key references the root object, which is already included in the response:
-```[json]
+
+```json
{
"votes": {
"up": 5,
@@ -609,16 +669,17 @@ When using the compact format:
}
}
```
+
-The compact format inlines object ID references under the @objectId@ key, allowing references to other objects to be differentiated from string values.
+The compact format inlines object ID references under the `objectId` key, allowing references to other objects to be differentiated from string values.
-
+
-h2(#updating-objects). Publishing operations
+## Publishing operations
-h6. POST rest.ably.io/channels/@@/objects
+#### POST rest.ably.io/channels/``/objects
All operations are published to the same endpoint. The request body specifies:
@@ -628,7 +689,8 @@ All operations are published to the same endpoint. The request body specifies:
The request body is of the form:
-```[json]
+
+```json
{
"operation": "",
"objectId": "",
@@ -636,28 +698,32 @@ The request body is of the form:
"data": ""
}
```
+
-The @operationType@ is a string that specifies the type of operation to publish and must be one of:
+The `operationType` is a string that specifies the type of operation to publish and must be one of:
-* @MAP_CREATE@
-* @MAP_SET@
-* @MAP_REMOVE@
-* @COUNTER_CREATE@
-* @COUNTER_INC@
+| Operation | Description |
+| --------- | ----------- |
+| `MAP_CREATE` | Creates LiveMap object. |
+| `MAP_SET` | Set the value of a key in a LiveMap object. |
+| `MAP_REMOVE` | Tombstone a key in a LiveMap object. |
+| `COUNTER_CREATE` | Creates a LiveCounter object. |
+| `COUNTER_INC` | Increments or decrements the value of a LiveCounter object. |
-
+
-Either the @objectId@ or @path@ fields are used to specify the target object(s) for the operation.
+Either the `objectId` or `path` fields are used to specify the target object(s) for the operation.
-The operation payload is provided in the @data@ in accordance with the specified operation type.
+The operation payload is provided in the `data` in accordance with the specified operation type.
-h3(#updating-objects-by-id). Update a specific object instance
+### Update a specific object instance
To perform operations on a specific object instance, you need to provide its object ID in the request body:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -667,28 +733,32 @@ To perform operations on a specific object instance, you need to provide its obj
"data": {"number":1}
}'
```
+
The response includes the ID of the published operation message, the channel and a list of object IDs that were affected by the operation:
-```[json]
+
+```json
{
"messageId": "TJPWHhMTrF:0",
"channel": "my-channel",
"objectIds": ["counter:iVji62_MW_j4dShuJbr2fmsP2D8MyCs6tFqON9-xAkc@1745828645269"]
}
```
+
-h3(#updating-objects-by-path). Update an object by path
+### Update an object by path
Path operations provide a convenient way to target objects based on their location in the object tree.
-Paths are expressed relative to the structure of the object as defined by the "compact":#fetching-objects-compact view of the object tree.
+Paths are expressed relative to the structure of the object as defined by the [compact](#fetching-objects-compact) view of the object tree.
For example, given the following compact view of the object tree:
-The following example increments the @LiveCounter@ instance stored at the @up@ key on the @votes@ @LiveMap@ instance on the root object:
+The following example increments the `LiveCounter` instance stored at the `up` key on the `votes` `LiveMap` instance on the root object:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -698,15 +768,17 @@ The following example increments the @LiveCounter@ instance stored at the @up@ k
"data": { "number": 1 }
}'
```
+
-
+
-You can use wildcards in paths to target multiple objects at once. To increment all @LiveCounter@ instances in the @votes@ @LiveMap@ instance:
+You can use wildcards in paths to target multiple objects at once. To increment all `LiveCounter` instances in the `votes` `LiveMap` instance:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -716,10 +788,12 @@ You can use wildcards in paths to target multiple objects at once. To increment
"data": { "number": 1 }
}'
```
+
The response includes the IDs of each of the affected object instances:
-```[json]
+
+```json
{
"messageId": "0Q1w-LpA11:0",
"channel": "my-channel",
@@ -729,10 +803,12 @@ The response includes the IDs of each of the affected object instances:
]
}
```
+
Wildcards can be included at the end or in the middle of paths and will match exactly one level in the object tree. For example, given the following compact view of the object tree:
-```[json]
+
+```json
{
"posts": {
"post1": {
@@ -750,10 +826,12 @@ Wildcards can be included at the end or in the middle of paths and will match ex
}
}
```
+
-The following example increments the upvote @LiveCounter@ instances for all posts in the @posts@ @LiveMap@ instance:
+The following example increments the upvote `LiveCounter` instances for all posts in the `posts` `LiveMap` instance:
-```[sh]
+
+```shell
curl -X POST "https://sandbox-rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -763,10 +841,12 @@ The following example increments the upvote @LiveCounter@ instances for all post
"data": { "number": 1 }
}'
```
+
-If your @LiveMap@ keys contain periods, you can escape them with a backslash. The following example increments the upvote @LiveCounter@ instance for a post with the key @post.123@:
+If your `LiveMap` keys contain periods, you can escape them with a backslash. The following example increments the upvote `LiveCounter` instance for a post with the key `post.123`:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -776,14 +856,16 @@ If your @LiveMap@ keys contain periods, you can escape them with a backslash. Th
"data": { "number": 1 }
}'
```
+
-h3(#creating-objects). Creating objects
+### Creating objects
-Use the @MAP_CREATE@ and @COUNTER_CREATE@ operations to create new objects. You can optionally specify an initial value for the object in the @data@ field when creating it.
+Use the `MAP_CREATE` and `COUNTER_CREATE` operations to create new objects. You can optionally specify an initial value for the object in the `data` field when creating it.
-For @MAP_CREATE@, the @data@ field should be a JSON object that contains the initial entries for the @LiveMap@ instance:
+For `MAP_CREATE`, the `data` field should be a JSON object that contains the initial entries for the `LiveMap` instance:
-```[json]
+
+```json
{
"operation": "MAP_CREATE",
"data": {
@@ -793,23 +875,27 @@ For @MAP_CREATE@, the @data@ field should be a JSON object that contains the ini
}
}
```
+
-For @COUNTER_CREATE@, the @data@ field should be a JSON object that contains the initial value for the @LiveCounter@ instance:
+For `COUNTER_CREATE`, the `data` field should be a JSON object that contains the initial value for the `LiveCounter` instance:
-```[json]
+
+```json
{
"operation": "COUNTER_CREATE",
"data": { "number": 5 }
}
```
+
-When you create a new object it is important that the new object is assigned to the object tree so that it is "reachable":/docs/liveobjects/concepts/objects#reachability from the root object.
+When you create a new object it is important that the new object is assigned to the object tree so that it is [reachable](/docs/liveobjects/concepts/objects#reachability) from the root object.
-The simplest way to do this is to use the @path@ field in the request body. The path is relative to the root object and specifies where in the object tree the new object should be created.
+The simplest way to do this is to use the `path` field in the request body. The path is relative to the root object and specifies where in the object tree the new object should be created.
-The following example creates a new @LiveMap@ instance and assigns it to the @posts@ @LiveMap@ instance on the root object under the key @post1@:
+The following example creates a new `LiveMap` instance and assigns it to the `posts` `LiveMap` instance on the root object under the key `post1`:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -823,15 +909,17 @@ The following example creates a new @LiveMap@ instance and assigns it to the @po
}
}'
```
+
-When using the @path@ specifier with a @COUNTER_CREATE@ or @MAP_CREATE@ operation, the server constructs _two_ operations which are published as a "batch":#batch-operations :
+When using the `path` specifier with a `COUNTER_CREATE` or `MAP_CREATE` operation, the server constructs *two* operations which are published as a [batch](#batch-operations):
-* A @MAP_CREATE@ or @COUNTER_CREATE@ operation used to create the new object
-* A @MAP_SET@ operation used to assign the new object to the @LiveMap@ instance specified by the @path@
+* A `MAP_CREATE` or `COUNTER_CREATE` operation used to create the new object
+* A `MAP_SET` operation used to assign the new object to the `LiveMap` instance specified by the `path`
Therefore the response will include the object IDs of all objects affected by the resulting set of operations:
-```[json]
+
+```json
{
"messageId": "mkfjWU2jju:0",
"channel": "my-channel",
@@ -841,14 +929,16 @@ Therefore the response will include the object IDs of all objects affected by th
]
}
```
+
-h3(#removing-objects). Removing objects
+### Removing objects
There is no explicit delete operation for objects themselves. Objects that are not reachable from the root map will be eligible for garbage collection.
-Remove a reference to a nested object in a @LiveMap@ instance using the @MAP_REMOVE@ operation:
+Remove a reference to a nested object in a `LiveMap` instance using the `MAP_REMOVE` operation:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -858,22 +948,24 @@ Remove a reference to a nested object in a @LiveMap@ instance using the @MAP_REM
"data": {"key": "posts"}
}'
```
+
If no other references to the object exist, it will no longer be reachable from the root object and will be eligible for garbage collection.
-
+
-h3(#batch-operations). Batch operations
+### Batch operations
You can group several operations into a single request by sending an array of operations.
-All operations inside the array form a "batch operation":/docs/liveobjects/concepts/operations#batch-operations which is published as a single message. All operations in the batch are processed as a single atomic unit.
+All operations inside the array form a [batch operation](/docs/liveobjects/concepts/operations#batch-operations) which is published as a single message. All operations in the batch are processed as a single atomic unit.
-The following example shows how to increment two distinct @LiveCounter@ instances in a single batch operation:
+The following example shows how to increment two distinct `LiveCounter` instances in a single batch operation:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -890,12 +982,14 @@ The following example shows how to increment two distinct @LiveCounter@ instance
}
]'
```
+
-h3(#idempotent-operations). Idempotent operations
+### Idempotent operations
-Publish operations idempotently in the same way as for "idempotent message publishing":/docs/api/rest-api#idempotent-publish by specifying a @id@ for the operation message:
+Publish operations idempotently in the same way as for [idempotent message publishing](/docs/api/rest-api#idempotent-publish) by specifying a `id` for the operation message:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -906,10 +1000,12 @@ Publish operations idempotently in the same way as for "idempotent message publi
"data": {"number": 1}
}'
```
+
-For batch operations, use the format @:@ where the index is the zero-based index of the operation in the array:
+For batch operations, use the format `:` where the index is the zero-based index of the operation in the array:
-```[sh]
+
+```shell
curl -X POST "https://rest.ably.io/channels/my-channel/objects" \
-u {{API_KEY}} \
-H "Content-Type: application/json" \
@@ -928,3 +1024,4 @@ For batch operations, use the format @:@ where the index is the z
}
]'
```
+
diff --git a/src/pages/docs/liveobjects/storage.mdx b/src/pages/docs/liveobjects/storage.mdx
new file mode 100644
index 0000000000..5207a4b19a
--- /dev/null
+++ b/src/pages/docs/liveobjects/storage.mdx
@@ -0,0 +1,61 @@
+---
+title: Object storage
+meta_description: "Learn about LiveObjects object storage."
+---
+
+
+
+## Default object storage - 90 days
+
+Ably durably stores all objects on a channel for a retention period that is configured to 90 days by default. If the data is not updated within the retention period, it will automatically expire. After expiry, the channel is reset to its initial state and only includes an empty [root object](/docs/liveobjects/concepts/objects#root-object).
+
+
+
+## Store objects outside of Ably
+
+You can store your objects outside of Ably by obtaining the channel objects via the [REST API](/docs/liveobjects/rest-api-usage#fetching-objects) and storing them in your own database. This is useful if you want to keep a permanent record of the objects on a channel or if you want to perform analytics on the data.
+
+In order to receive notifications when the objects on a channel are updated, use [inband objects](/docs/liveobjects/inband-objects) to receive updates as regular channel messages.
+
+
+
+## Operation storage
+
+When you update an object, the change is expressed as an [operation](/docs/liveobjects/concepts/operations) that is sent as an [object message](/docs/metadata-stats/stats#messages) on the channel. Like all messages, Ably stores object messages for 2 minutes by default.
+
+This means that if a client disconnects from Ably for a short period of time, it can automatically retrieve any operations it may have missed when it reconnects. If a client disconnects for longer than 2 minutes, the client will be sent the latest state of the objects on the channel (which are durably stored for 90 days) when it reconnects, ensuring the client remains fully synchronized.
+
+Operations themselves are not included in the [history](/docs/storage-history/history) or [rewind](/docs/channels/options/rewind) backlog of a channel. Instead, you should interact with objects directly via the client library.
+
+## Object count and size limits
+
+There is a maximum number of objects that can be stored on a [channel](/docs/pricing/limits#channel), which is configured to 100 objects by default.
+
+
+
+A `LiveCounter` is a double-precision floating-point number and has a size of 8 bytes.
+
+The size of a `LiveMap` object is calculated as the sum of the length of all keys plus the size of all values where:
+
+* `string` values are the length of the string
+* `number` values are 8 bytes
+* `boolean` values are 1 byte
+* `bytes` values are the length of the byte array
+
+The maximum allowed size of a single `LiveMap` object is the same as the [message size limit](/docs/pricing/limits#message) for your package. This is because objects are [synchronized](/docs/liveobjects/concepts/synchronization#client-objects) to the client as channel messages.
+
+
+
+For more information, see [limits](/docs/pricing/limits).
diff --git a/src/pages/docs/liveobjects/typing.mdx b/src/pages/docs/liveobjects/typing.mdx
new file mode 100644
index 0000000000..dbd8716368
--- /dev/null
+++ b/src/pages/docs/liveobjects/typing.mdx
@@ -0,0 +1,82 @@
+---
+title: Typing
+meta_description: "Type objects on a channel for type safety and code autocompletion."
+---
+
+
+
+If you are using TypeScript in your project, you can leverage LiveObjects' built-in TypeScript support to ensure type safety and enable autocompletion when working with objects on a channel.
+
+## Global AblyObjectsTypes interface
+
+You can type objects on all your channels by defining a global `AblyObjectsTypes` interface. If you only want to type the root object for a specific channel, see the [Typing `channel.objects.getRoot()`](#getroot) section below.
+
+Define the `AblyObjectsTypes` interface in a type declaration file. You can create a file named `ably.config.d.ts` in the root of your application:
+
+
+```javascript
+// file: ably.config.d.ts
+import { LiveCounter, LiveMap } from 'ably';
+
+// Define dedicated types and export them for reuse in your application
+export type MyCustomRoot = {
+ reactions: LiveMap<{
+ hearts: LiveCounter;
+ likes: LiveCounter;
+ }>;
+};
+
+declare global {
+ export interface AblyObjectsTypes {
+ root: MyCustomRoot;
+ }
+}
+```
+
+
+This enables TypeScript to infer the correct types when accessing and mutating LiveObjects:
+
+
+```javascript
+// LiveMap<{ reactions: LiveMap<{ hearts: LiveCounter; likes: LiveCounter }> }>
+const root = await channel.objects.getRoot();
+
+// LiveMap<{ hearts: LiveCounter; likes: LiveCounter }>
+const reactions = root.get('reactions');
+
+// LiveCounter
+const likes = reactions.get('likes');
+
+reactions.set('hearts', 1); // Error: Argument of type 'number' is not assignable to parameter of type 'LiveCounter'.ts(2345)
+```
+
+
+## Typing channel.objects.getRoot()
+
+You can pass a type parameter directly to the `channel.objects.getRoot()` method call to type the root object for a channel explicitly:
+
+
+```javascript
+// Define types for different root objects
+type ReactionsRoot = {
+ hearts: LiveCounter;
+ likes: LiveCounter;
+};
+
+type PollsRoot = {
+ currentPoll: LiveMap;
+};
+
+// LiveMap<{ hearts: LiveCounter; likes: LiveCounter }>
+const reactionsRoot = await reactionsChannel.objects.getRoot();
+
+// LiveMap<{ currentPoll: LiveMap }>
+const pollsRoot = await pollsChannel.objects.getRoot();
+```
+
+
+Typing `channel.objects.getRoot()` is particularly useful when your application uses multiple channels, each with a different object structure.