@liveblocks/client 0.16.1 → 0.16.4-beta1

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/shared.mjs ADDED
@@ -0,0 +1,1947 @@
1
+ var ServerMessageType;
2
+ (function (ServerMessageType) {
3
+ ServerMessageType[ServerMessageType["UpdatePresence"] = 100] = "UpdatePresence";
4
+ ServerMessageType[ServerMessageType["UserJoined"] = 101] = "UserJoined";
5
+ ServerMessageType[ServerMessageType["UserLeft"] = 102] = "UserLeft";
6
+ ServerMessageType[ServerMessageType["Event"] = 103] = "Event";
7
+ ServerMessageType[ServerMessageType["RoomState"] = 104] = "RoomState";
8
+ ServerMessageType[ServerMessageType["InitialStorageState"] = 200] = "InitialStorageState";
9
+ ServerMessageType[ServerMessageType["UpdateStorage"] = 201] = "UpdateStorage";
10
+ })(ServerMessageType || (ServerMessageType = {}));
11
+ var ClientMessageType;
12
+ (function (ClientMessageType) {
13
+ ClientMessageType[ClientMessageType["UpdatePresence"] = 100] = "UpdatePresence";
14
+ ClientMessageType[ClientMessageType["ClientEvent"] = 103] = "ClientEvent";
15
+ ClientMessageType[ClientMessageType["FetchStorage"] = 200] = "FetchStorage";
16
+ ClientMessageType[ClientMessageType["UpdateStorage"] = 201] = "UpdateStorage";
17
+ })(ClientMessageType || (ClientMessageType = {}));
18
+ var CrdtType;
19
+ (function (CrdtType) {
20
+ CrdtType[CrdtType["Object"] = 0] = "Object";
21
+ CrdtType[CrdtType["List"] = 1] = "List";
22
+ CrdtType[CrdtType["Map"] = 2] = "Map";
23
+ CrdtType[CrdtType["Register"] = 3] = "Register";
24
+ })(CrdtType || (CrdtType = {}));
25
+ var OpType;
26
+ (function (OpType) {
27
+ OpType[OpType["Init"] = 0] = "Init";
28
+ OpType[OpType["SetParentKey"] = 1] = "SetParentKey";
29
+ OpType[OpType["CreateList"] = 2] = "CreateList";
30
+ OpType[OpType["UpdateObject"] = 3] = "UpdateObject";
31
+ OpType[OpType["CreateObject"] = 4] = "CreateObject";
32
+ OpType[OpType["DeleteCrdt"] = 5] = "DeleteCrdt";
33
+ OpType[OpType["DeleteObjectKey"] = 6] = "DeleteObjectKey";
34
+ OpType[OpType["CreateMap"] = 7] = "CreateMap";
35
+ OpType[OpType["CreateRegister"] = 8] = "CreateRegister";
36
+ })(OpType || (OpType = {}));
37
+ var WebsocketCloseCodes;
38
+ (function (WebsocketCloseCodes) {
39
+ WebsocketCloseCodes[WebsocketCloseCodes["CLOSE_ABNORMAL"] = 1006] = "CLOSE_ABNORMAL";
40
+ WebsocketCloseCodes[WebsocketCloseCodes["INVALID_MESSAGE_FORMAT"] = 4000] = "INVALID_MESSAGE_FORMAT";
41
+ WebsocketCloseCodes[WebsocketCloseCodes["NOT_ALLOWED"] = 4001] = "NOT_ALLOWED";
42
+ WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_MESSAGES_PER_SECONDS"] = 4002] = "MAX_NUMBER_OF_MESSAGES_PER_SECONDS";
43
+ WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS"] = 4003] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS";
44
+ WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP"] = 4004] = "MAX_NUMBER_OF_MESSAGES_PER_DAY_PER_APP";
45
+ WebsocketCloseCodes[WebsocketCloseCodes["MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM"] = 4005] = "MAX_NUMBER_OF_CONCURRENT_CONNECTIONS_PER_ROOM";
46
+ WebsocketCloseCodes[WebsocketCloseCodes["CLOSE_WITHOUT_RETRY"] = 4999] = "CLOSE_WITHOUT_RETRY";
47
+ })(WebsocketCloseCodes || (WebsocketCloseCodes = {}));
48
+
49
+ class AbstractCrdt {
50
+ /**
51
+ * @internal
52
+ */
53
+ get _doc() {
54
+ return this.__doc;
55
+ }
56
+ get roomId() {
57
+ return this.__doc ? this.__doc.roomId : null;
58
+ }
59
+ /**
60
+ * @internal
61
+ */
62
+ get _id() {
63
+ return this.__id;
64
+ }
65
+ /**
66
+ * @internal
67
+ */
68
+ get _parent() {
69
+ return this.__parent;
70
+ }
71
+ /**
72
+ * @internal
73
+ */
74
+ get _parentKey() {
75
+ return this.__parentKey;
76
+ }
77
+ /**
78
+ * @internal
79
+ */
80
+ _apply(op, _isLocal) {
81
+ switch (op.type) {
82
+ case OpType.DeleteCrdt: {
83
+ if (this._parent != null && this._parentKey != null) {
84
+ return this._parent._detachChild(this);
85
+ }
86
+ return { modified: false };
87
+ }
88
+ }
89
+ return { modified: false };
90
+ }
91
+ /**
92
+ * @internal
93
+ */
94
+ _setParentLink(parent, key) {
95
+ if (this.__parent != null && this.__parent !== parent) {
96
+ throw new Error("Cannot attach parent if it already exist");
97
+ }
98
+ this.__parentKey = key;
99
+ this.__parent = parent;
100
+ }
101
+ /**
102
+ * @internal
103
+ */
104
+ _attach(id, doc) {
105
+ if (this.__id || this.__doc) {
106
+ throw new Error("Cannot attach if CRDT is already attached");
107
+ }
108
+ doc.addItem(id, this);
109
+ this.__id = id;
110
+ this.__doc = doc;
111
+ }
112
+ /**
113
+ * @internal
114
+ */
115
+ _detach() {
116
+ if (this.__doc && this.__id) {
117
+ this.__doc.deleteItem(this.__id);
118
+ }
119
+ this.__parent = undefined;
120
+ this.__doc = undefined;
121
+ }
122
+ }
123
+
124
+ const min = 32;
125
+ const max = 126;
126
+ function makePosition(before, after) {
127
+ // No children
128
+ if (before == null && after == null) {
129
+ return pos([min + 1]);
130
+ }
131
+ // Insert at the end
132
+ if (before != null && after == null) {
133
+ return getNextPosition(before);
134
+ }
135
+ // Insert at the start
136
+ if (before == null && after != null) {
137
+ return getPreviousPosition(after);
138
+ }
139
+ return pos(makePositionFromCodes(posCodes(before), posCodes(after)));
140
+ }
141
+ function getPreviousPosition(after) {
142
+ const result = [];
143
+ const afterCodes = posCodes(after);
144
+ for (let i = 0; i < afterCodes.length; i++) {
145
+ const code = afterCodes[i];
146
+ if (code <= min + 1) {
147
+ result.push(min);
148
+ if (afterCodes.length - 1 === i) {
149
+ result.push(max);
150
+ break;
151
+ }
152
+ }
153
+ else {
154
+ result.push(code - 1);
155
+ break;
156
+ }
157
+ }
158
+ return pos(result);
159
+ }
160
+ function getNextPosition(before) {
161
+ const result = [];
162
+ const beforeCodes = posCodes(before);
163
+ for (let i = 0; i < beforeCodes.length; i++) {
164
+ const code = beforeCodes[i];
165
+ if (code === max) {
166
+ result.push(code);
167
+ if (beforeCodes.length - 1 === i) {
168
+ result.push(min + 1);
169
+ break;
170
+ }
171
+ }
172
+ else {
173
+ result.push(code + 1);
174
+ break;
175
+ }
176
+ }
177
+ return pos(result);
178
+ }
179
+ function makePositionFromCodes(before, after) {
180
+ let index = 0;
181
+ const result = [];
182
+ while (true) {
183
+ const beforeDigit = before[index] || min;
184
+ const afterDigit = after[index] || max;
185
+ if (beforeDigit > afterDigit) {
186
+ throw new Error(`Impossible to generate position between ${before} and ${after}`);
187
+ }
188
+ if (beforeDigit === afterDigit) {
189
+ result.push(beforeDigit);
190
+ index++;
191
+ continue;
192
+ }
193
+ if (afterDigit - beforeDigit === 1) {
194
+ result.push(beforeDigit);
195
+ result.push(...makePositionFromCodes(before.slice(index + 1), []));
196
+ break;
197
+ }
198
+ const mid = (afterDigit + beforeDigit) >> 1;
199
+ result.push(mid);
200
+ break;
201
+ }
202
+ return result;
203
+ }
204
+ function posCodes(str) {
205
+ const codes = [];
206
+ for (let i = 0; i < str.length; i++) {
207
+ codes.push(str.charCodeAt(i));
208
+ }
209
+ return codes;
210
+ }
211
+ function pos(codes) {
212
+ return String.fromCharCode(...codes);
213
+ }
214
+ function compare(posA, posB) {
215
+ const aCodes = posCodes(posA);
216
+ const bCodes = posCodes(posB);
217
+ const maxLength = Math.max(aCodes.length, bCodes.length);
218
+ for (let i = 0; i < maxLength; i++) {
219
+ const a = aCodes[i] == null ? min : aCodes[i];
220
+ const b = bCodes[i] == null ? min : bCodes[i];
221
+ if (a === b) {
222
+ continue;
223
+ }
224
+ else {
225
+ return a - b;
226
+ }
227
+ }
228
+ throw new Error(`Impossible to compare similar position "${posA}" and "${posB}"`);
229
+ }
230
+
231
+ /**
232
+ * @internal
233
+ */
234
+ class LiveRegister extends AbstractCrdt {
235
+ constructor(data) {
236
+ super();
237
+ this._data = data;
238
+ }
239
+ get data() {
240
+ return this._data;
241
+ }
242
+ /**
243
+ * INTERNAL
244
+ */
245
+ static _deserialize([id, item], _parentToChildren, doc) {
246
+ if (item.type !== CrdtType.Register) {
247
+ throw new Error(`Tried to deserialize a map but item type is "${item.type}"`);
248
+ }
249
+ const register = new LiveRegister(item.data);
250
+ register._attach(id, doc);
251
+ return register;
252
+ }
253
+ /**
254
+ * INTERNAL
255
+ */
256
+ _serialize(parentId, parentKey, doc, intent) {
257
+ if (this._id == null || parentId == null || parentKey == null) {
258
+ throw new Error("Cannot serialize register if parentId or parentKey is undefined");
259
+ }
260
+ return [
261
+ {
262
+ type: OpType.CreateRegister,
263
+ opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(),
264
+ id: this._id,
265
+ intent,
266
+ parentId,
267
+ parentKey,
268
+ data: this.data,
269
+ },
270
+ ];
271
+ }
272
+ /**
273
+ * INTERNAL
274
+ */
275
+ _toSerializedCrdt() {
276
+ var _a;
277
+ return {
278
+ type: CrdtType.Register,
279
+ parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id,
280
+ parentKey: this._parentKey,
281
+ data: this.data,
282
+ };
283
+ }
284
+ _attachChild(_op, _isLocal) {
285
+ throw new Error("Method not implemented.");
286
+ }
287
+ _detachChild(_crdt) {
288
+ throw new Error("Method not implemented.");
289
+ }
290
+ _apply(op, isLocal) {
291
+ return super._apply(op, isLocal);
292
+ }
293
+ }
294
+
295
+ /**
296
+ * The LiveList class represents an ordered collection of items that is synchronized across clients.
297
+ */
298
+ class LiveList extends AbstractCrdt {
299
+ constructor(items = []) {
300
+ super();
301
+ this._items = [];
302
+ let position = undefined;
303
+ for (let i = 0; i < items.length; i++) {
304
+ const newPosition = makePosition(position);
305
+ const item = selfOrRegister(items[i]);
306
+ this._items.push([item, newPosition]);
307
+ position = newPosition;
308
+ }
309
+ }
310
+ /**
311
+ * @internal
312
+ */
313
+ static _deserialize([id], parentToChildren, doc) {
314
+ const list = new LiveList([]);
315
+ list._attach(id, doc);
316
+ const children = parentToChildren.get(id);
317
+ if (children == null) {
318
+ return list;
319
+ }
320
+ for (const entry of children) {
321
+ const child = deserialize(entry, parentToChildren, doc);
322
+ child._setParentLink(list, entry[1].parentKey);
323
+ list._items.push([child, entry[1].parentKey]);
324
+ list._items.sort((itemA, itemB) => compare(itemA[1], itemB[1]));
325
+ }
326
+ return list;
327
+ }
328
+ /**
329
+ * @internal
330
+ */
331
+ _serialize(parentId, parentKey, doc, intent) {
332
+ if (this._id == null) {
333
+ throw new Error("Cannot serialize item is not attached");
334
+ }
335
+ if (parentId == null || parentKey == null) {
336
+ throw new Error("Cannot serialize list if parentId or parentKey is undefined");
337
+ }
338
+ const ops = [];
339
+ const op = {
340
+ id: this._id,
341
+ opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(),
342
+ intent,
343
+ type: OpType.CreateList,
344
+ parentId,
345
+ parentKey,
346
+ };
347
+ ops.push(op);
348
+ for (const [value, key] of this._items) {
349
+ ops.push(...value._serialize(this._id, key, doc));
350
+ }
351
+ return ops;
352
+ }
353
+ /**
354
+ * @internal
355
+ */
356
+ _indexOfPosition(position) {
357
+ return this._items.findIndex((item) => item[1] === position);
358
+ }
359
+ /**
360
+ * @internal
361
+ */
362
+ _attach(id, doc) {
363
+ super._attach(id, doc);
364
+ for (const [item] of this._items) {
365
+ item._attach(doc.generateId(), doc);
366
+ }
367
+ }
368
+ /**
369
+ * @internal
370
+ */
371
+ _detach() {
372
+ super._detach();
373
+ for (const [value] of this._items) {
374
+ value._detach();
375
+ }
376
+ }
377
+ /**
378
+ * @internal
379
+ */
380
+ _attachChild(op, isLocal) {
381
+ var _a;
382
+ if (this._doc == null) {
383
+ throw new Error("Can't attach child if doc is not present");
384
+ }
385
+ const { id, parentKey, intent } = op;
386
+ const key = parentKey;
387
+ const child = creationOpToLiveStructure(op);
388
+ if (this._doc.getItem(id) !== undefined) {
389
+ return { modified: false };
390
+ }
391
+ child._attach(id, this._doc);
392
+ child._setParentLink(this, key);
393
+ const index = this._items.findIndex((entry) => entry[1] === key);
394
+ let newKey = key;
395
+ // If there is a conflict
396
+ if (index !== -1) {
397
+ if (intent === "set") {
398
+ const existingItem = this._items[index][0];
399
+ existingItem._detach();
400
+ const storageUpdate = {
401
+ node: this,
402
+ type: "LiveList",
403
+ updates: [
404
+ {
405
+ index,
406
+ type: "set",
407
+ item: child instanceof LiveRegister ? child.data : child,
408
+ },
409
+ ],
410
+ };
411
+ this._items[index][0] = child;
412
+ return {
413
+ modified: storageUpdate,
414
+ reverse: existingItem._serialize(this._id, key, this._doc, "set"),
415
+ };
416
+ }
417
+ else if (isLocal) {
418
+ // If change is local => assign a temporary position to newly attached child
419
+ const before = this._items[index] ? this._items[index][1] : undefined;
420
+ const after = this._items[index + 1]
421
+ ? this._items[index + 1][1]
422
+ : undefined;
423
+ newKey = makePosition(before, after);
424
+ child._setParentLink(this, newKey);
425
+ }
426
+ else {
427
+ // If change is remote => assign a temporary position to existing child until we get the fix from the backend
428
+ this._items[index][1] = makePosition(key, (_a = this._items[index + 1]) === null || _a === void 0 ? void 0 : _a[1]);
429
+ }
430
+ }
431
+ this._items.push([child, newKey]);
432
+ this._items.sort((itemA, itemB) => compare(itemA[1], itemB[1]));
433
+ const newIndex = this._items.findIndex((entry) => entry[1] === newKey);
434
+ return {
435
+ reverse: [{ type: OpType.DeleteCrdt, id }],
436
+ modified: {
437
+ node: this,
438
+ type: "LiveList",
439
+ updates: [
440
+ {
441
+ index: newIndex,
442
+ type: "insert",
443
+ item: child instanceof LiveRegister ? child.data : child,
444
+ },
445
+ ],
446
+ },
447
+ };
448
+ }
449
+ /**
450
+ * @internal
451
+ */
452
+ _detachChild(child) {
453
+ if (child) {
454
+ const reverse = child._serialize(this._id, child._parentKey, this._doc);
455
+ const indexToDelete = this._items.findIndex((item) => item[0] === child);
456
+ this._items.splice(indexToDelete, 1);
457
+ child._detach();
458
+ const storageUpdate = {
459
+ node: this,
460
+ type: "LiveList",
461
+ updates: [{ index: indexToDelete, type: "delete" }],
462
+ };
463
+ return { modified: storageUpdate, reverse };
464
+ }
465
+ return { modified: false };
466
+ }
467
+ /**
468
+ * @internal
469
+ */
470
+ _setChildKey(key, child, previousKey) {
471
+ var _a;
472
+ child._setParentLink(this, key);
473
+ const previousIndex = this._items.findIndex((entry) => entry[0]._id === child._id);
474
+ const index = this._items.findIndex((entry) => entry[1] === key);
475
+ // Assign a temporary position until we get the fix from the backend
476
+ if (index !== -1) {
477
+ this._items[index][1] = makePosition(key, (_a = this._items[index + 1]) === null || _a === void 0 ? void 0 : _a[1]);
478
+ }
479
+ const item = this._items.find((item) => item[0] === child);
480
+ if (item) {
481
+ item[1] = key;
482
+ }
483
+ this._items.sort((itemA, itemB) => compare(itemA[1], itemB[1]));
484
+ const newIndex = this._items.findIndex((entry) => entry[0]._id === child._id);
485
+ const updatesDelta = newIndex === previousIndex
486
+ ? []
487
+ : [
488
+ {
489
+ index: newIndex,
490
+ item: child instanceof LiveRegister ? child.data : child,
491
+ previousIndex: previousIndex,
492
+ type: "move",
493
+ },
494
+ ];
495
+ return {
496
+ modified: {
497
+ node: this,
498
+ type: "LiveList",
499
+ updates: updatesDelta,
500
+ },
501
+ reverse: [
502
+ {
503
+ type: OpType.SetParentKey,
504
+ id: item === null || item === void 0 ? void 0 : item[0]._id,
505
+ parentKey: previousKey,
506
+ },
507
+ ],
508
+ };
509
+ }
510
+ /**
511
+ * @internal
512
+ */
513
+ _apply(op, isLocal) {
514
+ return super._apply(op, isLocal);
515
+ }
516
+ /**
517
+ * @internal
518
+ */
519
+ _toSerializedCrdt() {
520
+ var _a;
521
+ return {
522
+ type: CrdtType.List,
523
+ parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id,
524
+ parentKey: this._parentKey,
525
+ };
526
+ }
527
+ /**
528
+ * Returns the number of elements.
529
+ */
530
+ get length() {
531
+ return this._items.length;
532
+ }
533
+ /**
534
+ * Adds one element to the end of the LiveList.
535
+ * @param element The element to add to the end of the LiveList.
536
+ */
537
+ push(element) {
538
+ return this.insert(element, this.length);
539
+ }
540
+ /**
541
+ * Inserts one element at a specified index.
542
+ * @param element The element to insert.
543
+ * @param index The index at which you want to insert the element.
544
+ */
545
+ insert(element, index) {
546
+ if (index < 0 || index > this._items.length) {
547
+ throw new Error(`Cannot insert list item at index "${index}". index should be between 0 and ${this._items.length}`);
548
+ }
549
+ const before = this._items[index - 1]
550
+ ? this._items[index - 1][1]
551
+ : undefined;
552
+ const after = this._items[index] ? this._items[index][1] : undefined;
553
+ const position = makePosition(before, after);
554
+ const value = selfOrRegister(element);
555
+ value._setParentLink(this, position);
556
+ this._items.push([value, position]);
557
+ this._items.sort((itemA, itemB) => compare(itemA[1], itemB[1]));
558
+ const newIndex = this._items.findIndex((entry) => entry[1] === position);
559
+ if (this._doc && this._id) {
560
+ const id = this._doc.generateId();
561
+ value._attach(id, this._doc);
562
+ const storageUpdates = new Map();
563
+ storageUpdates.set(this._id, {
564
+ node: this,
565
+ type: "LiveList",
566
+ updates: [
567
+ {
568
+ index: newIndex,
569
+ item: value instanceof LiveRegister ? value.data : value,
570
+ type: "insert",
571
+ },
572
+ ],
573
+ });
574
+ this._doc.dispatch(value._serialize(this._id, position, this._doc), [{ type: OpType.DeleteCrdt, id }], storageUpdates);
575
+ }
576
+ }
577
+ /**
578
+ * Move one element from one index to another.
579
+ * @param index The index of the element to move
580
+ * @param targetIndex The index where the element should be after moving.
581
+ */
582
+ move(index, targetIndex) {
583
+ if (targetIndex < 0) {
584
+ throw new Error("targetIndex cannot be less than 0");
585
+ }
586
+ if (targetIndex >= this._items.length) {
587
+ throw new Error("targetIndex cannot be greater or equal than the list length");
588
+ }
589
+ if (index < 0) {
590
+ throw new Error("index cannot be less than 0");
591
+ }
592
+ if (index >= this._items.length) {
593
+ throw new Error("index cannot be greater or equal than the list length");
594
+ }
595
+ let beforePosition = null;
596
+ let afterPosition = null;
597
+ if (index < targetIndex) {
598
+ afterPosition =
599
+ targetIndex === this._items.length - 1
600
+ ? undefined
601
+ : this._items[targetIndex + 1][1];
602
+ beforePosition = this._items[targetIndex][1];
603
+ }
604
+ else {
605
+ afterPosition = this._items[targetIndex][1];
606
+ beforePosition =
607
+ targetIndex === 0 ? undefined : this._items[targetIndex - 1][1];
608
+ }
609
+ const position = makePosition(beforePosition, afterPosition);
610
+ const item = this._items[index];
611
+ const previousPosition = item[1];
612
+ item[1] = position;
613
+ item[0]._setParentLink(this, position);
614
+ this._items.sort((itemA, itemB) => compare(itemA[1], itemB[1]));
615
+ const newIndex = this._items.findIndex((entry) => entry[1] === position);
616
+ if (this._doc && this._id) {
617
+ const storageUpdates = new Map();
618
+ storageUpdates.set(this._id, {
619
+ node: this,
620
+ type: "LiveList",
621
+ updates: [
622
+ {
623
+ index: newIndex,
624
+ previousIndex: index,
625
+ item: item[0],
626
+ type: "move",
627
+ },
628
+ ],
629
+ });
630
+ this._doc.dispatch([
631
+ {
632
+ type: OpType.SetParentKey,
633
+ id: item[0]._id,
634
+ opId: this._doc.generateOpId(),
635
+ parentKey: position,
636
+ },
637
+ ], [
638
+ {
639
+ type: OpType.SetParentKey,
640
+ id: item[0]._id,
641
+ parentKey: previousPosition,
642
+ },
643
+ ], storageUpdates);
644
+ }
645
+ }
646
+ /**
647
+ * Deletes an element at the specified index
648
+ * @param index The index of the element to delete
649
+ */
650
+ delete(index) {
651
+ if (index < 0 || index >= this._items.length) {
652
+ throw new Error(`Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length - 1}`);
653
+ }
654
+ const item = this._items[index];
655
+ item[0]._detach();
656
+ this._items.splice(index, 1);
657
+ if (this._doc) {
658
+ const childRecordId = item[0]._id;
659
+ if (childRecordId) {
660
+ const storageUpdates = new Map();
661
+ storageUpdates.set(this._id, {
662
+ node: this,
663
+ type: "LiveList",
664
+ updates: [{ index: index, type: "delete" }],
665
+ });
666
+ this._doc.dispatch([
667
+ {
668
+ id: childRecordId,
669
+ opId: this._doc.generateOpId(),
670
+ type: OpType.DeleteCrdt,
671
+ },
672
+ ], item[0]._serialize(this._id, item[1]), storageUpdates);
673
+ }
674
+ }
675
+ }
676
+ clear() {
677
+ if (this._doc) {
678
+ const ops = [];
679
+ const reverseOps = [];
680
+ const updateDelta = [];
681
+ let i = 0;
682
+ for (const item of this._items) {
683
+ item[0]._detach();
684
+ const childId = item[0]._id;
685
+ if (childId) {
686
+ ops.push({ id: childId, type: OpType.DeleteCrdt });
687
+ reverseOps.push(...item[0]._serialize(this._id, item[1]));
688
+ updateDelta.push({ index: i, type: "delete" });
689
+ }
690
+ i++;
691
+ }
692
+ this._items = [];
693
+ const storageUpdates = new Map();
694
+ storageUpdates.set(this._id, {
695
+ node: this,
696
+ type: "LiveList",
697
+ updates: updateDelta,
698
+ });
699
+ this._doc.dispatch(ops, reverseOps, storageUpdates);
700
+ }
701
+ else {
702
+ for (const item of this._items) {
703
+ item[0]._detach();
704
+ }
705
+ this._items = [];
706
+ }
707
+ }
708
+ set(index, item) {
709
+ if (index < 0 || index >= this._items.length) {
710
+ throw new Error(`Cannot set list item at index "${index}". index should be between 0 and ${this._items.length - 1}`);
711
+ }
712
+ const [existingItem, position] = this._items[index];
713
+ existingItem._detach();
714
+ const value = selfOrRegister(item);
715
+ value._setParentLink(this, position);
716
+ this._items[index][0] = value;
717
+ if (this._doc && this._id) {
718
+ const id = this._doc.generateId();
719
+ value._attach(id, this._doc);
720
+ const storageUpdates = new Map();
721
+ storageUpdates.set(this._id, {
722
+ node: this,
723
+ type: "LiveList",
724
+ updates: [
725
+ {
726
+ index,
727
+ item: value instanceof LiveRegister ? value.data : value,
728
+ type: "set",
729
+ },
730
+ ],
731
+ });
732
+ this._doc.dispatch(value._serialize(this._id, position, this._doc, "set"), existingItem._serialize(this._id, position, undefined, "set"), storageUpdates);
733
+ }
734
+ }
735
+ /**
736
+ * Returns an Array of all the elements in the LiveList.
737
+ */
738
+ toArray() {
739
+ return this._items.map((entry) => selfOrRegisterValue(entry[0]));
740
+ }
741
+ /**
742
+ * Tests whether all elements pass the test implemented by the provided function.
743
+ * @param predicate Function to test for each element, taking two arguments (the element and its index).
744
+ * @returns true if the predicate function returns a truthy value for every element. Otherwise, false.
745
+ */
746
+ every(predicate) {
747
+ return this.toArray().every(predicate);
748
+ }
749
+ /**
750
+ * Creates an array with all elements that pass the test implemented by the provided function.
751
+ * @param predicate Function to test each element of the LiveList. Return a value that coerces to true to keep the element, or to false otherwise.
752
+ * @returns An array with the elements that pass the test.
753
+ */
754
+ filter(predicate) {
755
+ return this.toArray().filter(predicate);
756
+ }
757
+ /**
758
+ * Returns the first element that satisfies the provided testing function.
759
+ * @param predicate Function to execute on each value.
760
+ * @returns The value of the first element in the LiveList that satisfies the provided testing function. Otherwise, undefined is returned.
761
+ */
762
+ find(predicate) {
763
+ return this.toArray().find(predicate);
764
+ }
765
+ /**
766
+ * Returns the index of the first element in the LiveList that satisfies the provided testing function.
767
+ * @param predicate Function to execute on each value until the function returns true, indicating that the satisfying element was found.
768
+ * @returns The index of the first element in the LiveList that passes the test. Otherwise, -1.
769
+ */
770
+ findIndex(predicate) {
771
+ return this.toArray().findIndex(predicate);
772
+ }
773
+ /**
774
+ * Executes a provided function once for each element.
775
+ * @param callbackfn Function to execute on each element.
776
+ */
777
+ forEach(callbackfn) {
778
+ return this.toArray().forEach(callbackfn);
779
+ }
780
+ /**
781
+ * Get the element at the specified index.
782
+ * @param index The index on the element to get.
783
+ * @returns The element at the specified index or undefined.
784
+ */
785
+ get(index) {
786
+ if (index < 0 || index >= this._items.length) {
787
+ return undefined;
788
+ }
789
+ return selfOrRegisterValue(this._items[index][0]);
790
+ }
791
+ /**
792
+ * Returns the first index at which a given element can be found in the LiveList, or -1 if it is not present.
793
+ * @param searchElement Element to locate.
794
+ * @param fromIndex The index to start the search at.
795
+ * @returns The first index of the element in the LiveList; -1 if not found.
796
+ */
797
+ indexOf(searchElement, fromIndex) {
798
+ return this.toArray().indexOf(searchElement, fromIndex);
799
+ }
800
+ /**
801
+ * Returns the last index at which a given element can be found in the LiveList, or -1 if it is not present. The LiveLsit is searched backwards, starting at fromIndex.
802
+ * @param searchElement Element to locate.
803
+ * @param fromIndex The index at which to start searching backwards.
804
+ * @returns
805
+ */
806
+ lastIndexOf(searchElement, fromIndex) {
807
+ return this.toArray().lastIndexOf(searchElement, fromIndex);
808
+ }
809
+ /**
810
+ * Creates an array populated with the results of calling a provided function on every element.
811
+ * @param callback Function that is called for every element.
812
+ * @returns An array with each element being the result of the callback function.
813
+ */
814
+ map(callback) {
815
+ return this._items.map((entry, i) => callback(selfOrRegisterValue(entry[0]), i));
816
+ }
817
+ /**
818
+ * Tests whether at least one element in the LiveList passes the test implemented by the provided function.
819
+ * @param predicate Function to test for each element.
820
+ * @returns true if the callback function returns a truthy value for at least one element. Otherwise, false.
821
+ */
822
+ some(predicate) {
823
+ return this.toArray().some(predicate);
824
+ }
825
+ [Symbol.iterator]() {
826
+ return new LiveListIterator(this._items);
827
+ }
828
+ }
829
+ class LiveListIterator {
830
+ constructor(items) {
831
+ this._innerIterator = items[Symbol.iterator]();
832
+ }
833
+ [Symbol.iterator]() {
834
+ return this;
835
+ }
836
+ next() {
837
+ const result = this._innerIterator.next();
838
+ if (result.done) {
839
+ return {
840
+ done: true,
841
+ value: undefined,
842
+ };
843
+ }
844
+ return {
845
+ value: selfOrRegisterValue(result.value[0]),
846
+ };
847
+ }
848
+ }
849
+
850
+ /**
851
+ * The LiveMap class is similar to a JavaScript Map that is synchronized on all clients.
852
+ * Keys should be a string, and values should be serializable to JSON.
853
+ * If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.
854
+ */
855
+ class LiveMap extends AbstractCrdt {
856
+ constructor(entries) {
857
+ super();
858
+ if (entries) {
859
+ const mappedEntries = [];
860
+ for (const entry of entries) {
861
+ const value = selfOrRegister(entry[1]);
862
+ value._setParentLink(this, entry[0]);
863
+ mappedEntries.push([entry[0], value]);
864
+ }
865
+ this._map = new Map(mappedEntries);
866
+ }
867
+ else {
868
+ this._map = new Map();
869
+ }
870
+ }
871
+ /**
872
+ * @internal
873
+ */
874
+ _serialize(parentId, parentKey, doc, intent) {
875
+ if (this._id == null) {
876
+ throw new Error("Cannot serialize item is not attached");
877
+ }
878
+ if (parentId == null || parentKey == null) {
879
+ throw new Error("Cannot serialize map if parentId or parentKey is undefined");
880
+ }
881
+ const ops = [];
882
+ const op = {
883
+ id: this._id,
884
+ opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(),
885
+ type: OpType.CreateMap,
886
+ intent,
887
+ parentId,
888
+ parentKey,
889
+ };
890
+ ops.push(op);
891
+ for (const [key, value] of this._map) {
892
+ ops.push(...value._serialize(this._id, key, doc));
893
+ }
894
+ return ops;
895
+ }
896
+ /**
897
+ * @internal
898
+ */
899
+ static _deserialize([id, item], parentToChildren, doc) {
900
+ if (item.type !== CrdtType.Map) {
901
+ throw new Error(`Tried to deserialize a map but item type is "${item.type}"`);
902
+ }
903
+ const map = new LiveMap();
904
+ map._attach(id, doc);
905
+ const children = parentToChildren.get(id);
906
+ if (children == null) {
907
+ return map;
908
+ }
909
+ for (const entry of children) {
910
+ const crdt = entry[1];
911
+ if (crdt.parentKey == null) {
912
+ throw new Error("Tried to deserialize a crdt but it does not have a parentKey and is not the root");
913
+ }
914
+ const child = deserialize(entry, parentToChildren, doc);
915
+ child._setParentLink(map, crdt.parentKey);
916
+ map._map.set(crdt.parentKey, child);
917
+ }
918
+ return map;
919
+ }
920
+ /**
921
+ * @internal
922
+ */
923
+ _attach(id, doc) {
924
+ super._attach(id, doc);
925
+ for (const [_key, value] of this._map) {
926
+ if (isCrdt(value)) {
927
+ value._attach(doc.generateId(), doc);
928
+ }
929
+ }
930
+ }
931
+ /**
932
+ * @internal
933
+ */
934
+ _attachChild(op, _isLocal) {
935
+ if (this._doc == null) {
936
+ throw new Error("Can't attach child if doc is not present");
937
+ }
938
+ const { id, parentKey } = op;
939
+ const key = parentKey;
940
+ const child = creationOpToLiveStructure(op);
941
+ if (this._doc.getItem(id) !== undefined) {
942
+ return { modified: false };
943
+ }
944
+ const previousValue = this._map.get(key);
945
+ let reverse;
946
+ if (previousValue) {
947
+ reverse = previousValue._serialize(this._id, key);
948
+ previousValue._detach();
949
+ }
950
+ else {
951
+ reverse = [{ type: OpType.DeleteCrdt, id }];
952
+ }
953
+ child._setParentLink(this, key);
954
+ child._attach(id, this._doc);
955
+ this._map.set(key, child);
956
+ return {
957
+ modified: {
958
+ node: this,
959
+ type: "LiveMap",
960
+ updates: { [key]: { type: "update" } },
961
+ },
962
+ reverse,
963
+ };
964
+ }
965
+ /**
966
+ * @internal
967
+ */
968
+ _detach() {
969
+ super._detach();
970
+ for (const item of this._map.values()) {
971
+ item._detach();
972
+ }
973
+ }
974
+ /**
975
+ * @internal
976
+ */
977
+ _detachChild(child) {
978
+ const reverse = child._serialize(this._id, child._parentKey, this._doc);
979
+ for (const [key, value] of this._map) {
980
+ if (value === child) {
981
+ this._map.delete(key);
982
+ }
983
+ }
984
+ child._detach();
985
+ const storageUpdate = {
986
+ node: this,
987
+ type: "LiveMap",
988
+ updates: { [child._parentKey]: { type: "delete" } },
989
+ };
990
+ return { modified: storageUpdate, reverse };
991
+ }
992
+ /**
993
+ * @internal
994
+ */
995
+ _toSerializedCrdt() {
996
+ var _a;
997
+ return {
998
+ type: CrdtType.Map,
999
+ parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id,
1000
+ parentKey: this._parentKey,
1001
+ };
1002
+ }
1003
+ /**
1004
+ * Returns a specified element from the LiveMap.
1005
+ * @param key The key of the element to return.
1006
+ * @returns The element associated with the specified key, or undefined if the key can't be found in the LiveMap.
1007
+ */
1008
+ get(key) {
1009
+ const value = this._map.get(key);
1010
+ if (value == undefined) {
1011
+ return undefined;
1012
+ }
1013
+ return selfOrRegisterValue(value);
1014
+ }
1015
+ /**
1016
+ * Adds or updates an element with a specified key and a value.
1017
+ * @param key The key of the element to add. Should be a string.
1018
+ * @param value The value of the element to add. Should be serializable to JSON.
1019
+ */
1020
+ set(key, value) {
1021
+ const oldValue = this._map.get(key);
1022
+ if (oldValue) {
1023
+ oldValue._detach();
1024
+ }
1025
+ const item = selfOrRegister(value);
1026
+ item._setParentLink(this, key);
1027
+ this._map.set(key, item);
1028
+ if (this._doc && this._id) {
1029
+ const id = this._doc.generateId();
1030
+ item._attach(id, this._doc);
1031
+ const storageUpdates = new Map();
1032
+ storageUpdates.set(this._id, {
1033
+ node: this,
1034
+ type: "LiveMap",
1035
+ updates: { [key]: { type: "update" } },
1036
+ });
1037
+ this._doc.dispatch(item._serialize(this._id, key, this._doc), oldValue
1038
+ ? oldValue._serialize(this._id, key)
1039
+ : [{ type: OpType.DeleteCrdt, id }], storageUpdates);
1040
+ }
1041
+ }
1042
+ /**
1043
+ * Returns the number of elements in the LiveMap.
1044
+ */
1045
+ get size() {
1046
+ return this._map.size;
1047
+ }
1048
+ /**
1049
+ * Returns a boolean indicating whether an element with the specified key exists or not.
1050
+ * @param key The key of the element to test for presence.
1051
+ */
1052
+ has(key) {
1053
+ return this._map.has(key);
1054
+ }
1055
+ /**
1056
+ * Removes the specified element by key.
1057
+ * @param key The key of the element to remove.
1058
+ * @returns true if an element existed and has been removed, or false if the element does not exist.
1059
+ */
1060
+ delete(key) {
1061
+ const item = this._map.get(key);
1062
+ if (item == null) {
1063
+ return false;
1064
+ }
1065
+ item._detach();
1066
+ this._map.delete(key);
1067
+ if (this._doc && item._id) {
1068
+ const storageUpdates = new Map();
1069
+ storageUpdates.set(this._id, {
1070
+ node: this,
1071
+ type: "LiveMap",
1072
+ updates: { [key]: { type: "delete" } },
1073
+ });
1074
+ this._doc.dispatch([
1075
+ {
1076
+ type: OpType.DeleteCrdt,
1077
+ id: item._id,
1078
+ opId: this._doc.generateOpId(),
1079
+ },
1080
+ ], item._serialize(this._id, key), storageUpdates);
1081
+ }
1082
+ return true;
1083
+ }
1084
+ /**
1085
+ * Returns a new Iterator object that contains the [key, value] pairs for each element.
1086
+ */
1087
+ entries() {
1088
+ const innerIterator = this._map.entries();
1089
+ return {
1090
+ [Symbol.iterator]: function () {
1091
+ return this;
1092
+ },
1093
+ next() {
1094
+ const iteratorValue = innerIterator.next();
1095
+ if (iteratorValue.done) {
1096
+ return {
1097
+ done: true,
1098
+ value: undefined,
1099
+ };
1100
+ }
1101
+ const entry = iteratorValue.value;
1102
+ return {
1103
+ value: [entry[0], selfOrRegisterValue(iteratorValue.value[1])],
1104
+ };
1105
+ },
1106
+ };
1107
+ }
1108
+ /**
1109
+ * Same function object as the initial value of the entries method.
1110
+ */
1111
+ [Symbol.iterator]() {
1112
+ return this.entries();
1113
+ }
1114
+ /**
1115
+ * Returns a new Iterator object that contains the keys for each element.
1116
+ */
1117
+ keys() {
1118
+ return this._map.keys();
1119
+ }
1120
+ /**
1121
+ * Returns a new Iterator object that contains the values for each element.
1122
+ */
1123
+ values() {
1124
+ const innerIterator = this._map.values();
1125
+ return {
1126
+ [Symbol.iterator]: function () {
1127
+ return this;
1128
+ },
1129
+ next() {
1130
+ const iteratorValue = innerIterator.next();
1131
+ if (iteratorValue.done) {
1132
+ return {
1133
+ done: true,
1134
+ value: undefined,
1135
+ };
1136
+ }
1137
+ return {
1138
+ value: selfOrRegisterValue(iteratorValue.value),
1139
+ };
1140
+ },
1141
+ };
1142
+ }
1143
+ /**
1144
+ * Executes a provided function once per each key/value pair in the Map object, in insertion order.
1145
+ * @param callback Function to execute for each entry in the map.
1146
+ */
1147
+ forEach(callback) {
1148
+ for (const entry of this) {
1149
+ callback(entry[1], entry[0], this);
1150
+ }
1151
+ }
1152
+ }
1153
+
1154
+ /**
1155
+ * Alternative to JSON.parse() that will not throw in production. If the passed
1156
+ * string cannot be parsed, this will return `undefined`.
1157
+ */
1158
+ function parseJson(rawMessage) {
1159
+ try {
1160
+ // eslint-disable-next-line no-restricted-syntax
1161
+ return JSON.parse(rawMessage);
1162
+ }
1163
+ catch (e) {
1164
+ return undefined;
1165
+ }
1166
+ }
1167
+ function isJsonArray(data) {
1168
+ return Array.isArray(data);
1169
+ }
1170
+ function isJsonObject(data) {
1171
+ return data !== null && typeof data === "object" && !isJsonArray(data);
1172
+ }
1173
+
1174
+ // Keeps a set of deprecation messages in memory that it has warned about
1175
+ // already. There will be only one deprecation message in the console, no
1176
+ // matter how often it gets called.
1177
+ const _emittedDeprecationWarnings = new Set();
1178
+ /**
1179
+ * Displays a deprecation warning in the dev console. Only in dev mode, and
1180
+ * only once per message/key. In production, this is a no-op.
1181
+ */
1182
+ function deprecate(message, key = message) {
1183
+ if (process.env.NODE_ENV !== "production") {
1184
+ if (!_emittedDeprecationWarnings.has(key)) {
1185
+ _emittedDeprecationWarnings.add(key);
1186
+ console.warn(`DEPRECATION WARNING: ${message}`);
1187
+ }
1188
+ }
1189
+ }
1190
+ /**
1191
+ * Conditionally displays a deprecation warning in the dev
1192
+ * console if the first argument is truthy. Only in dev mode, and
1193
+ * only once per message/key. In production, this is a no-op.
1194
+ */
1195
+ function deprecateIf(condition, message, key = message) {
1196
+ if (process.env.NODE_ENV !== "production") {
1197
+ if (condition) {
1198
+ deprecate(message, key);
1199
+ }
1200
+ }
1201
+ }
1202
+ function remove(array, item) {
1203
+ for (let i = 0; i < array.length; i++) {
1204
+ if (array[i] === item) {
1205
+ array.splice(i, 1);
1206
+ break;
1207
+ }
1208
+ }
1209
+ }
1210
+ /**
1211
+ * Removes null and undefined values from the array, and reflects this in the
1212
+ * output type.
1213
+ */
1214
+ function compact(items) {
1215
+ return items.filter((item) => item != null);
1216
+ }
1217
+ function creationOpToLiveStructure(op) {
1218
+ switch (op.type) {
1219
+ case OpType.CreateRegister:
1220
+ return new LiveRegister(op.data);
1221
+ case OpType.CreateObject:
1222
+ return new LiveObject(op.data);
1223
+ case OpType.CreateMap:
1224
+ return new LiveMap();
1225
+ case OpType.CreateList:
1226
+ return new LiveList();
1227
+ }
1228
+ }
1229
+ function isSameNodeOrChildOf(node, parent) {
1230
+ if (node === parent) {
1231
+ return true;
1232
+ }
1233
+ if (node._parent) {
1234
+ return isSameNodeOrChildOf(node._parent, parent);
1235
+ }
1236
+ return false;
1237
+ }
1238
+ function deserialize(entry, parentToChildren, doc) {
1239
+ switch (entry[1].type) {
1240
+ case CrdtType.Object: {
1241
+ return LiveObject._deserialize(entry, parentToChildren, doc);
1242
+ }
1243
+ case CrdtType.List: {
1244
+ return LiveList._deserialize(entry, parentToChildren, doc);
1245
+ }
1246
+ case CrdtType.Map: {
1247
+ return LiveMap._deserialize(entry, parentToChildren, doc);
1248
+ }
1249
+ case CrdtType.Register: {
1250
+ return LiveRegister._deserialize(entry, parentToChildren, doc);
1251
+ }
1252
+ default: {
1253
+ throw new Error("Unexpected CRDT type");
1254
+ }
1255
+ }
1256
+ }
1257
+ function isCrdt(obj) {
1258
+ return (obj instanceof LiveObject ||
1259
+ obj instanceof LiveMap ||
1260
+ obj instanceof LiveList ||
1261
+ obj instanceof LiveRegister);
1262
+ }
1263
+ function selfOrRegisterValue(obj) {
1264
+ if (obj instanceof LiveRegister) {
1265
+ return obj.data;
1266
+ }
1267
+ return obj;
1268
+ }
1269
+ function selfOrRegister(obj) {
1270
+ if (obj instanceof LiveObject ||
1271
+ obj instanceof LiveMap ||
1272
+ obj instanceof LiveList) {
1273
+ return obj;
1274
+ }
1275
+ else if (obj instanceof LiveRegister) {
1276
+ throw new Error("Internal error. LiveRegister should not be created from selfOrRegister");
1277
+ }
1278
+ else {
1279
+ // By now, we've checked that obj isn't a Live storage instance.
1280
+ // Technically what remains here can still be a (1) live data scalar, or
1281
+ // a (2) list of Lson values, or (3) an object with Lson values.
1282
+ //
1283
+ // Of these, (1) is fine, because a live data scalar is also a legal Json
1284
+ // scalar.
1285
+ //
1286
+ // But (2) and (3) are only technically fine if those only contain Json
1287
+ // values. Technically, these can still contain nested Live storage
1288
+ // instances, and we should probably assert that they don't at runtime.
1289
+ //
1290
+ // TypeScript understands this and doesn't let us use `obj` until we do :)
1291
+ //
1292
+ return new LiveRegister(obj);
1293
+ // ^^^^^^^
1294
+ // TODO: Better to assert than to force-cast here!
1295
+ }
1296
+ }
1297
+ function getTreesDiffOperations(currentItems, newItems) {
1298
+ const ops = [];
1299
+ currentItems.forEach((_, id) => {
1300
+ if (!newItems.get(id)) {
1301
+ // Delete crdt
1302
+ ops.push({
1303
+ type: OpType.DeleteCrdt,
1304
+ id: id,
1305
+ });
1306
+ }
1307
+ });
1308
+ newItems.forEach((crdt, id) => {
1309
+ const currentCrdt = currentItems.get(id);
1310
+ if (currentCrdt) {
1311
+ if (crdt.type === CrdtType.Object) {
1312
+ if (JSON.stringify(crdt.data) !==
1313
+ JSON.stringify(currentCrdt.data)) {
1314
+ ops.push({
1315
+ type: OpType.UpdateObject,
1316
+ id: id,
1317
+ data: crdt.data,
1318
+ });
1319
+ }
1320
+ }
1321
+ if (crdt.parentKey !== currentCrdt.parentKey) {
1322
+ ops.push({
1323
+ type: OpType.SetParentKey,
1324
+ id: id,
1325
+ parentKey: crdt.parentKey,
1326
+ });
1327
+ }
1328
+ }
1329
+ else {
1330
+ // new Crdt
1331
+ switch (crdt.type) {
1332
+ case CrdtType.Register:
1333
+ ops.push({
1334
+ type: OpType.CreateRegister,
1335
+ id: id,
1336
+ parentId: crdt.parentId,
1337
+ parentKey: crdt.parentKey,
1338
+ data: crdt.data,
1339
+ });
1340
+ break;
1341
+ case CrdtType.List:
1342
+ ops.push({
1343
+ type: OpType.CreateList,
1344
+ id: id,
1345
+ parentId: crdt.parentId,
1346
+ parentKey: crdt.parentKey,
1347
+ });
1348
+ break;
1349
+ case CrdtType.Object:
1350
+ ops.push({
1351
+ type: OpType.CreateObject,
1352
+ id: id,
1353
+ parentId: crdt.parentId,
1354
+ parentKey: crdt.parentKey,
1355
+ data: crdt.data,
1356
+ });
1357
+ break;
1358
+ case CrdtType.Map:
1359
+ ops.push({
1360
+ type: OpType.CreateMap,
1361
+ id: id,
1362
+ parentId: crdt.parentId,
1363
+ parentKey: crdt.parentKey,
1364
+ });
1365
+ break;
1366
+ }
1367
+ }
1368
+ });
1369
+ return ops;
1370
+ }
1371
+ function mergeObjectStorageUpdates(first, second) {
1372
+ const updates = first.updates;
1373
+ for (const [key, value] of entries(second.updates)) {
1374
+ updates[key] = value;
1375
+ }
1376
+ return Object.assign(Object.assign({}, second), { updates: updates });
1377
+ }
1378
+ function mergeMapStorageUpdates(first, second) {
1379
+ const updates = first.updates;
1380
+ for (const [key, value] of entries(second.updates)) {
1381
+ updates[key] = value;
1382
+ }
1383
+ return Object.assign(Object.assign({}, second), { updates: updates });
1384
+ }
1385
+ function mergeListStorageUpdates(first, second) {
1386
+ const updates = first.updates;
1387
+ return Object.assign(Object.assign({}, second), { updates: updates.concat(second.updates) });
1388
+ }
1389
+ // prettier-ignore
1390
+ function mergeStorageUpdates(first, second) {
1391
+ if (!first) {
1392
+ return second;
1393
+ }
1394
+ if (first.type === "LiveObject" && second.type === "LiveObject") {
1395
+ return mergeObjectStorageUpdates(first, second);
1396
+ }
1397
+ else if (first.type === "LiveMap" && second.type === "LiveMap") {
1398
+ return mergeMapStorageUpdates(first, second);
1399
+ }
1400
+ else if (first.type === "LiveList" && second.type === "LiveList") {
1401
+ return mergeListStorageUpdates(first, second);
1402
+ }
1403
+ else ;
1404
+ return second;
1405
+ }
1406
+ function isPlain(value) {
1407
+ const type = typeof value;
1408
+ return (type === "undefined" ||
1409
+ value === null ||
1410
+ type === "string" ||
1411
+ type === "boolean" ||
1412
+ type === "number" ||
1413
+ Array.isArray(value) ||
1414
+ isPlainObject(value));
1415
+ }
1416
+ function isPlainObject(value) {
1417
+ if (typeof value !== "object" || value === null)
1418
+ return false;
1419
+ const proto = Object.getPrototypeOf(value);
1420
+ if (proto === null)
1421
+ return true;
1422
+ let baseProto = proto;
1423
+ while (Object.getPrototypeOf(baseProto) !== null) {
1424
+ baseProto = Object.getPrototypeOf(baseProto);
1425
+ }
1426
+ return proto === baseProto;
1427
+ }
1428
+ function findNonSerializableValue(value, path = "") {
1429
+ if (!isPlain) {
1430
+ return {
1431
+ path: path || "root",
1432
+ value: value,
1433
+ };
1434
+ }
1435
+ if (typeof value !== "object" || value === null) {
1436
+ return false;
1437
+ }
1438
+ for (const [key, nestedValue] of Object.entries(value)) {
1439
+ const nestedPath = path ? path + "." + key : key;
1440
+ if (!isPlain(nestedValue)) {
1441
+ return {
1442
+ path: nestedPath,
1443
+ value: nestedValue,
1444
+ };
1445
+ }
1446
+ if (typeof nestedValue === "object") {
1447
+ const nonSerializableNestedValue = findNonSerializableValue(nestedValue, nestedPath);
1448
+ if (nonSerializableNestedValue) {
1449
+ return nonSerializableNestedValue;
1450
+ }
1451
+ }
1452
+ }
1453
+ return false;
1454
+ }
1455
+ function isTokenValid(token) {
1456
+ const tokenParts = token.split(".");
1457
+ if (tokenParts.length !== 3) {
1458
+ return false;
1459
+ }
1460
+ const data = parseJson(atob(tokenParts[1]));
1461
+ if (data === undefined ||
1462
+ !isJsonObject(data) ||
1463
+ typeof data.exp !== "number") {
1464
+ return false;
1465
+ }
1466
+ const now = Date.now();
1467
+ if (now / 1000 > data.exp - 300) {
1468
+ return false;
1469
+ }
1470
+ return true;
1471
+ }
1472
+ /**
1473
+ * Drop-in replacement for Object.entries() that retains better types.
1474
+ */
1475
+ function entries(obj) {
1476
+ return Object.entries(obj);
1477
+ }
1478
+
1479
+ /**
1480
+ * The LiveObject class is similar to a JavaScript object that is synchronized on all clients.
1481
+ * Keys should be a string, and values should be serializable to JSON.
1482
+ * If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.
1483
+ */
1484
+ class LiveObject extends AbstractCrdt {
1485
+ constructor(object = {}) {
1486
+ super();
1487
+ this._propToLastUpdate = new Map();
1488
+ for (const key in object) {
1489
+ const value = object[key];
1490
+ if (value instanceof AbstractCrdt) {
1491
+ value._setParentLink(this, key);
1492
+ }
1493
+ }
1494
+ this._map = new Map(Object.entries(object));
1495
+ }
1496
+ /**
1497
+ * @internal
1498
+ */
1499
+ _serialize(parentId, parentKey, doc, intent) {
1500
+ if (this._id == null) {
1501
+ throw new Error("Cannot serialize item is not attached");
1502
+ }
1503
+ const ops = [];
1504
+ const op = {
1505
+ id: this._id,
1506
+ opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(),
1507
+ intent,
1508
+ type: OpType.CreateObject,
1509
+ parentId,
1510
+ parentKey,
1511
+ data: {},
1512
+ };
1513
+ ops.push(op);
1514
+ for (const [key, value] of this._map) {
1515
+ if (value instanceof AbstractCrdt) {
1516
+ ops.push(...value._serialize(this._id, key, doc));
1517
+ }
1518
+ else {
1519
+ op.data[key] = value;
1520
+ }
1521
+ }
1522
+ return ops;
1523
+ }
1524
+ /**
1525
+ * @internal
1526
+ */
1527
+ static _deserialize([id, item], parentToChildren, doc) {
1528
+ if (item.type !== CrdtType.Object) {
1529
+ throw new Error(`Tried to deserialize a record but item type is "${item.type}"`);
1530
+ }
1531
+ const object = new LiveObject(item.data);
1532
+ object._attach(id, doc);
1533
+ return this._deserializeChildren(object, parentToChildren, doc);
1534
+ }
1535
+ /**
1536
+ * @internal
1537
+ */
1538
+ static _deserializeChildren(object, parentToChildren, doc) {
1539
+ const children = parentToChildren.get(object._id);
1540
+ if (children == null) {
1541
+ return object;
1542
+ }
1543
+ for (const entry of children) {
1544
+ const crdt = entry[1];
1545
+ if (crdt.parentKey == null) {
1546
+ throw new Error("Tried to deserialize a crdt but it does not have a parentKey and is not the root");
1547
+ }
1548
+ const child = deserialize(entry, parentToChildren, doc);
1549
+ child._setParentLink(object, crdt.parentKey);
1550
+ object._map.set(crdt.parentKey, child);
1551
+ }
1552
+ return object;
1553
+ }
1554
+ /**
1555
+ * @internal
1556
+ */
1557
+ _attach(id, doc) {
1558
+ super._attach(id, doc);
1559
+ for (const [_key, value] of this._map) {
1560
+ if (value instanceof AbstractCrdt) {
1561
+ value._attach(doc.generateId(), doc);
1562
+ }
1563
+ }
1564
+ }
1565
+ /**
1566
+ * @internal
1567
+ */
1568
+ _attachChild(op, isLocal) {
1569
+ if (this._doc == null) {
1570
+ throw new Error("Can't attach child if doc is not present");
1571
+ }
1572
+ const { id, parentKey, opId } = op;
1573
+ const key = parentKey;
1574
+ const child = creationOpToLiveStructure(op);
1575
+ if (this._doc.getItem(id) !== undefined) {
1576
+ if (this._propToLastUpdate.get(key) === opId) {
1577
+ // Acknowlegment from local operation
1578
+ this._propToLastUpdate.delete(key);
1579
+ }
1580
+ return { modified: false };
1581
+ }
1582
+ if (isLocal) {
1583
+ this._propToLastUpdate.set(key, opId);
1584
+ }
1585
+ else if (this._propToLastUpdate.get(key) === undefined) ;
1586
+ else if (this._propToLastUpdate.get(key) === opId) {
1587
+ // Acknowlegment from local operation
1588
+ this._propToLastUpdate.delete(key);
1589
+ return { modified: false };
1590
+ }
1591
+ else {
1592
+ // Conflict, ignore remote operation
1593
+ return { modified: false };
1594
+ }
1595
+ const previousValue = this._map.get(key);
1596
+ let reverse;
1597
+ if (isCrdt(previousValue)) {
1598
+ reverse = previousValue._serialize(this._id, key);
1599
+ previousValue._detach();
1600
+ }
1601
+ else if (previousValue === undefined) {
1602
+ reverse = [
1603
+ { type: OpType.DeleteObjectKey, id: this._id, key: key },
1604
+ ];
1605
+ }
1606
+ else {
1607
+ reverse = [
1608
+ {
1609
+ type: OpType.UpdateObject,
1610
+ id: this._id,
1611
+ data: { [key]: previousValue },
1612
+ },
1613
+ ];
1614
+ }
1615
+ this._map.set(key, child);
1616
+ child._setParentLink(this, key);
1617
+ child._attach(id, this._doc);
1618
+ return {
1619
+ reverse,
1620
+ modified: {
1621
+ node: this,
1622
+ type: "LiveObject",
1623
+ updates: { [key]: { type: "update" } },
1624
+ },
1625
+ };
1626
+ }
1627
+ /**
1628
+ * @internal
1629
+ */
1630
+ _detachChild(child) {
1631
+ if (child) {
1632
+ const reverse = child._serialize(this._id, child._parentKey, this._doc);
1633
+ for (const [key, value] of this._map) {
1634
+ if (value === child) {
1635
+ this._map.delete(key);
1636
+ }
1637
+ }
1638
+ child._detach();
1639
+ const storageUpdate = {
1640
+ node: this,
1641
+ type: "LiveObject",
1642
+ updates: {
1643
+ [child._parentKey]: { type: "delete" },
1644
+ },
1645
+ };
1646
+ return { modified: storageUpdate, reverse };
1647
+ }
1648
+ return { modified: false };
1649
+ }
1650
+ /**
1651
+ * @internal
1652
+ */
1653
+ _detachChildren() {
1654
+ for (const [key, value] of this._map) {
1655
+ this._map.delete(key);
1656
+ value._detach();
1657
+ }
1658
+ }
1659
+ /**
1660
+ * @internal
1661
+ */
1662
+ _detach() {
1663
+ super._detach();
1664
+ for (const value of this._map.values()) {
1665
+ if (isCrdt(value)) {
1666
+ value._detach();
1667
+ }
1668
+ }
1669
+ }
1670
+ /**
1671
+ * @internal
1672
+ */
1673
+ _apply(op, isLocal) {
1674
+ if (op.type === OpType.UpdateObject) {
1675
+ return this._applyUpdate(op, isLocal);
1676
+ }
1677
+ else if (op.type === OpType.DeleteObjectKey) {
1678
+ return this._applyDeleteObjectKey(op);
1679
+ }
1680
+ return super._apply(op, isLocal);
1681
+ }
1682
+ /**
1683
+ * @internal
1684
+ */
1685
+ _toSerializedCrdt() {
1686
+ var _a;
1687
+ const data = {};
1688
+ for (const [key, value] of this._map) {
1689
+ if (value instanceof AbstractCrdt === false) {
1690
+ data[key] = value;
1691
+ }
1692
+ }
1693
+ return {
1694
+ type: CrdtType.Object,
1695
+ parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id,
1696
+ parentKey: this._parentKey,
1697
+ data,
1698
+ };
1699
+ }
1700
+ _applyUpdate(op, isLocal) {
1701
+ let isModified = false;
1702
+ const reverse = [];
1703
+ const reverseUpdate = {
1704
+ type: OpType.UpdateObject,
1705
+ id: this._id,
1706
+ data: {},
1707
+ };
1708
+ reverse.push(reverseUpdate);
1709
+ for (const key in op.data) {
1710
+ const oldValue = this._map.get(key);
1711
+ if (oldValue instanceof AbstractCrdt) {
1712
+ reverse.push(...oldValue._serialize(this._id, key));
1713
+ oldValue._detach();
1714
+ }
1715
+ else if (oldValue !== undefined) {
1716
+ reverseUpdate.data[key] = oldValue;
1717
+ }
1718
+ else if (oldValue === undefined) {
1719
+ reverse.push({ type: OpType.DeleteObjectKey, id: this._id, key });
1720
+ }
1721
+ }
1722
+ const updateDelta = {};
1723
+ for (const key in op.data) {
1724
+ if (isLocal) {
1725
+ this._propToLastUpdate.set(key, op.opId);
1726
+ }
1727
+ else if (this._propToLastUpdate.get(key) == null) {
1728
+ // Not modified localy so we apply update
1729
+ isModified = true;
1730
+ }
1731
+ else if (this._propToLastUpdate.get(key) === op.opId) {
1732
+ // Acknowlegment from local operation
1733
+ this._propToLastUpdate.delete(key);
1734
+ continue;
1735
+ }
1736
+ else {
1737
+ // Conflict, ignore remote operation
1738
+ continue;
1739
+ }
1740
+ const oldValue = this._map.get(key);
1741
+ if (isCrdt(oldValue)) {
1742
+ oldValue._detach();
1743
+ }
1744
+ isModified = true;
1745
+ updateDelta[key] = { type: "update" };
1746
+ this._map.set(key, op.data[key]);
1747
+ }
1748
+ if (Object.keys(reverseUpdate.data).length !== 0) {
1749
+ reverse.unshift(reverseUpdate);
1750
+ }
1751
+ return isModified
1752
+ ? {
1753
+ modified: {
1754
+ node: this,
1755
+ type: "LiveObject",
1756
+ updates: updateDelta,
1757
+ },
1758
+ reverse,
1759
+ }
1760
+ : { modified: false };
1761
+ }
1762
+ _applyDeleteObjectKey(op) {
1763
+ const key = op.key;
1764
+ // If property does not exist, exit without notifying
1765
+ if (this._map.has(key) === false) {
1766
+ return { modified: false };
1767
+ }
1768
+ // If a local operation exists on the same key
1769
+ // prevent flickering by not applying delete op.
1770
+ if (this._propToLastUpdate.get(key) !== undefined) {
1771
+ return { modified: false };
1772
+ }
1773
+ const oldValue = this._map.get(key);
1774
+ let reverse = [];
1775
+ if (isCrdt(oldValue)) {
1776
+ reverse = oldValue._serialize(this._id, op.key);
1777
+ oldValue._detach();
1778
+ }
1779
+ else if (oldValue !== undefined) {
1780
+ reverse = [
1781
+ {
1782
+ type: OpType.UpdateObject,
1783
+ id: this._id,
1784
+ data: { [key]: oldValue },
1785
+ },
1786
+ ];
1787
+ }
1788
+ this._map.delete(key);
1789
+ return {
1790
+ modified: {
1791
+ node: this,
1792
+ type: "LiveObject",
1793
+ updates: { [op.key]: { type: "delete" } },
1794
+ },
1795
+ reverse,
1796
+ };
1797
+ }
1798
+ /**
1799
+ * Transform the LiveObject into a javascript object
1800
+ */
1801
+ toObject() {
1802
+ return Object.fromEntries(this._map);
1803
+ }
1804
+ /**
1805
+ * Adds or updates a property with a specified key and a value.
1806
+ * @param key The key of the property to add
1807
+ * @param value The value of the property to add
1808
+ */
1809
+ set(key, value) {
1810
+ // TODO: Find out why typescript complains
1811
+ this.update({ [key]: value });
1812
+ }
1813
+ /**
1814
+ * Returns a specified property from the LiveObject.
1815
+ * @param key The key of the property to get
1816
+ */
1817
+ get(key) {
1818
+ return this._map.get(key);
1819
+ }
1820
+ /**
1821
+ * Deletes a key from the LiveObject
1822
+ * @param key The key of the property to delete
1823
+ */
1824
+ delete(key) {
1825
+ const keyAsString = key;
1826
+ const oldValue = this._map.get(keyAsString);
1827
+ if (oldValue === undefined) {
1828
+ return;
1829
+ }
1830
+ if (this._doc == null || this._id == null) {
1831
+ if (oldValue instanceof AbstractCrdt) {
1832
+ oldValue._detach();
1833
+ }
1834
+ this._map.delete(keyAsString);
1835
+ return;
1836
+ }
1837
+ let reverse;
1838
+ if (oldValue instanceof AbstractCrdt) {
1839
+ oldValue._detach();
1840
+ reverse = oldValue._serialize(this._id, keyAsString);
1841
+ }
1842
+ else {
1843
+ reverse = [
1844
+ {
1845
+ type: OpType.UpdateObject,
1846
+ data: { [keyAsString]: oldValue },
1847
+ id: this._id,
1848
+ },
1849
+ ];
1850
+ }
1851
+ this._map.delete(keyAsString);
1852
+ const storageUpdates = new Map();
1853
+ storageUpdates.set(this._id, {
1854
+ node: this,
1855
+ type: "LiveObject",
1856
+ updates: { [key]: { type: "delete" } },
1857
+ });
1858
+ this._doc.dispatch([
1859
+ {
1860
+ type: OpType.DeleteObjectKey,
1861
+ key: keyAsString,
1862
+ id: this._id,
1863
+ opId: this._doc.generateOpId(),
1864
+ },
1865
+ ], reverse, storageUpdates);
1866
+ }
1867
+ /**
1868
+ * Adds or updates multiple properties at once with an object.
1869
+ * @param overrides The object used to overrides properties
1870
+ */
1871
+ update(overrides) {
1872
+ if (this._doc == null || this._id == null) {
1873
+ for (const key in overrides) {
1874
+ const oldValue = this._map.get(key);
1875
+ if (oldValue instanceof AbstractCrdt) {
1876
+ oldValue._detach();
1877
+ }
1878
+ const newValue = overrides[key];
1879
+ if (newValue instanceof AbstractCrdt) {
1880
+ newValue._setParentLink(this, key);
1881
+ }
1882
+ this._map.set(key, newValue);
1883
+ }
1884
+ return;
1885
+ }
1886
+ const ops = [];
1887
+ const reverseOps = [];
1888
+ const opId = this._doc.generateOpId();
1889
+ const updatedProps = {};
1890
+ const reverseUpdateOp = {
1891
+ id: this._id,
1892
+ type: OpType.UpdateObject,
1893
+ data: {},
1894
+ };
1895
+ const updateDelta = {};
1896
+ for (const key in overrides) {
1897
+ const oldValue = this._map.get(key);
1898
+ if (oldValue instanceof AbstractCrdt) {
1899
+ reverseOps.push(...oldValue._serialize(this._id, key));
1900
+ oldValue._detach();
1901
+ }
1902
+ else if (oldValue === undefined) {
1903
+ reverseOps.push({ type: OpType.DeleteObjectKey, id: this._id, key });
1904
+ }
1905
+ else {
1906
+ reverseUpdateOp.data[key] = oldValue;
1907
+ }
1908
+ const newValue = overrides[key];
1909
+ if (newValue instanceof AbstractCrdt) {
1910
+ newValue._setParentLink(this, key);
1911
+ newValue._attach(this._doc.generateId(), this._doc);
1912
+ const newAttachChildOps = newValue._serialize(this._id, key, this._doc);
1913
+ const createCrdtOp = newAttachChildOps.find((op) => op.parentId === this._id);
1914
+ if (createCrdtOp) {
1915
+ this._propToLastUpdate.set(key, createCrdtOp.opId);
1916
+ }
1917
+ ops.push(...newAttachChildOps);
1918
+ }
1919
+ else {
1920
+ updatedProps[key] = newValue;
1921
+ this._propToLastUpdate.set(key, opId);
1922
+ }
1923
+ this._map.set(key, newValue);
1924
+ updateDelta[key] = { type: "update" };
1925
+ }
1926
+ if (Object.keys(reverseUpdateOp.data).length !== 0) {
1927
+ reverseOps.unshift(reverseUpdateOp);
1928
+ }
1929
+ if (Object.keys(updatedProps).length !== 0) {
1930
+ ops.unshift({
1931
+ opId,
1932
+ id: this._id,
1933
+ type: OpType.UpdateObject,
1934
+ data: updatedProps,
1935
+ });
1936
+ }
1937
+ const storageUpdates = new Map();
1938
+ storageUpdates.set(this._id, {
1939
+ node: this,
1940
+ type: "LiveObject",
1941
+ updates: updateDelta,
1942
+ });
1943
+ this._doc.dispatch(ops, reverseOps, storageUpdates);
1944
+ }
1945
+ }
1946
+
1947
+ export { AbstractCrdt as A, ClientMessageType as C, LiveObject as L, OpType as O, ServerMessageType as S, WebsocketCloseCodes as W, isSameNodeOrChildOf as a, LiveList as b, isJsonArray as c, compact as d, isJsonObject as e, LiveMap as f, getTreesDiffOperations as g, LiveRegister as h, isTokenValid as i, findNonSerializableValue as j, deprecate as k, deprecateIf as l, mergeStorageUpdates as m, CrdtType as n, min as o, parseJson as p, max as q, remove as r, makePosition as s, posCodes as t, pos as u, compare as v };