@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
package/lib/cjs/room.js
CHANGED
|
@@ -58,6 +58,9 @@ function makeOthers(presenceMap) {
|
|
|
58
58
|
get count() {
|
|
59
59
|
return array.length;
|
|
60
60
|
},
|
|
61
|
+
[Symbol.iterator]() {
|
|
62
|
+
return array[Symbol.iterator]();
|
|
63
|
+
},
|
|
61
64
|
map(callback) {
|
|
62
65
|
return array.map(callback);
|
|
63
66
|
},
|
|
@@ -443,7 +446,9 @@ See v0.13 release notes for more information.
|
|
|
443
446
|
state.socket = socket;
|
|
444
447
|
}
|
|
445
448
|
function authenticationFailure(error) {
|
|
446
|
-
|
|
449
|
+
if (process.env.NODE_ENV !== "production") {
|
|
450
|
+
console.error("Call to authentication endpoint failed", error);
|
|
451
|
+
}
|
|
447
452
|
updateConnection({ state: "unavailable" });
|
|
448
453
|
state.numberOfRetry++;
|
|
449
454
|
state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
|
|
@@ -609,13 +614,20 @@ See v0.13 release notes for more information.
|
|
|
609
614
|
updateConnection({ state: "failed" });
|
|
610
615
|
const error = new LiveblocksError(event.reason, event.code);
|
|
611
616
|
for (const listener of state.listeners.error) {
|
|
617
|
+
if (process.env.NODE_ENV !== "production") {
|
|
618
|
+
console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
|
|
619
|
+
}
|
|
612
620
|
listener(error);
|
|
613
621
|
}
|
|
614
622
|
}
|
|
615
623
|
else if (event.wasClean === false) {
|
|
616
|
-
updateConnection({ state: "unavailable" });
|
|
617
624
|
state.numberOfRetry++;
|
|
618
|
-
|
|
625
|
+
const delay = getRetryDelay();
|
|
626
|
+
if (process.env.NODE_ENV !== "production") {
|
|
627
|
+
console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
|
|
628
|
+
}
|
|
629
|
+
updateConnection({ state: "unavailable" });
|
|
630
|
+
state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
|
|
619
631
|
}
|
|
620
632
|
else {
|
|
621
633
|
updateConnection({ state: "closed" });
|
|
@@ -757,8 +769,10 @@ See v0.13 release notes for more information.
|
|
|
757
769
|
function getOthers() {
|
|
758
770
|
return state.others;
|
|
759
771
|
}
|
|
760
|
-
function broadcastEvent(event
|
|
761
|
-
|
|
772
|
+
function broadcastEvent(event, options = {
|
|
773
|
+
shouldQueueEventIfNotReady: false,
|
|
774
|
+
}) {
|
|
775
|
+
if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
|
|
762
776
|
return;
|
|
763
777
|
}
|
|
764
778
|
state.buffer.messages.push({
|
|
@@ -839,10 +853,14 @@ See v0.13 release notes for more information.
|
|
|
839
853
|
}
|
|
840
854
|
finally {
|
|
841
855
|
state.isBatching = false;
|
|
842
|
-
|
|
856
|
+
if (state.batch.reverseOps.length > 0) {
|
|
857
|
+
addToUndoStack(state.batch.reverseOps);
|
|
858
|
+
}
|
|
843
859
|
// Clear the redo stack because batch is always called from a local operation
|
|
844
860
|
state.redoStack = [];
|
|
845
|
-
|
|
861
|
+
if (state.batch.ops.length > 0) {
|
|
862
|
+
dispatch(state.batch.ops);
|
|
863
|
+
}
|
|
846
864
|
notify(state.batch.updates);
|
|
847
865
|
state.batch = {
|
|
848
866
|
ops: [],
|
package/lib/cjs/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
|
};
|
package/lib/esm/LiveList.d.ts
CHANGED
package/lib/esm/LiveList.js
CHANGED
|
@@ -253,6 +253,28 @@ export class LiveList extends AbstractCrdt {
|
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
}
|
|
256
|
+
clear() {
|
|
257
|
+
if (this._doc) {
|
|
258
|
+
let ops = [];
|
|
259
|
+
let reverseOps = [];
|
|
260
|
+
for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) {
|
|
261
|
+
item[0]._detach();
|
|
262
|
+
const childId = item[0]._id;
|
|
263
|
+
if (childId) {
|
|
264
|
+
ops.push({ id: childId, type: OpType.DeleteCrdt });
|
|
265
|
+
reverseOps.push(...item[0]._serialize(this._id, item[1]));
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
__classPrivateFieldSet(this, _LiveList_items, [], "f");
|
|
269
|
+
this._doc.dispatch(ops, reverseOps, [this]);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) {
|
|
273
|
+
item[0]._detach();
|
|
274
|
+
}
|
|
275
|
+
__classPrivateFieldSet(this, _LiveList_items, [], "f");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
256
278
|
/**
|
|
257
279
|
* Returns an Array of all the elements in the LiveList.
|
|
258
280
|
*/
|
package/lib/esm/LiveObject.d.ts
CHANGED
|
@@ -53,6 +53,11 @@ export declare class LiveObject<T extends Record<string, any> = Record<string, a
|
|
|
53
53
|
* @param key The key of the property to get
|
|
54
54
|
*/
|
|
55
55
|
get<TKey extends keyof T>(key: TKey): T[TKey];
|
|
56
|
+
/**
|
|
57
|
+
* Deletes a key from the LiveObject
|
|
58
|
+
* @param key The key of the property to delete
|
|
59
|
+
*/
|
|
60
|
+
delete(key: keyof T): void;
|
|
56
61
|
/**
|
|
57
62
|
* Adds or updates multiple properties at once with an object.
|
|
58
63
|
* @param overrides The object used to overrides properties
|
package/lib/esm/LiveObject.js
CHANGED
|
@@ -183,6 +183,40 @@ export class LiveObject extends AbstractCrdt {
|
|
|
183
183
|
get(key) {
|
|
184
184
|
return __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Deletes a key from the LiveObject
|
|
188
|
+
* @param key The key of the property to delete
|
|
189
|
+
*/
|
|
190
|
+
delete(key) {
|
|
191
|
+
const keyAsString = key;
|
|
192
|
+
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(keyAsString);
|
|
193
|
+
if (oldValue === undefined) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (this._doc == null || this._id == null) {
|
|
197
|
+
if (oldValue instanceof AbstractCrdt) {
|
|
198
|
+
oldValue._detach();
|
|
199
|
+
}
|
|
200
|
+
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
let reverse;
|
|
204
|
+
if (oldValue instanceof AbstractCrdt) {
|
|
205
|
+
oldValue._detach();
|
|
206
|
+
reverse = oldValue._serialize(this._id, keyAsString);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
reverse = [
|
|
210
|
+
{
|
|
211
|
+
type: OpType.UpdateObject,
|
|
212
|
+
data: { [keyAsString]: oldValue },
|
|
213
|
+
id: this._id,
|
|
214
|
+
},
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString);
|
|
218
|
+
this._doc.dispatch([{ type: OpType.DeleteObjectKey, key: keyAsString, id: this._id }], reverse, [this]);
|
|
219
|
+
}
|
|
186
220
|
/**
|
|
187
221
|
* Adds or updates multiple properties at once with an object.
|
|
188
222
|
* @param overrides The object used to overrides properties
|
|
@@ -306,6 +340,10 @@ _LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _
|
|
|
306
340
|
return isModified ? { modified: this, reverse } : { modified: false };
|
|
307
341
|
}, _LiveObject_applyDeleteObjectKey = function _LiveObject_applyDeleteObjectKey(op) {
|
|
308
342
|
const key = op.key;
|
|
343
|
+
// If property does not exist, exit without notifying
|
|
344
|
+
if (__classPrivateFieldGet(this, _LiveObject_map, "f").has(key) === false) {
|
|
345
|
+
return { modified: false };
|
|
346
|
+
}
|
|
309
347
|
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
|
|
310
348
|
let reverse = [];
|
|
311
349
|
if (isCrdt(oldValue)) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { LiveList } from "../LiveList";
|
|
2
|
+
import { LiveObject } from "../LiveObject";
|
|
3
|
+
import { StorageUpdate } from "../types";
|
|
4
|
+
export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
|
|
5
|
+
export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
|
|
6
|
+
export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
|
|
7
|
+
export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { LiveList } from "../LiveList";
|
|
2
|
+
import { LiveMap } from "../LiveMap";
|
|
3
|
+
import { LiveObject } from "../LiveObject";
|
|
4
|
+
export function liveObjectToJson(liveObject) {
|
|
5
|
+
const result = {};
|
|
6
|
+
const obj = liveObject.toObject();
|
|
7
|
+
for (const key in obj) {
|
|
8
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
9
|
+
}
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
function liveMapToJson(map) {
|
|
13
|
+
const result = {};
|
|
14
|
+
const obj = Object.fromEntries(map);
|
|
15
|
+
for (const key in obj) {
|
|
16
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
function liveListToJson(value) {
|
|
21
|
+
return value.toArray().map(liveNodeToJson);
|
|
22
|
+
}
|
|
23
|
+
function liveNodeToJson(value) {
|
|
24
|
+
if (value instanceof LiveObject) {
|
|
25
|
+
return liveObjectToJson(value);
|
|
26
|
+
}
|
|
27
|
+
else if (value instanceof LiveList) {
|
|
28
|
+
return liveListToJson(value);
|
|
29
|
+
}
|
|
30
|
+
else if (value instanceof LiveMap) {
|
|
31
|
+
return liveMapToJson(value);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function isPlainObject(obj) {
|
|
36
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
37
|
+
}
|
|
38
|
+
function anyToCrdt(obj) {
|
|
39
|
+
if (obj == null) {
|
|
40
|
+
return obj;
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(obj)) {
|
|
43
|
+
return new LiveList(obj.map(anyToCrdt));
|
|
44
|
+
}
|
|
45
|
+
if (isPlainObject(obj)) {
|
|
46
|
+
const init = {};
|
|
47
|
+
for (const key in obj) {
|
|
48
|
+
init[key] = anyToCrdt(obj[key]);
|
|
49
|
+
}
|
|
50
|
+
return new LiveObject(init);
|
|
51
|
+
}
|
|
52
|
+
return obj;
|
|
53
|
+
}
|
|
54
|
+
export function patchLiveList(liveList, prev, next) {
|
|
55
|
+
let i = 0;
|
|
56
|
+
let prevEnd = prev.length - 1;
|
|
57
|
+
let nextEnd = next.length - 1;
|
|
58
|
+
let prevNode = prev[0];
|
|
59
|
+
let nextNode = next[0];
|
|
60
|
+
/**
|
|
61
|
+
* For A,B,C => A,B,C,D
|
|
62
|
+
* i = 3, prevEnd = 2, nextEnd = 3
|
|
63
|
+
*
|
|
64
|
+
* For A,B,C => B,C
|
|
65
|
+
* i = 2, prevEnd = 2, nextEnd = 1
|
|
66
|
+
*
|
|
67
|
+
* For B,C => A,B,C
|
|
68
|
+
* i = 0, pre
|
|
69
|
+
*/
|
|
70
|
+
outer: {
|
|
71
|
+
while (prevNode === nextNode) {
|
|
72
|
+
++i;
|
|
73
|
+
if (i > prevEnd || i > nextEnd) {
|
|
74
|
+
break outer;
|
|
75
|
+
}
|
|
76
|
+
prevNode = prev[i];
|
|
77
|
+
nextNode = next[i];
|
|
78
|
+
}
|
|
79
|
+
prevNode = prev[prevEnd];
|
|
80
|
+
nextNode = next[nextEnd];
|
|
81
|
+
while (prevNode === nextNode) {
|
|
82
|
+
prevEnd--;
|
|
83
|
+
nextEnd--;
|
|
84
|
+
if (i > prevEnd || i > nextEnd) {
|
|
85
|
+
break outer;
|
|
86
|
+
}
|
|
87
|
+
prevNode = prev[prevEnd];
|
|
88
|
+
nextNode = next[nextEnd];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (i > prevEnd) {
|
|
92
|
+
if (i <= nextEnd) {
|
|
93
|
+
while (i <= nextEnd) {
|
|
94
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
95
|
+
i++;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else if (i > nextEnd) {
|
|
100
|
+
while (i <= prevEnd) {
|
|
101
|
+
liveList.delete(i++);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
while (i <= prevEnd && i <= nextEnd) {
|
|
106
|
+
prevNode = prev[i];
|
|
107
|
+
nextNode = next[i];
|
|
108
|
+
const liveListNode = liveList.get(i);
|
|
109
|
+
if (liveListNode instanceof LiveObject &&
|
|
110
|
+
isPlainObject(prevNode) &&
|
|
111
|
+
isPlainObject(nextNode)) {
|
|
112
|
+
patchLiveObject(liveListNode, prevNode, nextNode);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
liveList.delete(i);
|
|
116
|
+
liveList.insert(anyToCrdt(nextNode), i);
|
|
117
|
+
}
|
|
118
|
+
i++;
|
|
119
|
+
}
|
|
120
|
+
while (i <= nextEnd) {
|
|
121
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
122
|
+
i++;
|
|
123
|
+
}
|
|
124
|
+
while (i <= prevEnd) {
|
|
125
|
+
liveList.delete(i);
|
|
126
|
+
i++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export function patchLiveObject(root, prev, next) {
|
|
131
|
+
const updates = {};
|
|
132
|
+
for (const key in next) {
|
|
133
|
+
if (prev[key] === next[key]) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
else if (Array.isArray(prev[key]) && Array.isArray(next[key])) {
|
|
137
|
+
patchLiveList(root.get(key), prev[key], next[key]);
|
|
138
|
+
}
|
|
139
|
+
else if (isPlainObject(prev[key]) && isPlainObject(next[key])) {
|
|
140
|
+
patchLiveObject(root.get(key), prev[key], next[key]);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
updates[key] = anyToCrdt(next[key]);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const key in prev) {
|
|
147
|
+
if (next[key] === undefined) {
|
|
148
|
+
root.delete(key);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (Object.keys(updates).length > 0) {
|
|
152
|
+
root.update(updates);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function getParentsPath(node) {
|
|
156
|
+
const path = [];
|
|
157
|
+
while (node._parentKey != null && node._parent != null) {
|
|
158
|
+
if (node._parent instanceof LiveList) {
|
|
159
|
+
path.push(node._parent._indexOfPosition(node._parentKey));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
path.push(node._parentKey);
|
|
163
|
+
}
|
|
164
|
+
node = node._parent;
|
|
165
|
+
}
|
|
166
|
+
return path;
|
|
167
|
+
}
|
|
168
|
+
export function patchImmutableObject(state, updates) {
|
|
169
|
+
return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
|
|
170
|
+
}
|
|
171
|
+
function patchImmutableObjectWithUpdate(state, update) {
|
|
172
|
+
const path = getParentsPath(update.node);
|
|
173
|
+
return patchImmutableNode(state, path, update);
|
|
174
|
+
}
|
|
175
|
+
function patchImmutableNode(state, path, update) {
|
|
176
|
+
const pathItem = path.pop();
|
|
177
|
+
if (pathItem === undefined) {
|
|
178
|
+
switch (update.type) {
|
|
179
|
+
case "LiveObject": {
|
|
180
|
+
if (typeof state !== "object") {
|
|
181
|
+
throw new Error("Internal: received update on LiveObject but state was not an object");
|
|
182
|
+
}
|
|
183
|
+
return liveObjectToJson(update.node);
|
|
184
|
+
}
|
|
185
|
+
case "LiveList": {
|
|
186
|
+
if (Array.isArray(state) === false) {
|
|
187
|
+
throw new Error("Internal: received update on LiveList but state was not an array");
|
|
188
|
+
}
|
|
189
|
+
return liveListToJson(update.node);
|
|
190
|
+
}
|
|
191
|
+
case "LiveMap": {
|
|
192
|
+
if (typeof state !== "object") {
|
|
193
|
+
throw new Error("Internal: received update on LiveMap but state was not an object");
|
|
194
|
+
}
|
|
195
|
+
return liveMapToJson(update.node);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (Array.isArray(state)) {
|
|
200
|
+
const newArray = [...state];
|
|
201
|
+
newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
|
|
202
|
+
return newArray;
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveObject } from "./LiveObject";
|
|
3
|
+
import { StorageUpdate } from "./types";
|
|
4
|
+
export declare function liveObjectToJson(liveObject: LiveObject<any>): any;
|
|
5
|
+
export declare function patchLiveList<T>(liveList: LiveList<T>, prev: Array<T>, next: Array<T>): void;
|
|
6
|
+
export declare function patchLiveObjectKey<T>(liveObject: LiveObject<T>, key: keyof T, prev: any, next: any): void;
|
|
7
|
+
export declare function patchLiveObject<T extends Record<string, any>>(root: LiveObject<T>, prev: T, next: T): void;
|
|
8
|
+
export declare function patchImmutableObject<T>(state: T, updates: StorageUpdate[]): T;
|