Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions content/api/realtime-sdk/channels.textile
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ h6(#push).

Provides access to the "PushChannel":/docs/api/realtime-sdk/push#push-channel object for this channel which can be used to access members present on the channel, or participate in presence.

h6(#objects).
default: objects

Provides access to the "Objects":/docs/liveobjects object for this channel which can be used to read, modify and subscribe to LiveObjects on a channel.

h3. Methods

h6(#publish).
Expand Down
148 changes: 148 additions & 0 deletions content/liveobjects/batch.textile
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
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.

<aside data-type='important'>
<p>
The batch callback function must be synchronous because the batch method sends the grouped operations as soon as the callback function completes. If you need to perform asynchronous operations (such as fetching data to do operations inside a batch), do so outside the callback function before calling @channel.objects.batch()@.
</p>
</aside>

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/quickstart#step-4 object instance. This wrapper enables you to access and modify objects within a batch.

<aside data-type='note'>
<p>
Although the batch context provides a synchronous API, updates to objects are only applied _after_ the batch callback function has run and changes have been echoed back to the client, just like regular mutation operations.
</p>
</aside>

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');
```
186 changes: 186 additions & 0 deletions content/liveobjects/counter.textile
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
---
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/quickstart#step-4.

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.

<aside data-type='note'>
<p>
Note that you need to first "attach to the channel":/quickstart#step-3 before creating a new @LiveCounter@ instance, as this operation requires sending a message to the Ably system.
</p>
</aside>

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.

<aside data-type='note'>
<p>
@LiveCounter@ mutation methods do not directly modify the local counter state. Instead, they send the intended operation to the Ably system, and the change is applied only when the corresponding realtime operation is echoed back to the client. This means that the state retrieved immediately after a mutation may not reflect the latest updates yet. You will be notified via subscription when the counter is updated.
</p>
</aside>

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();
```
Loading