@liveblocks/client 0.13.2 → 0.15.0-alpha.1

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.
Files changed (41) hide show
  1. package/lib/cjs/AbstractCrdt.d.ts +13 -7
  2. package/lib/cjs/AbstractCrdt.js +2 -5
  3. package/lib/cjs/LiveList.d.ts +17 -6
  4. package/lib/cjs/LiveList.js +140 -15
  5. package/lib/cjs/LiveMap.d.ts +12 -4
  6. package/lib/cjs/LiveMap.js +57 -7
  7. package/lib/cjs/LiveObject.d.ts +22 -8
  8. package/lib/cjs/LiveObject.js +109 -24
  9. package/lib/cjs/LiveRegister.d.ts +13 -5
  10. package/lib/cjs/LiveRegister.js +23 -4
  11. package/lib/cjs/{immutable/index.d.ts → immutable.d.ts} +5 -3
  12. package/lib/cjs/{immutable/index.js → immutable.js} +77 -19
  13. package/lib/cjs/index.d.ts +2 -1
  14. package/lib/cjs/index.js +8 -1
  15. package/lib/cjs/live.d.ts +7 -0
  16. package/lib/cjs/room.d.ts +11 -3
  17. package/lib/cjs/room.js +150 -76
  18. package/lib/cjs/types.d.ts +33 -1
  19. package/lib/cjs/utils.d.ts +4 -1
  20. package/lib/cjs/utils.js +101 -1
  21. package/lib/esm/AbstractCrdt.d.ts +13 -7
  22. package/lib/esm/AbstractCrdt.js +2 -5
  23. package/lib/esm/LiveList.d.ts +17 -6
  24. package/lib/esm/LiveList.js +141 -16
  25. package/lib/esm/LiveMap.d.ts +12 -4
  26. package/lib/esm/LiveMap.js +57 -7
  27. package/lib/esm/LiveObject.d.ts +22 -8
  28. package/lib/esm/LiveObject.js +109 -24
  29. package/lib/esm/LiveRegister.d.ts +13 -5
  30. package/lib/esm/LiveRegister.js +24 -5
  31. package/lib/esm/{immutable/index.d.ts → immutable.d.ts} +5 -3
  32. package/lib/esm/{immutable/index.js → immutable.js} +75 -19
  33. package/lib/esm/index.d.ts +2 -1
  34. package/lib/esm/index.js +1 -0
  35. package/lib/esm/live.d.ts +7 -0
  36. package/lib/esm/room.d.ts +11 -3
  37. package/lib/esm/room.js +151 -77
  38. package/lib/esm/types.d.ts +33 -1
  39. package/lib/esm/utils.d.ts +4 -1
  40. package/lib/esm/utils.js +99 -1
  41. 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
  },
@@ -129,18 +132,23 @@ function makeStateMachine(state, context, mockedEffects) {
129
132
  };
130
133
  return genericSubscribe(cb);
131
134
  }
132
- function createRootFromMessage(message) {
133
- state.root = load(message.items);
135
+ function createOrUpdateRootFromMessage(message) {
136
+ if (message.items.length === 0) {
137
+ throw new Error("Internal error: cannot load storage without items");
138
+ }
139
+ if (state.root) {
140
+ updateRoot(message.items);
141
+ }
142
+ else {
143
+ state.root = load(message.items);
144
+ }
134
145
  for (const key in state.defaultStorageRoot) {
135
146
  if (state.root.get(key) == null) {
136
147
  state.root.set(key, state.defaultStorageRoot[key]);
137
148
  }
138
149
  }
139
150
  }
140
- function load(items) {
141
- if (items.length === 0) {
142
- throw new Error("Internal error: cannot load storage without items");
143
- }
151
+ function buildRootAndParentToChildren(items) {
144
152
  const parentToChildren = new Map();
145
153
  let root = null;
146
154
  for (const tuple of items) {
@@ -161,6 +169,23 @@ function makeStateMachine(state, context, mockedEffects) {
161
169
  if (root == null) {
162
170
  throw new Error("Root can't be null");
163
171
  }
172
+ return [root, parentToChildren];
173
+ }
174
+ function updateRoot(items) {
175
+ if (!state.root) {
176
+ return;
177
+ }
178
+ const currentItems = new Map();
179
+ state.items.forEach((liveCrdt, id) => {
180
+ currentItems.set(id, liveCrdt._toSerializedCrdt());
181
+ });
182
+ // Get operations that represent the diff between 2 states.
183
+ const ops = (0, utils_1.getTreesDiffOperations)(currentItems, new Map(items));
184
+ const result = apply(ops, false);
185
+ notify(result.updates);
186
+ }
187
+ function load(items) {
188
+ const [root, parentToChildren] = buildRootAndParentToChildren(items);
164
189
  return LiveObject_1.LiveObject._deserialize(root, parentToChildren, {
165
190
  addItem,
166
191
  deleteItem,
@@ -190,22 +215,22 @@ function makeStateMachine(state, context, mockedEffects) {
190
215
  state.undoStack.push(historyItem);
191
216
  }
192
217
  }
193
- function storageDispatch(ops, reverse, modified) {
218
+ function storageDispatch(ops, reverse, storageUpdates) {
194
219
  if (state.isBatching) {
195
220
  state.batch.ops.push(...ops);
196
- for (const item of modified) {
197
- state.batch.updates.nodes.add(item);
198
- }
221
+ storageUpdates.forEach((value, key) => {
222
+ state.batch.updates.storageUpdates.set(key, (0, utils_1.mergeStorageUpdates)(state.batch.updates.storageUpdates.get(key), value));
223
+ });
199
224
  state.batch.reverseOps.push(...reverse);
200
225
  }
201
226
  else {
202
227
  addToUndoStack(reverse);
203
228
  state.redoStack = [];
204
229
  dispatch(ops);
205
- notify({ nodes: new Set(modified) });
230
+ notify({ storageUpdates: storageUpdates });
206
231
  }
207
232
  }
208
- function notify({ nodes = new Set(), presence = false, others = [], }) {
233
+ function notify({ storageUpdates = new Map(), presence = false, others = [], }) {
209
234
  if (others.length > 0) {
210
235
  state.others = makeOthers(state.users);
211
236
  for (const event of others) {
@@ -219,28 +244,9 @@ function makeStateMachine(state, context, mockedEffects) {
219
244
  listener(state.me);
220
245
  }
221
246
  }
222
- if (nodes.size > 0) {
247
+ if (storageUpdates.size > 0) {
223
248
  for (const subscriber of state.listeners.storage) {
224
- subscriber(Array.from(nodes).map((m) => {
225
- if (m instanceof LiveObject_1.LiveObject) {
226
- return {
227
- type: "LiveObject",
228
- node: m,
229
- };
230
- }
231
- else if (m instanceof LiveList_1.LiveList) {
232
- return {
233
- type: "LiveList",
234
- node: m,
235
- };
236
- }
237
- else {
238
- return {
239
- type: "LiveMap",
240
- node: m,
241
- };
242
- }
243
- }));
249
+ subscriber(Array.from(storageUpdates.values()));
244
250
  }
245
251
  }
246
252
  }
@@ -249,7 +255,10 @@ function makeStateMachine(state, context, mockedEffects) {
249
255
  state.connection.state === "connecting") {
250
256
  return state.connection.id;
251
257
  }
252
- throw new Error("Internal. Tried to get connection id but connection is not open");
258
+ else if (state.lastConnectionId !== null) {
259
+ return state.lastConnectionId;
260
+ }
261
+ throw new Error("Internal. Tried to get connection id but connection was never open");
253
262
  }
254
263
  function generateId() {
255
264
  return `${getConnectionId()}:${state.clock++}`;
@@ -257,10 +266,13 @@ function makeStateMachine(state, context, mockedEffects) {
257
266
  function generateOpId() {
258
267
  return `${getConnectionId()}:${state.opClock++}`;
259
268
  }
260
- function apply(item) {
269
+ function apply(item, isLocal) {
261
270
  const result = {
262
271
  reverse: [],
263
- updates: { nodes: new Set(), presence: false },
272
+ updates: {
273
+ storageUpdates: new Map(),
274
+ presence: false,
275
+ },
264
276
  };
265
277
  for (const op of item) {
266
278
  if (op.type === "presence") {
@@ -284,16 +296,23 @@ function makeStateMachine(state, context, mockedEffects) {
284
296
  result.updates.presence = true;
285
297
  }
286
298
  else {
287
- const applyOpResult = applyOp(op);
299
+ // Ops applied after undo/redo don't have an opId.
300
+ if (isLocal && !op.opId) {
301
+ op.opId = generateOpId();
302
+ }
303
+ const applyOpResult = applyOp(op, isLocal);
288
304
  if (applyOpResult.modified) {
289
- result.updates.nodes.add(applyOpResult.modified);
305
+ result.updates.storageUpdates.set(applyOpResult.modified.node._id, (0, utils_1.mergeStorageUpdates)(result.updates.storageUpdates.get(applyOpResult.modified.node._id), applyOpResult.modified));
290
306
  result.reverse.unshift(...applyOpResult.reverse);
291
307
  }
292
308
  }
293
309
  }
294
310
  return result;
295
311
  }
296
- function applyOp(op) {
312
+ function applyOp(op, isLocal) {
313
+ if (op.opId) {
314
+ state.offlineOperations.delete(op.opId);
315
+ }
297
316
  switch (op.type) {
298
317
  case live_1.OpType.DeleteObjectKey:
299
318
  case live_1.OpType.UpdateObject:
@@ -302,7 +321,7 @@ function makeStateMachine(state, context, mockedEffects) {
302
321
  if (item == null) {
303
322
  return { modified: false };
304
323
  }
305
- return item._apply(op);
324
+ return item._apply(op, isLocal);
306
325
  }
307
326
  case live_1.OpType.SetParentKey: {
308
327
  const item = state.items.get(op.id);
@@ -311,17 +330,12 @@ function makeStateMachine(state, context, mockedEffects) {
311
330
  }
312
331
  if (item._parent instanceof LiveList_1.LiveList) {
313
332
  const previousKey = item._parentKey;
314
- item._parent._setChildKey(op.parentKey, item);
315
- return {
316
- reverse: [
317
- {
318
- type: live_1.OpType.SetParentKey,
319
- id: item._id,
320
- parentKey: previousKey,
321
- },
322
- ],
323
- modified: item._parent,
324
- };
333
+ if (previousKey === op.parentKey) {
334
+ return { modified: false };
335
+ }
336
+ else {
337
+ return item._parent._setChildKey(op.parentKey, item, previousKey);
338
+ }
325
339
  }
326
340
  return { modified: false };
327
341
  }
@@ -330,28 +344,28 @@ function makeStateMachine(state, context, mockedEffects) {
330
344
  if (parent == null || getItem(op.id) != null) {
331
345
  return { modified: false };
332
346
  }
333
- return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data));
347
+ return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data), isLocal);
334
348
  }
335
349
  case live_1.OpType.CreateList: {
336
350
  const parent = state.items.get(op.parentId);
337
351
  if (parent == null || getItem(op.id) != null) {
338
352
  return { modified: false };
339
353
  }
340
- return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList());
354
+ return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList(), isLocal);
341
355
  }
342
356
  case live_1.OpType.CreateRegister: {
343
357
  const parent = state.items.get(op.parentId);
344
358
  if (parent == null || getItem(op.id) != null) {
345
359
  return { modified: false };
346
360
  }
347
- return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data));
361
+ return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data), isLocal);
348
362
  }
349
363
  case live_1.OpType.CreateMap: {
350
364
  const parent = state.items.get(op.parentId);
351
365
  if (parent == null || getItem(op.id) != null) {
352
366
  return { modified: false };
353
367
  }
354
- return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap());
368
+ return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap(), isLocal);
355
369
  }
356
370
  }
357
371
  return { modified: false };
@@ -443,7 +457,9 @@ See v0.13 release notes for more information.
443
457
  state.socket = socket;
444
458
  }
445
459
  function authenticationFailure(error) {
446
- console.error(error);
460
+ if (process.env.NODE_ENV !== "production") {
461
+ console.error("Call to authentication endpoint failed", error);
462
+ }
447
463
  updateConnection({ state: "unavailable" });
448
464
  state.numberOfRetry++;
449
465
  state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
@@ -542,7 +558,7 @@ See v0.13 release notes for more information.
542
558
  subMessages.push(message);
543
559
  }
544
560
  const updates = {
545
- nodes: new Set(),
561
+ storageUpdates: new Map(),
546
562
  others: [],
547
563
  };
548
564
  for (const subMessage of subMessages) {
@@ -571,15 +587,16 @@ See v0.13 release notes for more information.
571
587
  break;
572
588
  }
573
589
  case live_1.ServerMessageType.InitialStorageState: {
574
- createRootFromMessage(subMessage);
590
+ createOrUpdateRootFromMessage(subMessage);
591
+ applyAndSendOfflineOps();
575
592
  _getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
576
593
  break;
577
594
  }
578
595
  case live_1.ServerMessageType.UpdateStorage: {
579
- const applyResult = apply(subMessage.ops);
580
- for (const node of applyResult.updates.nodes) {
581
- updates.nodes.add(node);
582
- }
596
+ const applyResult = apply(subMessage.ops, false);
597
+ applyResult.updates.storageUpdates.forEach((value, key) => {
598
+ updates.storageUpdates.set(key, (0, utils_1.mergeStorageUpdates)(updates.storageUpdates.get(key), value));
599
+ });
583
600
  break;
584
601
  }
585
602
  }
@@ -609,14 +626,20 @@ See v0.13 release notes for more information.
609
626
  updateConnection({ state: "failed" });
610
627
  const error = new LiveblocksError(event.reason, event.code);
611
628
  for (const listener of state.listeners.error) {
612
- console.error(`Liveblocks WebSocket connection closed. Reason: ${error.message} (code: ${error.code})`);
629
+ if (process.env.NODE_ENV !== "production") {
630
+ console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
631
+ }
613
632
  listener(error);
614
633
  }
615
634
  }
616
635
  else if (event.wasClean === false) {
617
- updateConnection({ state: "unavailable" });
618
636
  state.numberOfRetry++;
619
- state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
637
+ const delay = getRetryDelay();
638
+ if (process.env.NODE_ENV !== "production") {
639
+ console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
640
+ }
641
+ updateConnection({ state: "unavailable" });
642
+ state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
620
643
  }
621
644
  else {
622
645
  updateConnection({ state: "closed" });
@@ -640,6 +663,10 @@ See v0.13 release notes for more information.
640
663
  if (state.connection.state === "connecting") {
641
664
  updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
642
665
  state.numberOfRetry = 0;
666
+ state.lastConnectionId = state.connection.id;
667
+ if (state.root) {
668
+ state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage });
669
+ }
643
670
  tryFlushing();
644
671
  }
645
672
  else {
@@ -679,11 +706,29 @@ See v0.13 release notes for more information.
679
706
  clearInterval(state.intervalHandles.heartbeat);
680
707
  connect();
681
708
  }
682
- function tryFlushing() {
683
- if (state.socket == null) {
709
+ function applyAndSendOfflineOps() {
710
+ if (state.offlineOperations.size === 0) {
684
711
  return;
685
712
  }
686
- if (state.socket.readyState !== WebSocket.OPEN) {
713
+ const messages = [];
714
+ const ops = Array.from(state.offlineOperations.values());
715
+ const result = apply(ops, true);
716
+ messages.push({
717
+ type: live_1.ClientMessageType.UpdateStorage,
718
+ ops: ops,
719
+ });
720
+ notify(result.updates);
721
+ effects.send(messages);
722
+ }
723
+ function tryFlushing() {
724
+ const storageOps = state.buffer.storageOperations;
725
+ if (storageOps.length > 0) {
726
+ storageOps.forEach((op) => {
727
+ state.offlineOperations.set(op.opId, op);
728
+ });
729
+ }
730
+ if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
731
+ state.buffer.storageOperations = [];
687
732
  return;
688
733
  }
689
734
  const now = Date.now();
@@ -758,8 +803,10 @@ See v0.13 release notes for more information.
758
803
  function getOthers() {
759
804
  return state.others;
760
805
  }
761
- function broadcastEvent(event) {
762
- if (state.socket == null) {
806
+ function broadcastEvent(event, options = {
807
+ shouldQueueEventIfNotReady: false,
808
+ }) {
809
+ if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
763
810
  return;
764
811
  }
765
812
  state.buffer.messages.push({
@@ -801,7 +848,7 @@ See v0.13 release notes for more information.
801
848
  return;
802
849
  }
803
850
  state.isHistoryPaused = false;
804
- const result = apply(historyItem);
851
+ const result = apply(historyItem, true);
805
852
  notify(result.updates);
806
853
  state.redoStack.push(result.reverse);
807
854
  for (const op of historyItem) {
@@ -820,7 +867,7 @@ See v0.13 release notes for more information.
820
867
  return;
821
868
  }
822
869
  state.isHistoryPaused = false;
823
- const result = apply(historyItem);
870
+ const result = apply(historyItem, true);
824
871
  notify(result.updates);
825
872
  state.undoStack.push(result.reverse);
826
873
  for (const op of historyItem) {
@@ -843,8 +890,11 @@ See v0.13 release notes for more information.
843
890
  if (state.batch.reverseOps.length > 0) {
844
891
  addToUndoStack(state.batch.reverseOps);
845
892
  }
846
- // Clear the redo stack because batch is always called from a local operation
847
- state.redoStack = [];
893
+ if (state.batch.ops.length > 0) {
894
+ // Only clear the redo stack if something has changed during a batch
895
+ // Clear the redo stack because batch is always called from a local operation
896
+ state.redoStack = [];
897
+ }
848
898
  if (state.batch.ops.length > 0) {
849
899
  dispatch(state.batch.ops);
850
900
  }
@@ -854,7 +904,7 @@ See v0.13 release notes for more information.
854
904
  reverseOps: [],
855
905
  updates: {
856
906
  others: [],
857
- nodes: new Set(),
907
+ storageUpdates: new Map(),
858
908
  presence: false,
859
909
  },
860
910
  };
@@ -872,6 +922,16 @@ See v0.13 release notes for more information.
872
922
  }
873
923
  state.pausedHistory = [];
874
924
  }
925
+ function simulateSocketClose() {
926
+ if (state.socket) {
927
+ state.socket.close();
928
+ }
929
+ }
930
+ function simulateSendCloseEvent(event) {
931
+ if (state.socket) {
932
+ onClose(event);
933
+ }
934
+ }
875
935
  return {
876
936
  // Internal
877
937
  onOpen,
@@ -880,6 +940,9 @@ See v0.13 release notes for more information.
880
940
  authenticationSuccess,
881
941
  heartbeat,
882
942
  onNavigatorOnline,
943
+ // Internal dev tools
944
+ simulateSocketClose,
945
+ simulateSendCloseEvent,
883
946
  // onWakeUp,
884
947
  onVisibilityChange,
885
948
  getUndoStack: () => state.undoStack,
@@ -912,6 +975,7 @@ exports.makeStateMachine = makeStateMachine;
912
975
  function defaultState(me, defaultStorageRoot) {
913
976
  return {
914
977
  connection: { state: "closed" },
978
+ lastConnectionId: null,
915
979
  socket: null,
916
980
  listeners: {
917
981
  event: [],
@@ -953,9 +1017,14 @@ function defaultState(me, defaultStorageRoot) {
953
1017
  isBatching: false,
954
1018
  batch: {
955
1019
  ops: [],
956
- updates: { nodes: new Set(), presence: false, others: [] },
1020
+ updates: {
1021
+ storageUpdates: new Map(),
1022
+ presence: false,
1023
+ others: [],
1024
+ },
957
1025
  reverseOps: [],
958
1026
  },
1027
+ offlineOperations: new Map(),
959
1028
  };
960
1029
  }
961
1030
  exports.defaultState = defaultState;
@@ -1002,6 +1071,11 @@ function createRoom(name, options) {
1002
1071
  pause: machine.pauseHistory,
1003
1072
  resume: machine.resumeHistory,
1004
1073
  },
1074
+ // @ts-ignore
1075
+ internalDevTools: {
1076
+ closeWebsocket: machine.simulateSocketClose,
1077
+ sendCloseEvent: machine.simulateSendCloseEvent,
1078
+ },
1005
1079
  };
1006
1080
  return {
1007
1081
  connect: machine.connect,
@@ -16,17 +16,45 @@ export declare type RoomEventCallbackMap = {
16
16
  error: ErrorCallback;
17
17
  connection: ConnectionCallback;
18
18
  };
19
+ export declare type UpdateDelta = {
20
+ type: "update";
21
+ } | {
22
+ type: "delete";
23
+ };
19
24
  export declare type LiveMapUpdates<TKey extends string = string, TValue = any> = {
20
25
  type: "LiveMap";
21
26
  node: LiveMap<TKey, TValue>;
27
+ updates: Record<TKey, UpdateDelta>;
22
28
  };
29
+ export declare type LiveObjectUpdateDelta<T> = Partial<{
30
+ [Property in keyof T]: UpdateDelta;
31
+ }>;
23
32
  export declare type LiveObjectUpdates<TData = any> = {
24
33
  type: "LiveObject";
25
34
  node: LiveObject<TData>;
35
+ updates: LiveObjectUpdateDelta<TData>;
36
+ };
37
+ export declare type ListUpdateDelta = {
38
+ type: "insert";
39
+ } | {
40
+ type: "delete";
41
+ };
42
+ export declare type LiveListUpdateDelta = {
43
+ index: number;
44
+ type: "insert" | "delete";
26
45
  };
27
46
  export declare type LiveListUpdates<TItem = any> = {
28
47
  type: "LiveList";
29
48
  node: LiveList<TItem>;
49
+ updates: LiveListUpdateDelta[];
50
+ };
51
+ export declare type BroadcastOptions = {
52
+ /**
53
+ * Whether or not event is queued if the connection is currently closed.
54
+ *
55
+ * ❗ 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
56
+ */
57
+ shouldQueueEventIfNotReady: boolean;
30
58
  };
31
59
  export declare type StorageUpdate = LiveMapUpdates | LiveObjectUpdates | LiveListUpdates;
32
60
  export declare type StorageCallback = (updates: StorageUpdate[]) => void;
@@ -65,6 +93,10 @@ export interface Others<TPresence extends Presence = Presence> {
65
93
  * Number of other users in the room.
66
94
  */
67
95
  readonly count: number;
96
+ /**
97
+ * Returns a new Iterator object that contains the users.
98
+ */
99
+ [Symbol.iterator](): IterableIterator<User<TPresence>>;
68
100
  /**
69
101
  * Returns the array of connected users in room.
70
102
  */
@@ -423,7 +455,7 @@ export declare type Room = {
423
455
  * }
424
456
  * });
425
457
  */
426
- broadcastEvent: (event: any) => void;
458
+ broadcastEvent: (event: any, options?: BroadcastOptions) => void;
427
459
  /**
428
460
  * Get the room's storage asynchronously.
429
461
  * The storage's root is a {@link LiveObject}.
@@ -1,8 +1,11 @@
1
1
  import { AbstractCrdt, Doc } from "./AbstractCrdt";
2
- import { SerializedCrdtWithId } from "./live";
2
+ import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live";
3
+ import { StorageUpdate } from "./types";
3
4
  export declare function remove<T>(array: T[], item: T): void;
4
5
  export declare function isSameNodeOrChildOf(node: AbstractCrdt, parent: AbstractCrdt): boolean;
5
6
  export declare function deserialize(entry: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): AbstractCrdt;
6
7
  export declare function isCrdt(obj: any): obj is AbstractCrdt;
7
8
  export declare function selfOrRegisterValue(obj: AbstractCrdt): any;
8
9
  export declare function selfOrRegister(obj: any): AbstractCrdt;
10
+ export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[];
11
+ export declare function mergeStorageUpdates(first: StorageUpdate | undefined, second: StorageUpdate): StorageUpdate;
package/lib/cjs/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0;
3
+ exports.mergeStorageUpdates = exports.getTreesDiffOperations = exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0;
4
4
  const live_1 = require("./live");
5
5
  const LiveList_1 = require("./LiveList");
6
6
  const LiveMap_1 = require("./LiveMap");
@@ -73,3 +73,103 @@ function selfOrRegister(obj) {
73
73
  }
74
74
  }
75
75
  exports.selfOrRegister = selfOrRegister;
76
+ function getTreesDiffOperations(currentItems, newItems) {
77
+ const ops = [];
78
+ currentItems.forEach((_, id) => {
79
+ if (!newItems.get(id)) {
80
+ // Delete crdt
81
+ ops.push({
82
+ type: live_1.OpType.DeleteCrdt,
83
+ id: id,
84
+ });
85
+ }
86
+ });
87
+ newItems.forEach((crdt, id) => {
88
+ const currentCrdt = currentItems.get(id);
89
+ if (currentCrdt) {
90
+ if (crdt.type === live_1.CrdtType.Object) {
91
+ if (JSON.stringify(crdt.data) !==
92
+ JSON.stringify(currentCrdt.data)) {
93
+ ops.push({
94
+ type: live_1.OpType.UpdateObject,
95
+ id: id,
96
+ data: crdt.data,
97
+ });
98
+ }
99
+ }
100
+ if (crdt.parentKey !== currentCrdt.parentKey) {
101
+ ops.push({
102
+ type: live_1.OpType.SetParentKey,
103
+ id: id,
104
+ parentKey: crdt.parentKey,
105
+ });
106
+ }
107
+ }
108
+ else {
109
+ // new Crdt
110
+ switch (crdt.type) {
111
+ case live_1.CrdtType.Register:
112
+ ops.push({
113
+ type: live_1.OpType.CreateRegister,
114
+ id: id,
115
+ parentId: crdt.parentId,
116
+ parentKey: crdt.parentKey,
117
+ data: crdt.data,
118
+ });
119
+ break;
120
+ case live_1.CrdtType.List:
121
+ ops.push({
122
+ type: live_1.OpType.CreateList,
123
+ id: id,
124
+ parentId: crdt.parentId,
125
+ parentKey: crdt.parentKey,
126
+ });
127
+ break;
128
+ case live_1.CrdtType.Object:
129
+ ops.push({
130
+ type: live_1.OpType.CreateObject,
131
+ id: id,
132
+ parentId: crdt.parentId,
133
+ parentKey: crdt.parentKey,
134
+ data: crdt.data,
135
+ });
136
+ break;
137
+ case live_1.CrdtType.Map:
138
+ ops.push({
139
+ type: live_1.OpType.CreateMap,
140
+ id: id,
141
+ parentId: crdt.parentId,
142
+ parentKey: crdt.parentKey,
143
+ });
144
+ break;
145
+ }
146
+ }
147
+ });
148
+ return ops;
149
+ }
150
+ exports.getTreesDiffOperations = getTreesDiffOperations;
151
+ function mergeStorageUpdates(first, second) {
152
+ if (!first) {
153
+ return second;
154
+ }
155
+ if (second.type === "LiveObject") {
156
+ const updates = first.updates;
157
+ for (const [key, value] of Object.entries(second.updates)) {
158
+ updates[key] = value;
159
+ }
160
+ return Object.assign(Object.assign({}, second), { updates: updates });
161
+ }
162
+ else if (second.type === "LiveMap") {
163
+ const updates = first.updates;
164
+ for (const [key, value] of Object.entries(second.updates)) {
165
+ updates[key] = value;
166
+ }
167
+ return Object.assign(Object.assign({}, second), { updates: updates });
168
+ }
169
+ else if (second.type === "LiveList") {
170
+ const updates = first.updates;
171
+ return Object.assign(Object.assign({}, second), { updates: updates.concat(second.updates) });
172
+ }
173
+ return second;
174
+ }
175
+ exports.mergeStorageUpdates = mergeStorageUpdates;