LiveMap is a synchronized key/value data structure that stores primitive values, such as numbers, strings, booleans, binary data, JSON-serializable objects or arrays and other live object types. It ensures that all updates are correctly applied and synchronized across clients in realtime, automatically resolving conflicts with last-write-wins (LWW) semantics.
You interact with LiveMap through a PathObject or by obtaining a specific Instance.
Create a map
Create a LiveMap using the LiveMap.create() static method and assign it to a path:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { LiveMap } from 'ably/liveobjects';
// Create an empty map
await myObject.set('settings', LiveMap.create());
// Create a map with initial data
await myObject.set('user', LiveMap.create({
name: 'Alice',
score: 42,
active: true
}));
// Create nested maps
await myObject.set('config', LiveMap.create({
theme: LiveMap.create({
color: 'dark',
fontSize: 14
})
}));LiveMap.create() returns a value type that describes the initial data for a new map. The actual object is created when assigned to a path. Each assignment creates a distinct object with its own unique ID:
1
2
3
4
5
6
7
8
9
10
const mapValue = LiveMap.create({ theme: 'dark' });
// Each assignment creates a different object
await myObject.set('map1', mapValue);
await myObject.set('map2', mapValue);
// map1 and map2 are different objects with different IDs
const id1 = myObject.get('map1').instance()?.id();
const id2 = myObject.get('map2').instance()?.id();
console.log(id1 === id2); // falseGet map values
Access a LiveMap through a PathObject for path-based operations, or obtain a specific Instance to work with the underlying object directly. Use the get() method to navigate to entries within the map.
What you can do with the result depends on the type of value stored in the entry:
- For entries containing primitive values or
LiveCounterobjects, use thevalue()method to get the current value. - For entries containing nested
LiveMapobjects, useget()to continue navigating deeper into the structure.
1
2
3
4
5
6
7
8
9
10
11
const myObject = await channel.object.get();
// PathObject access: path-based operations that resolve at runtime
const theme = myObject.get('settings').get('theme'); // settings holds a LiveMap with a 'theme' key
console.log(theme.value()); // e.g. 'dark'
const visits = myObject.get('visits'); // visits holds a LiveCounter
console.log(visits.value()); // e.g. 5
// Instance access: reference to a specific map object
const settingsInstance = myObject.get('settings').instance();
console.log(settingsInstance?.get('theme')?.value()); // e.g. 'dark' (primitive string)Get compact object
Get a JavaScript object representation of the map using the compact() or compactJson() methods:
1
2
3
4
5
6
7
8
9
// Get a PathObject to a LiveMap stored in 'settings'
const settings = myObject.get('settings');
console.log(settings.compact());
// e.g. { theme: 'dark', fontSize: 14, notifications: true }
// Get the Instance of a LiveMap stored in 'settings'
const settingsInstance = myObject.get('settings').instance();
console.log(settingsInstance?.compact());
// e.g. { theme: 'dark', fontSize: 14, notifications: true }Set map values
Set a value for a key in the map using the set() method. You can store primitive values or other live object types:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// PathObject: set values at path
await myObject.get('settings').set('theme', 'dark');
await myObject.get('settings').set('fontSize', 14);
await myObject.get('settings').set('notifications', true);
// Set a nested map
await myObject.get('settings').set('advanced', LiveMap.create({
debugMode: false,
logLevel: 'info'
}));
// Set a counter
await myObject.get('settings').set('visits', LiveCounter.create(0));
// Instance: set values on specific map instance
const settingsInstance = myObject.get('settings').instance();
await settingsInstance?.set('theme', 'dark');
await settingsInstance?.set('fontSize', 14);Remove a key
Remove a key from the map using the remove() method:
1
2
3
4
5
6
// PathObject: remove key at path
await myObject.get('settings').remove('oldSetting');
// Instance: remove key on specific map instance
const settingsInstance = myObject.get('settings').instance();
await settingsInstance?.remove('oldSetting');Get map size
Get the number of keys in the map using the size() method:
1
2
3
4
5
6
7
// PathObject: get size at path
const settings = myObject.get('settings');
console.log(settings.size()); // e.g. 5
// Instance: get size of specific map instance
const settingsInstance = myObject.get('settings').instance();
console.log(settingsInstance?.size()); // e.g. 5Enumerate entries
Iterate over the map's keys, values, or entries. When iterating using a PathObject, values are returned as a PathObject for the nested path. When iterating using an Instance, values are returned as an Instance for the entry:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// PathObject: iterate with PathObject values
const settings = myObject.get('settings');
for (const [key, value] of settings.entries()) {
console.log(`${key}:`, value.value());
}
for (const key of settings.keys()) {
console.log('Key:', key);
}
for (const value of settings.values()) {
console.log('Value:', value.value());
}
// Instance: iterate with Instance values
const settingsInstance = myObject.get('settings').instance();
if (settingsInstance) {
for (const [key, valueInstance] of settingsInstance.entries()) {
console.log(`${key}:`, valueInstance.value());
}
for (const key of settingsInstance.keys()) {
console.log('Key:', key);
}
for (const value of settingsInstance.values()) {
console.log('Value:', value.value());
}
}Batch multiple operations
Group multiple map operations into a single atomic message using the batch() method. All operations within the batch are sent as one logical unit which succeed or fail together:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// PathObject: batch operations on map at path
await myObject.get('settings').batch((ctx) => {
ctx.set('theme', 'dark');
ctx.set('fontSize', 14);
ctx.set('notifications', true);
ctx.remove('oldSetting');
});
// Instance: batch operations on specific map instance
const settingsInstance = myObject.get('settings').instance();
await settingsInstance?.batch((ctx) => {
ctx.set('theme', 'dark');
ctx.set('fontSize', 14);
});Subscribe to updates
Subscribe to LiveMap updates to receive realtime notifications when the map changes.
PathObject subscriptions observe a location and automatically track changes even if the LiveMap instance at that path is replaced. Instance subscriptions track a specific LiveMap instance, following it even if it moves in the channel object.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// PathObject: observe location - tracks changes even if map instance is replaced
const settings = myObject.get('settings');
const { unsubscribe } = settings.subscribe(() => {
console.log('Settings:', settings.compact());
});
// Later, stop listening to changes
unsubscribe();
// Instance: track specific map instance - follows it even if moved in object tree
const settingsInstance = myObject.get('settings').instance();
if (settingsInstance) {
const { unsubscribe } = settingsInstance.subscribe(() => {
console.log('Settings:', settingsInstance.compact());
});
// Later, stop listening to changes
unsubscribe();
}Alternatively, use the subscribeIterator() method for an async iterator syntax:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// PathObject: observe location - tracks changes even if map instance is replaced
const settings = myObject.get('settings');
for await (const _ of settings.subscribeIterator()) {
console.log('Settings:', settings.compact());
if (someCondition) {
break; // Unsubscribes
}
}
// Instance: track specific map instance - follows it even if moved in object tree
const settingsInstance = myObject.get('settings').instance();
if (settingsInstance) {
for await (const _ of settingsInstance.subscribeIterator()) {
console.log('Settings:', settingsInstance.compact());
if (someCondition) {
break; // Unsubscribes
}
}
}