@liveblocks/client 0.13.0-beta.1 → 0.13.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -8
- package/lib/cjs/LiveList.d.ts +1 -0
- package/lib/cjs/LiveList.js +22 -0
- package/lib/cjs/LiveObject.d.ts +5 -0
- package/lib/cjs/LiveObject.js +38 -0
- package/lib/cjs/immutable/index.d.ts +7 -0
- package/lib/cjs/immutable/index.js +214 -0
- package/lib/cjs/immutable.d.ts +8 -0
- package/lib/cjs/immutable.js +233 -0
- package/lib/cjs/index.d.ts +1 -1
- package/lib/cjs/room.d.ts +2 -2
- package/lib/cjs/room.js +25 -7
- package/lib/cjs/types.d.ts +60 -2
- package/lib/esm/LiveList.d.ts +1 -0
- package/lib/esm/LiveList.js +22 -0
- package/lib/esm/LiveObject.d.ts +5 -0
- package/lib/esm/LiveObject.js +38 -0
- package/lib/esm/immutable/index.d.ts +7 -0
- package/lib/esm/immutable/index.js +207 -0
- package/lib/esm/immutable.d.ts +8 -0
- package/lib/esm/immutable.js +225 -0
- package/lib/esm/index.d.ts +1 -1
- package/lib/esm/room.d.ts +2 -2
- package/lib/esm/room.js +25 -7
- package/lib/esm/types.d.ts +60 -2
- package/package.json +1 -1
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveMap } from "./LiveMap";
|
|
3
|
+
import { LiveObject } from "./LiveObject";
|
|
4
|
+
import { LiveRegister } from "./LiveRegister";
|
|
5
|
+
export function liveObjectToJson(liveObject) {
|
|
6
|
+
const result = {};
|
|
7
|
+
const obj = liveObject.toObject();
|
|
8
|
+
for (const key in obj) {
|
|
9
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
function liveMapToJson(map) {
|
|
14
|
+
const result = {};
|
|
15
|
+
const obj = Object.fromEntries(map);
|
|
16
|
+
for (const key in obj) {
|
|
17
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
function liveListToJson(value) {
|
|
22
|
+
return value.toArray().map(liveNodeToJson);
|
|
23
|
+
}
|
|
24
|
+
function liveNodeToJson(value) {
|
|
25
|
+
if (value instanceof LiveObject) {
|
|
26
|
+
return liveObjectToJson(value);
|
|
27
|
+
}
|
|
28
|
+
else if (value instanceof LiveList) {
|
|
29
|
+
return liveListToJson(value);
|
|
30
|
+
}
|
|
31
|
+
else if (value instanceof LiveMap) {
|
|
32
|
+
return liveMapToJson(value);
|
|
33
|
+
}
|
|
34
|
+
else if (value instanceof LiveRegister) {
|
|
35
|
+
return value.data;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function isPlainObject(obj) {
|
|
40
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
41
|
+
}
|
|
42
|
+
function anyToCrdt(obj) {
|
|
43
|
+
if (obj == null) {
|
|
44
|
+
return obj;
|
|
45
|
+
}
|
|
46
|
+
if (Array.isArray(obj)) {
|
|
47
|
+
return new LiveList(obj.map(anyToCrdt));
|
|
48
|
+
}
|
|
49
|
+
if (isPlainObject(obj)) {
|
|
50
|
+
const init = {};
|
|
51
|
+
for (const key in obj) {
|
|
52
|
+
init[key] = anyToCrdt(obj[key]);
|
|
53
|
+
}
|
|
54
|
+
return new LiveObject(init);
|
|
55
|
+
}
|
|
56
|
+
return obj;
|
|
57
|
+
}
|
|
58
|
+
export function patchLiveList(liveList, prev, next) {
|
|
59
|
+
let i = 0;
|
|
60
|
+
let prevEnd = prev.length - 1;
|
|
61
|
+
let nextEnd = next.length - 1;
|
|
62
|
+
let prevNode = prev[0];
|
|
63
|
+
let nextNode = next[0];
|
|
64
|
+
/**
|
|
65
|
+
* For A,B,C => A,B,C,D
|
|
66
|
+
* i = 3, prevEnd = 2, nextEnd = 3
|
|
67
|
+
*
|
|
68
|
+
* For A,B,C => B,C
|
|
69
|
+
* i = 2, prevEnd = 2, nextEnd = 1
|
|
70
|
+
*
|
|
71
|
+
* For B,C => A,B,C
|
|
72
|
+
* i = 0, pre
|
|
73
|
+
*/
|
|
74
|
+
outer: {
|
|
75
|
+
while (prevNode === nextNode) {
|
|
76
|
+
++i;
|
|
77
|
+
if (i > prevEnd || i > nextEnd) {
|
|
78
|
+
break outer;
|
|
79
|
+
}
|
|
80
|
+
prevNode = prev[i];
|
|
81
|
+
nextNode = next[i];
|
|
82
|
+
}
|
|
83
|
+
prevNode = prev[prevEnd];
|
|
84
|
+
nextNode = next[nextEnd];
|
|
85
|
+
while (prevNode === nextNode) {
|
|
86
|
+
prevEnd--;
|
|
87
|
+
nextEnd--;
|
|
88
|
+
if (i > prevEnd || i > nextEnd) {
|
|
89
|
+
break outer;
|
|
90
|
+
}
|
|
91
|
+
prevNode = prev[prevEnd];
|
|
92
|
+
nextNode = next[nextEnd];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (i > prevEnd) {
|
|
96
|
+
if (i <= nextEnd) {
|
|
97
|
+
while (i <= nextEnd) {
|
|
98
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
99
|
+
i++;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
else if (i > nextEnd) {
|
|
104
|
+
while (i <= prevEnd) {
|
|
105
|
+
liveList.delete(i++);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
while (i <= prevEnd && i <= nextEnd) {
|
|
110
|
+
prevNode = prev[i];
|
|
111
|
+
nextNode = next[i];
|
|
112
|
+
const liveListNode = liveList.get(i);
|
|
113
|
+
if (liveListNode instanceof LiveObject &&
|
|
114
|
+
isPlainObject(prevNode) &&
|
|
115
|
+
isPlainObject(nextNode)) {
|
|
116
|
+
patchLiveObject(liveListNode, prevNode, nextNode);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
liveList.delete(i);
|
|
120
|
+
liveList.insert(anyToCrdt(nextNode), i);
|
|
121
|
+
}
|
|
122
|
+
i++;
|
|
123
|
+
}
|
|
124
|
+
while (i <= nextEnd) {
|
|
125
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
while (i <= prevEnd) {
|
|
129
|
+
liveList.delete(i);
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
export function patchLiveObjectKey(liveObject, key, prev, next) {
|
|
135
|
+
const value = liveObject.get(key);
|
|
136
|
+
if (next === undefined) {
|
|
137
|
+
liveObject.delete(key);
|
|
138
|
+
}
|
|
139
|
+
else if (value === undefined) {
|
|
140
|
+
liveObject.set(key, anyToCrdt(next));
|
|
141
|
+
}
|
|
142
|
+
else if (prev === next) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
else if (value instanceof LiveList &&
|
|
146
|
+
Array.isArray(prev) &&
|
|
147
|
+
Array.isArray(next)) {
|
|
148
|
+
patchLiveList(value, prev, next);
|
|
149
|
+
}
|
|
150
|
+
else if (value instanceof LiveObject &&
|
|
151
|
+
isPlainObject(prev) &&
|
|
152
|
+
isPlainObject(next)) {
|
|
153
|
+
patchLiveObject(value, prev, next);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
liveObject.set(key, anyToCrdt(next));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export function patchLiveObject(root, prev, next) {
|
|
160
|
+
const updates = {};
|
|
161
|
+
for (const key in next) {
|
|
162
|
+
patchLiveObjectKey(root, key, prev[key], next[key]);
|
|
163
|
+
}
|
|
164
|
+
for (const key in prev) {
|
|
165
|
+
if (next[key] === undefined) {
|
|
166
|
+
root.delete(key);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (Object.keys(updates).length > 0) {
|
|
170
|
+
root.update(updates);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function getParentsPath(node) {
|
|
174
|
+
const path = [];
|
|
175
|
+
while (node._parentKey != null && node._parent != null) {
|
|
176
|
+
if (node._parent instanceof LiveList) {
|
|
177
|
+
path.push(node._parent._indexOfPosition(node._parentKey));
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
path.push(node._parentKey);
|
|
181
|
+
}
|
|
182
|
+
node = node._parent;
|
|
183
|
+
}
|
|
184
|
+
return path;
|
|
185
|
+
}
|
|
186
|
+
export function patchImmutableObject(state, updates) {
|
|
187
|
+
return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
|
|
188
|
+
}
|
|
189
|
+
function patchImmutableObjectWithUpdate(state, update) {
|
|
190
|
+
const path = getParentsPath(update.node);
|
|
191
|
+
return patchImmutableNode(state, path, update);
|
|
192
|
+
}
|
|
193
|
+
function patchImmutableNode(state, path, update) {
|
|
194
|
+
const pathItem = path.pop();
|
|
195
|
+
if (pathItem === undefined) {
|
|
196
|
+
switch (update.type) {
|
|
197
|
+
case "LiveObject": {
|
|
198
|
+
if (typeof state !== "object") {
|
|
199
|
+
throw new Error("Internal: received update on LiveObject but state was not an object");
|
|
200
|
+
}
|
|
201
|
+
return liveObjectToJson(update.node);
|
|
202
|
+
}
|
|
203
|
+
case "LiveList": {
|
|
204
|
+
if (Array.isArray(state) === false) {
|
|
205
|
+
throw new Error("Internal: received update on LiveList but state was not an array");
|
|
206
|
+
}
|
|
207
|
+
return liveListToJson(update.node);
|
|
208
|
+
}
|
|
209
|
+
case "LiveMap": {
|
|
210
|
+
if (typeof state !== "object") {
|
|
211
|
+
throw new Error("Internal: received update on LiveMap but state was not an object");
|
|
212
|
+
}
|
|
213
|
+
return liveMapToJson(update.node);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (Array.isArray(state)) {
|
|
218
|
+
const newArray = [...state];
|
|
219
|
+
newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
|
|
220
|
+
return newArray;
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
|
|
224
|
+
}
|
|
225
|
+
}
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { LiveObject } from "./LiveObject";
|
|
2
2
|
export { LiveMap } from "./LiveMap";
|
|
3
3
|
export { LiveList } from "./LiveList";
|
|
4
|
-
export type { Others, Presence, Room, Client, User } from "./types";
|
|
4
|
+
export type { Others, Presence, Room, Client, User, BroadcastOptions, } from "./types";
|
|
5
5
|
export { createClient } from "./client";
|
package/lib/esm/room.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Others, Presence, ClientOptions, Room, MyPresenceCallback, OthersEventCallback, AuthEndpoint, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate } from "./types";
|
|
1
|
+
import { Others, Presence, ClientOptions, Room, MyPresenceCallback, OthersEventCallback, AuthEndpoint, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate, BroadcastOptions } from "./types";
|
|
2
2
|
import { ClientMessage, Op } from "./live";
|
|
3
3
|
import { LiveMap } from "./LiveMap";
|
|
4
4
|
import { LiveObject } from "./LiveObject";
|
|
@@ -118,7 +118,7 @@ export declare function makeStateMachine(state: State, context: Context, mockedE
|
|
|
118
118
|
updatePresence: <T_4 extends Presence>(overrides: Partial<T_4>, options?: {
|
|
119
119
|
addToHistory: boolean;
|
|
120
120
|
} | undefined) => void;
|
|
121
|
-
broadcastEvent: (event: any) => void;
|
|
121
|
+
broadcastEvent: (event: any, options?: BroadcastOptions) => void;
|
|
122
122
|
batch: (callback: () => void) => void;
|
|
123
123
|
undo: () => void;
|
|
124
124
|
redo: () => void;
|
package/lib/esm/room.js
CHANGED
|
@@ -36,6 +36,9 @@ function makeOthers(presenceMap) {
|
|
|
36
36
|
get count() {
|
|
37
37
|
return array.length;
|
|
38
38
|
},
|
|
39
|
+
[Symbol.iterator]() {
|
|
40
|
+
return array[Symbol.iterator]();
|
|
41
|
+
},
|
|
39
42
|
map(callback) {
|
|
40
43
|
return array.map(callback);
|
|
41
44
|
},
|
|
@@ -421,7 +424,9 @@ See v0.13 release notes for more information.
|
|
|
421
424
|
state.socket = socket;
|
|
422
425
|
}
|
|
423
426
|
function authenticationFailure(error) {
|
|
424
|
-
|
|
427
|
+
if (process.env.NODE_ENV !== "production") {
|
|
428
|
+
console.error("Call to authentication endpoint failed", error);
|
|
429
|
+
}
|
|
425
430
|
updateConnection({ state: "unavailable" });
|
|
426
431
|
state.numberOfRetry++;
|
|
427
432
|
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
@@ -587,13 +592,20 @@ See v0.13 release notes for more information.
|
|
|
587
592
|
updateConnection({ state: "failed" });
|
|
588
593
|
const error = new LiveblocksError(event.reason, event.code);
|
|
589
594
|
for (const listener of state.listeners.error) {
|
|
595
|
+
if (process.env.NODE_ENV !== "production") {
|
|
596
|
+
console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
|
|
597
|
+
}
|
|
590
598
|
listener(error);
|
|
591
599
|
}
|
|
592
600
|
}
|
|
593
601
|
else if (event.wasClean === false) {
|
|
594
|
-
updateConnection({ state: "unavailable" });
|
|
595
602
|
state.numberOfRetry++;
|
|
596
|
-
|
|
603
|
+
const delay = getRetryDelay();
|
|
604
|
+
if (process.env.NODE_ENV !== "production") {
|
|
605
|
+
console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
|
|
606
|
+
}
|
|
607
|
+
updateConnection({ state: "unavailable" });
|
|
608
|
+
state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
|
|
597
609
|
}
|
|
598
610
|
else {
|
|
599
611
|
updateConnection({ state: "closed" });
|
|
@@ -735,8 +747,10 @@ See v0.13 release notes for more information.
|
|
|
735
747
|
function getOthers() {
|
|
736
748
|
return state.others;
|
|
737
749
|
}
|
|
738
|
-
function broadcastEvent(event
|
|
739
|
-
|
|
750
|
+
function broadcastEvent(event, options = {
|
|
751
|
+
shouldQueueEventIfNotReady: false,
|
|
752
|
+
}) {
|
|
753
|
+
if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
|
|
740
754
|
return;
|
|
741
755
|
}
|
|
742
756
|
state.buffer.messages.push({
|
|
@@ -817,10 +831,14 @@ See v0.13 release notes for more information.
|
|
|
817
831
|
}
|
|
818
832
|
finally {
|
|
819
833
|
state.isBatching = false;
|
|
820
|
-
|
|
834
|
+
if (state.batch.reverseOps.length > 0) {
|
|
835
|
+
addToUndoStack(state.batch.reverseOps);
|
|
836
|
+
}
|
|
821
837
|
// Clear the redo stack because batch is always called from a local operation
|
|
822
838
|
state.redoStack = [];
|
|
823
|
-
|
|
839
|
+
if (state.batch.ops.length > 0) {
|
|
840
|
+
dispatch(state.batch.ops);
|
|
841
|
+
}
|
|
824
842
|
notify(state.batch.updates);
|
|
825
843
|
state.batch = {
|
|
826
844
|
ops: [],
|
package/lib/esm/types.d.ts
CHANGED
|
@@ -28,6 +28,14 @@ export declare type LiveListUpdates<TItem = any> = {
|
|
|
28
28
|
type: "LiveList";
|
|
29
29
|
node: LiveList<TItem>;
|
|
30
30
|
};
|
|
31
|
+
export declare type BroadcastOptions = {
|
|
32
|
+
/**
|
|
33
|
+
* Whether or not event is queued if the connection is currently closed.
|
|
34
|
+
*
|
|
35
|
+
* ❗ We are not sure if we want to support this option in the future so it might be deprecated to be replaced by something else
|
|
36
|
+
*/
|
|
37
|
+
shouldQueueEventIfNotReady: boolean;
|
|
38
|
+
};
|
|
31
39
|
export declare type StorageUpdate = LiveMapUpdates | LiveObjectUpdates | LiveListUpdates;
|
|
32
40
|
export declare type StorageCallback = (updates: StorageUpdate[]) => void;
|
|
33
41
|
export declare type Client = {
|
|
@@ -65,6 +73,10 @@ export interface Others<TPresence extends Presence = Presence> {
|
|
|
65
73
|
* Number of other users in the room.
|
|
66
74
|
*/
|
|
67
75
|
readonly count: number;
|
|
76
|
+
/**
|
|
77
|
+
* Returns a new Iterator object that contains the users.
|
|
78
|
+
*/
|
|
79
|
+
[Symbol.iterator](): IterableIterator<User<TPresence>>;
|
|
68
80
|
/**
|
|
69
81
|
* Returns the array of connected users in room.
|
|
70
82
|
*/
|
|
@@ -277,25 +289,57 @@ export declare type Room = {
|
|
|
277
289
|
}): () => void;
|
|
278
290
|
};
|
|
279
291
|
/**
|
|
280
|
-
* Room's history contains
|
|
292
|
+
* Room's history contains functions that let you undo and redo operation made on by the current client on the presence and storage.
|
|
281
293
|
*/
|
|
282
294
|
history: {
|
|
283
295
|
/**
|
|
284
296
|
* Undoes the last operation executed by the current client.
|
|
285
297
|
* It does not impact operations made by other clients.
|
|
298
|
+
*
|
|
299
|
+
* @example
|
|
300
|
+
* room.updatePresence({ selectedId: "xxx" }, { addToHistory: true });
|
|
301
|
+
* room.updatePresence({ selectedId: "yyy" }, { addToHistory: true });
|
|
302
|
+
* room.history.undo();
|
|
303
|
+
* // room.getPresence() equals { selectedId: "xxx" }
|
|
286
304
|
*/
|
|
287
305
|
undo: () => void;
|
|
288
306
|
/**
|
|
289
307
|
* Redoes the last operation executed by the current client.
|
|
290
308
|
* It does not impact operations made by other clients.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* room.updatePresence({ selectedId: "xxx" }, { addToHistory: true });
|
|
312
|
+
* room.updatePresence({ selectedId: "yyy" }, { addToHistory: true });
|
|
313
|
+
* room.history.undo();
|
|
314
|
+
* // room.getPresence() equals { selectedId: "xxx" }
|
|
315
|
+
* room.history.redo();
|
|
316
|
+
* // room.getPresence() equals { selectedId: "yyy" }
|
|
291
317
|
*/
|
|
292
318
|
redo: () => void;
|
|
293
319
|
/**
|
|
294
320
|
* All future modifications made on the Room will be merged together to create a single history item until resume is called.
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });
|
|
324
|
+
* room.history.pause();
|
|
325
|
+
* room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });
|
|
326
|
+
* room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });
|
|
327
|
+
* room.history.resume();
|
|
328
|
+
* room.history.undo();
|
|
329
|
+
* // room.getPresence() equals { cursor: { x: 0, y: 0 } }
|
|
295
330
|
*/
|
|
296
331
|
pause: () => void;
|
|
297
332
|
/**
|
|
298
333
|
* Resumes history. Modifications made on the Room are not merged into a single history item anymore.
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* room.updatePresence({ cursor: { x: 0, y: 0 } }, { addToHistory: true });
|
|
337
|
+
* room.history.pause();
|
|
338
|
+
* room.updatePresence({ cursor: { x: 1, y: 1 } }, { addToHistory: true });
|
|
339
|
+
* room.updatePresence({ cursor: { x: 2, y: 2 } }, { addToHistory: true });
|
|
340
|
+
* room.history.resume();
|
|
341
|
+
* room.history.undo();
|
|
342
|
+
* // room.getPresence() equals { cursor: { x: 0, y: 0 } }
|
|
299
343
|
*/
|
|
300
344
|
resume: () => void;
|
|
301
345
|
};
|
|
@@ -391,7 +435,14 @@ export declare type Room = {
|
|
|
391
435
|
* }
|
|
392
436
|
* });
|
|
393
437
|
*/
|
|
394
|
-
broadcastEvent: (event: any) => void;
|
|
438
|
+
broadcastEvent: (event: any, options?: BroadcastOptions) => void;
|
|
439
|
+
/**
|
|
440
|
+
* Get the room's storage asynchronously.
|
|
441
|
+
* The storage's root is a {@link LiveObject}.
|
|
442
|
+
*
|
|
443
|
+
* @example
|
|
444
|
+
* const { root } = await room.getStorage();
|
|
445
|
+
*/
|
|
395
446
|
getStorage: <TRoot>() => Promise<{
|
|
396
447
|
root: LiveObject<TRoot>;
|
|
397
448
|
}>;
|
|
@@ -400,6 +451,13 @@ export declare type Room = {
|
|
|
400
451
|
* All the modifications are sent to other clients in a single message.
|
|
401
452
|
* All the subscribers are called only after the batch is over.
|
|
402
453
|
* All the modifications are merged in a single history item (undo/redo).
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* const { root } = await room.getStorage();
|
|
457
|
+
* room.batch(() => {
|
|
458
|
+
* root.set("x", 0);
|
|
459
|
+
* room.updatePresence({ cursor: { x: 100, y: 100 }});
|
|
460
|
+
* });
|
|
403
461
|
*/
|
|
404
462
|
batch: (fn: () => void) => void;
|
|
405
463
|
};
|