Skip to content
Open
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
26 changes: 14 additions & 12 deletions examples/liveobjects-live-counter/javascript/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
# Synchronize numeric values with LiveCounter

Enable clients to update and synchronize numerical values in an application in realtime.
Enable clients to update and synchronize numeric values in an application 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.
[`LiveCounter`](/docs/liveobjects/counter) is a synchronized numeric 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.

LiveCounter is useful for tracking values that need to be updated dynamically, such as votes, reaction counts, live leaderboards, game stats, or other numeric data points.
`LiveCounter` is useful for tracking values that need to be updated dynamically, such as votes, reaction counts, live leaderboards, game stats, or other numeric data points.

LiveCounter is implemented using [Ably LiveObjects](/docs/liveobjects). LiveObjects, as a feature of [Ably Pub/Sub](/docs/channels), contains a set of purpose-built APIs that abstract away the complexities of managing shared state between clients in an application. It is built on top of Ably's core platform, and so it provides the same performance guarantees and scaling potential.
`LiveCounter` is a feature of [LiveObjects](/docs/liveobjects), which provides a serverless, durable, and scalable way to create, update, and subscribe to shared state across large numbers of connected clients at any scale. LiveObjects is built on [channels](/docs/channels) and provides the same [performance guarantees and scaling potential](/docs/platform/architecture).

## Resources

Use the following methods to interact with a LiveCounter in your application:
Use the following methods to interact with a `LiveCounter` in your application:

- [`objects.getRoot()`](/docs/liveobjects/concepts/objects#root-object): retrieves the root object that serves as the starting point for storing and organizing objects on a channel.
- [`objects.createCounter()`](/docs/liveobjects/counter#create): creates a new LiveCounter instance.
- [`liveCounter.value()`](/docs/liveobjects/counter#value): returns the current value of a counter.
- [`liveCounter.increment()`](/docs/liveobjects/counter#update): sends the operation message to the Ably system to increase the counter value.
- [`liveCounter.decrement()`](/docs/liveobjects/counter#update): sends the operation message to the Ably system to decrease the counter value.
- [`liveCounter.subscribe()`](/docs/liveobjects/counter#subscribe-data): subscribes to LiveCounter updates by registering a listener.
- [`channel.object.get()`](/docs/liveobjects/concepts/path-object): retrieve a [`PathObject`](/docs/liveobjects/concepts/path-object) reference to the [channel object](/docs/liveobjects/concepts/objects#channel-object). Use this `PathObject` to update and subscribe to data via:
- [`get(key)`](/docs/liveobjects/concepts/path-object#navigate): navigate to child paths within the `PathObject`, such as entries in a [`LiveMap`](/docs/liveobjects/map).
- [`set(key, value)`](/docs/liveobjects/concepts/path-object#update): assign data, such as a `LiveCounter` instance, to the specified key on a `LiveMap`.
- [`value()`](/docs/liveobjects/concepts/path-object#read-values): read the current value of the `LiveCounter` instance at the specified path.
- [`increment()`](/docs/liveobjects/counter#increment): send an [operation](/docs/liveobjects/concepts/operations) to increment the `LiveCounter` at the specified path.
- [`subscribe()`](/docs/liveobjects/concepts/path-object#subscribe): subscribe to updates at the specified path by registering a listener.
- [`batch()`](/docs/liveobjects/concepts/path-object#batch-multiple-updates): group multiple operations into a single message for atomic updates.
- [`LiveCounter.create()`](/docs/liveobjects/counter#create): create a new `LiveCounter` instance.

Find out more about [LiveCounter](/docs/liveobjects/counter).
Find out more about [PathObject](/docs/liveobjects/concepts/path-object) and [LiveCounter](/docs/liveobjects/counter).

## Getting started

Expand Down
12 changes: 0 additions & 12 deletions examples/liveobjects-live-counter/javascript/src/ably.config.d.ts

This file was deleted.

84 changes: 39 additions & 45 deletions examples/liveobjects-live-counter/javascript/src/script.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { DefaultRoot, LiveCounter, LiveMap, Realtime } from 'ably';
import Objects from 'ably/objects';
import { Realtime } from 'ably';
import { LiveCounter, LiveMap, LiveObjects, type PathObject } from 'ably/liveobjects';
import { nanoid } from 'nanoid';
import { config } from './config';
import './styles.css';

export enum Color {
type ColorCounters = {
[Color.red]: LiveCounter;
[Color.green]: LiveCounter;
[Color.blue]: LiveCounter;
};

enum Color {
red = 'red',
green = 'green',
blue = 'blue',
Expand All @@ -13,75 +19,63 @@ export enum Color {
const client = new Realtime({
clientId: nanoid(),
key: config.ABLY_KEY,
plugins: { Objects },
plugins: { LiveObjects },
});

const channelName = config.CHANNEL_NAME || 'objects-live-counter';
const channel = client.channels.get(channelName, { modes: ['OBJECT_PUBLISH', 'OBJECT_SUBSCRIBE'] });
const channel = client.channels.get(channelName, { modes: ['object_publish', 'object_subscribe'] });

const colorCountDivs: Record<Color, HTMLElement> = {
red: document.getElementById('count-red')!,
green: document.getElementById('count-green')!,
blue: document.getElementById('count-blue')!,
const colorCountDivs: Record<Color, HTMLElement | null> = {
red: document.getElementById('count-red'),
green: document.getElementById('count-green'),
blue: document.getElementById('count-blue'),
};
const countersReset = document.getElementById('reset')!;
const countersReset = document.getElementById('reset');

async function main() {
await channel.attach();

const objects = channel.objects;
const root = await objects.getRoot();
const countersObject = await channel.object.get<ColorCounters>();

await initCounters(root);
addEventListenersToButtons(root);
await initCounters(countersObject);
addEventListenersToButtons(countersObject);
}

async function initCounters(root: LiveMap<DefaultRoot>) {
// subscribe to root to get notified when counter objects get changed on the root.
// for example, when we reset all counters
root.subscribe(({ update }) => {
Object.entries(update).forEach(([keyName, change]) => {
if (change === 'removed') {
return;
}

if (Object.values(Color).includes(keyName as Color)) {
// key pointing to a counter object got updated, resubscribe to a counter
const color = keyName as Color;
subscribeToCounterUpdates(color, root.get(color)!);
}
});
});

async function initCounters(counters: PathObject<LiveMap<ColorCounters>>) {
await Promise.all(
Object.values(Color).map(async (color) => {
if (root.get(color)) {
subscribeToCounterUpdates(color, root.get(color)!);
return;
}
subscribeToCounterUpdates(color, counters.get(color));

await root.set(color, await channel.objects.createCounter());
// Initialize counter if it doesn't exist
if (counters.get(color).value() === undefined) {
await counters.set(color, LiveCounter.create());
}
}),
);
}

function subscribeToCounterUpdates(color: Color, counter: LiveCounter) {
function subscribeToCounterUpdates(color: Color, counter: PathObject<LiveCounter>) {
counter.subscribe(() => {
colorCountDivs[color].innerHTML = counter.value().toString();
if (colorCountDivs[color]) {
colorCountDivs[color].innerHTML = counter.value()?.toString() ?? '0';
}
});
colorCountDivs[color].innerHTML = counter.value().toString();
if (colorCountDivs[color]) {
colorCountDivs[color].innerHTML = counter.value()?.toString() ?? '0';
}
}

function addEventListenersToButtons(root: LiveMap<DefaultRoot>) {
function addEventListenersToButtons(counters: PathObject<LiveMap<ColorCounters>>) {
document.querySelectorAll('.vote-button').forEach((button) => {
const color = button.getAttribute('data-color') as Color;
button.addEventListener('click', () => {
root.get(color)?.increment(1);
counters.get(color).increment(1);
});
});

countersReset.addEventListener('click', () => {
Object.values(Color).forEach(async (color) => root.set(color, await channel.objects.createCounter()));
countersReset?.addEventListener('click', () => {
// Use batch to reset all counters atomically
counters.batch((ctx) => {
Object.values(Color).forEach((color) => ctx.set(color, LiveCounter.create()));
});
});
}

Expand Down
25 changes: 14 additions & 11 deletions examples/liveobjects-live-map/javascript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@

Enable clients to update and synchronize key/value data in an application 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.
[`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, binary data, JSON-serializable objects or arrays and other live [object types](/docs/liveobjects/concepts/objects#object-types), enabling you to build complex, hierarchical channel objects.

LiveMap can be used to store both predefined and dynamic datasets that need to be updated in realtime. They are ideal for scenarios such as collaborative task management, live leaderboards, multiplayer game state, shared settings, or live dashboards.
`LiveMap` can be used to store both predefined and dynamic datasets that need to be updated in realtime. It is ideal for scenarios such as collaborative task management, live leaderboards, multiplayer game state, shared settings, or live dashboards.

LiveMap is implemented using [Ably LiveObjects](/docs/liveobjects). LiveObjects, as a feature of [Ably Pub/Sub](/docs/channels), contains a set of purpose-built APIs that abstract away the complexities of managing shared state between clients in an application. It is built on top of Ably's core platform, and so it provides the same performance guarantees and scaling potential.
`LiveMap` is a feature of [LiveObjects](/docs/liveobjects), which provides a serverless, durable, and scalable way to create, update, and subscribe to shared state across large numbers of connected clients at any scale. LiveObjects is built on [channels](/docs/channels) and provides the same [performance guarantees and scaling potential](/docs/platform/architecture).

## Resources

Use the following methods to interact with a LiveMap in your application:
Use the following methods to interact with a `LiveMap` in your application:

- [`objects.getRoot()`](/docs/liveobjects/concepts/objects#root-object): retrieves the root object that serves as the starting point for storing and organizing objects on a channel.
- [`objects.createMap()`](/docs/liveobjects/map#create): creates a new LiveMap instance.
- [`liveMap.get(key)`](/docs/liveobjects/map#get): returns the current value associated with a given key in the map.
- [`liveMap.set(key, value)`](/docs/liveobjects/map#set): sends the operation message to the Ably system to assign a value to a key in the map.
- [`liveMap.remove(key)`](/docs/liveobjects/map#remove): sends the operation message to the Ably system to remove a key from the map.
- [`liveMap.subscribe()`](/docs/liveobjects/map#subscribe-data): subscribes to LiveMap updates by registering a listener.
- [`channel.object.get()`](/docs/liveobjects/concepts/path-object): retrieve a [`PathObject`](/docs/liveobjects/concepts/path-object) reference to the [channel object](/docs/liveobjects/concepts/objects#channel-object). Use this `PathObject` to update and subscribe to data via:
- [`get(key)`](/docs/liveobjects/concepts/path-object#navigate): navigate to child paths within the `PathObject`, such as entries in a `LiveMap`.
- [`set(key, value)`](/docs/liveobjects/concepts/path-object#update): assign a value to a key in the `LiveMap`.
- [`remove(key)`](/docs/liveobjects/concepts/path-object#delete): send an [operation](/docs/liveobjects/concepts/operations) to remove a key from the `LiveMap`.
- [`value()`](/docs/liveobjects/concepts/path-object#read-values): read the current value at the specified path.
- [`entries()`](/docs/liveobjects/concepts/path-object#iterate): iterate over key-value pairs in the `LiveMap`.
- [`subscribe()`](/docs/liveobjects/concepts/path-object#subscribe): subscribe to updates at the specified path by registering a listener.
- [`batch()`](/docs/liveobjects/concepts/path-object#batch-multiple-updates): group multiple operations into a single message for atomic updates.
- [`LiveMap.create()`](/docs/liveobjects/map#create): create a new `LiveMap` instance.

Find out more about [LiveMap](/docs/liveobjects/map).
Find out more about [PathObject](/docs/liveobjects/concepts/path-object) and [LiveMap](/docs/liveobjects/map).

## Getting started

Expand Down
11 changes: 0 additions & 11 deletions examples/liveobjects-live-map/javascript/src/ably.config.d.ts

This file was deleted.

Loading