@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/esm/room.js CHANGED
@@ -7,7 +7,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { isSameNodeOrChildOf, remove } from "./utils";
10
+ import { getTreesDiffOperations, isSameNodeOrChildOf, remove, mergeStorageUpdates, } from "./utils";
11
11
  import auth, { parseToken } from "./authentication";
12
12
  import { ClientMessageType, ServerMessageType, OpType, } from "./live";
13
13
  import { LiveMap } from "./LiveMap";
@@ -36,6 +36,9 @@ function makeOthers(presenceMap) {
36
36
  get count() {
37
37
  return array.length;
38
38
  },
39
+ [Symbol.iterator]() {
40
+ return array[Symbol.iterator]();
41
+ },
39
42
  map(callback) {
40
43
  return array.map(callback);
41
44
  },
@@ -107,18 +110,23 @@ export function makeStateMachine(state, context, mockedEffects) {
107
110
  };
108
111
  return genericSubscribe(cb);
109
112
  }
110
- function createRootFromMessage(message) {
111
- state.root = load(message.items);
113
+ function createOrUpdateRootFromMessage(message) {
114
+ if (message.items.length === 0) {
115
+ throw new Error("Internal error: cannot load storage without items");
116
+ }
117
+ if (state.root) {
118
+ updateRoot(message.items);
119
+ }
120
+ else {
121
+ state.root = load(message.items);
122
+ }
112
123
  for (const key in state.defaultStorageRoot) {
113
124
  if (state.root.get(key) == null) {
114
125
  state.root.set(key, state.defaultStorageRoot[key]);
115
126
  }
116
127
  }
117
128
  }
118
- function load(items) {
119
- if (items.length === 0) {
120
- throw new Error("Internal error: cannot load storage without items");
121
- }
129
+ function buildRootAndParentToChildren(items) {
122
130
  const parentToChildren = new Map();
123
131
  let root = null;
124
132
  for (const tuple of items) {
@@ -139,6 +147,23 @@ export function makeStateMachine(state, context, mockedEffects) {
139
147
  if (root == null) {
140
148
  throw new Error("Root can't be null");
141
149
  }
150
+ return [root, parentToChildren];
151
+ }
152
+ function updateRoot(items) {
153
+ if (!state.root) {
154
+ return;
155
+ }
156
+ const currentItems = new Map();
157
+ state.items.forEach((liveCrdt, id) => {
158
+ currentItems.set(id, liveCrdt._toSerializedCrdt());
159
+ });
160
+ // Get operations that represent the diff between 2 states.
161
+ const ops = getTreesDiffOperations(currentItems, new Map(items));
162
+ const result = apply(ops, false);
163
+ notify(result.updates);
164
+ }
165
+ function load(items) {
166
+ const [root, parentToChildren] = buildRootAndParentToChildren(items);
142
167
  return LiveObject._deserialize(root, parentToChildren, {
143
168
  addItem,
144
169
  deleteItem,
@@ -168,22 +193,22 @@ export function makeStateMachine(state, context, mockedEffects) {
168
193
  state.undoStack.push(historyItem);
169
194
  }
170
195
  }
171
- function storageDispatch(ops, reverse, modified) {
196
+ function storageDispatch(ops, reverse, storageUpdates) {
172
197
  if (state.isBatching) {
173
198
  state.batch.ops.push(...ops);
174
- for (const item of modified) {
175
- state.batch.updates.nodes.add(item);
176
- }
199
+ storageUpdates.forEach((value, key) => {
200
+ state.batch.updates.storageUpdates.set(key, mergeStorageUpdates(state.batch.updates.storageUpdates.get(key), value));
201
+ });
177
202
  state.batch.reverseOps.push(...reverse);
178
203
  }
179
204
  else {
180
205
  addToUndoStack(reverse);
181
206
  state.redoStack = [];
182
207
  dispatch(ops);
183
- notify({ nodes: new Set(modified) });
208
+ notify({ storageUpdates: storageUpdates });
184
209
  }
185
210
  }
186
- function notify({ nodes = new Set(), presence = false, others = [], }) {
211
+ function notify({ storageUpdates = new Map(), presence = false, others = [], }) {
187
212
  if (others.length > 0) {
188
213
  state.others = makeOthers(state.users);
189
214
  for (const event of others) {
@@ -197,28 +222,9 @@ export function makeStateMachine(state, context, mockedEffects) {
197
222
  listener(state.me);
198
223
  }
199
224
  }
200
- if (nodes.size > 0) {
225
+ if (storageUpdates.size > 0) {
201
226
  for (const subscriber of state.listeners.storage) {
202
- subscriber(Array.from(nodes).map((m) => {
203
- if (m instanceof LiveObject) {
204
- return {
205
- type: "LiveObject",
206
- node: m,
207
- };
208
- }
209
- else if (m instanceof LiveList) {
210
- return {
211
- type: "LiveList",
212
- node: m,
213
- };
214
- }
215
- else {
216
- return {
217
- type: "LiveMap",
218
- node: m,
219
- };
220
- }
221
- }));
227
+ subscriber(Array.from(storageUpdates.values()));
222
228
  }
223
229
  }
224
230
  }
@@ -227,7 +233,10 @@ export function makeStateMachine(state, context, mockedEffects) {
227
233
  state.connection.state === "connecting") {
228
234
  return state.connection.id;
229
235
  }
230
- throw new Error("Internal. Tried to get connection id but connection is not open");
236
+ else if (state.lastConnectionId !== null) {
237
+ return state.lastConnectionId;
238
+ }
239
+ throw new Error("Internal. Tried to get connection id but connection was never open");
231
240
  }
232
241
  function generateId() {
233
242
  return `${getConnectionId()}:${state.clock++}`;
@@ -235,10 +244,13 @@ export function makeStateMachine(state, context, mockedEffects) {
235
244
  function generateOpId() {
236
245
  return `${getConnectionId()}:${state.opClock++}`;
237
246
  }
238
- function apply(item) {
247
+ function apply(item, isLocal) {
239
248
  const result = {
240
249
  reverse: [],
241
- updates: { nodes: new Set(), presence: false },
250
+ updates: {
251
+ storageUpdates: new Map(),
252
+ presence: false,
253
+ },
242
254
  };
243
255
  for (const op of item) {
244
256
  if (op.type === "presence") {
@@ -262,16 +274,23 @@ export function makeStateMachine(state, context, mockedEffects) {
262
274
  result.updates.presence = true;
263
275
  }
264
276
  else {
265
- const applyOpResult = applyOp(op);
277
+ // Ops applied after undo/redo don't have an opId.
278
+ if (isLocal && !op.opId) {
279
+ op.opId = generateOpId();
280
+ }
281
+ const applyOpResult = applyOp(op, isLocal);
266
282
  if (applyOpResult.modified) {
267
- result.updates.nodes.add(applyOpResult.modified);
283
+ result.updates.storageUpdates.set(applyOpResult.modified.node._id, mergeStorageUpdates(result.updates.storageUpdates.get(applyOpResult.modified.node._id), applyOpResult.modified));
268
284
  result.reverse.unshift(...applyOpResult.reverse);
269
285
  }
270
286
  }
271
287
  }
272
288
  return result;
273
289
  }
274
- function applyOp(op) {
290
+ function applyOp(op, isLocal) {
291
+ if (op.opId) {
292
+ state.offlineOperations.delete(op.opId);
293
+ }
275
294
  switch (op.type) {
276
295
  case OpType.DeleteObjectKey:
277
296
  case OpType.UpdateObject:
@@ -280,7 +299,7 @@ export function makeStateMachine(state, context, mockedEffects) {
280
299
  if (item == null) {
281
300
  return { modified: false };
282
301
  }
283
- return item._apply(op);
302
+ return item._apply(op, isLocal);
284
303
  }
285
304
  case OpType.SetParentKey: {
286
305
  const item = state.items.get(op.id);
@@ -289,17 +308,12 @@ export function makeStateMachine(state, context, mockedEffects) {
289
308
  }
290
309
  if (item._parent instanceof LiveList) {
291
310
  const previousKey = item._parentKey;
292
- item._parent._setChildKey(op.parentKey, item);
293
- return {
294
- reverse: [
295
- {
296
- type: OpType.SetParentKey,
297
- id: item._id,
298
- parentKey: previousKey,
299
- },
300
- ],
301
- modified: item._parent,
302
- };
311
+ if (previousKey === op.parentKey) {
312
+ return { modified: false };
313
+ }
314
+ else {
315
+ return item._parent._setChildKey(op.parentKey, item, previousKey);
316
+ }
303
317
  }
304
318
  return { modified: false };
305
319
  }
@@ -308,28 +322,28 @@ export function makeStateMachine(state, context, mockedEffects) {
308
322
  if (parent == null || getItem(op.id) != null) {
309
323
  return { modified: false };
310
324
  }
311
- return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data));
325
+ return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal);
312
326
  }
313
327
  case OpType.CreateList: {
314
328
  const parent = state.items.get(op.parentId);
315
329
  if (parent == null || getItem(op.id) != null) {
316
330
  return { modified: false };
317
331
  }
318
- return parent._attachChild(op.id, op.parentKey, new LiveList());
332
+ return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal);
319
333
  }
320
334
  case OpType.CreateRegister: {
321
335
  const parent = state.items.get(op.parentId);
322
336
  if (parent == null || getItem(op.id) != null) {
323
337
  return { modified: false };
324
338
  }
325
- return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data));
339
+ return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal);
326
340
  }
327
341
  case OpType.CreateMap: {
328
342
  const parent = state.items.get(op.parentId);
329
343
  if (parent == null || getItem(op.id) != null) {
330
344
  return { modified: false };
331
345
  }
332
- return parent._attachChild(op.id, op.parentKey, new LiveMap());
346
+ return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal);
333
347
  }
334
348
  }
335
349
  return { modified: false };
@@ -421,7 +435,9 @@ See v0.13 release notes for more information.
421
435
  state.socket = socket;
422
436
  }
423
437
  function authenticationFailure(error) {
424
- console.error(error);
438
+ if (process.env.NODE_ENV !== "production") {
439
+ console.error("Call to authentication endpoint failed", error);
440
+ }
425
441
  updateConnection({ state: "unavailable" });
426
442
  state.numberOfRetry++;
427
443
  state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
@@ -520,7 +536,7 @@ See v0.13 release notes for more information.
520
536
  subMessages.push(message);
521
537
  }
522
538
  const updates = {
523
- nodes: new Set(),
539
+ storageUpdates: new Map(),
524
540
  others: [],
525
541
  };
526
542
  for (const subMessage of subMessages) {
@@ -549,15 +565,16 @@ See v0.13 release notes for more information.
549
565
  break;
550
566
  }
551
567
  case ServerMessageType.InitialStorageState: {
552
- createRootFromMessage(subMessage);
568
+ createOrUpdateRootFromMessage(subMessage);
569
+ applyAndSendOfflineOps();
553
570
  _getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
554
571
  break;
555
572
  }
556
573
  case ServerMessageType.UpdateStorage: {
557
- const applyResult = apply(subMessage.ops);
558
- for (const node of applyResult.updates.nodes) {
559
- updates.nodes.add(node);
560
- }
574
+ const applyResult = apply(subMessage.ops, false);
575
+ applyResult.updates.storageUpdates.forEach((value, key) => {
576
+ updates.storageUpdates.set(key, mergeStorageUpdates(updates.storageUpdates.get(key), value));
577
+ });
561
578
  break;
562
579
  }
563
580
  }
@@ -587,14 +604,20 @@ See v0.13 release notes for more information.
587
604
  updateConnection({ state: "failed" });
588
605
  const error = new LiveblocksError(event.reason, event.code);
589
606
  for (const listener of state.listeners.error) {
590
- console.error(`Liveblocks WebSocket connection closed. Reason: ${error.message} (code: ${error.code})`);
607
+ if (process.env.NODE_ENV !== "production") {
608
+ console.error(`Connection to Liveblocks websocket server closed. Reason: ${error.message} (code: ${error.code})`);
609
+ }
591
610
  listener(error);
592
611
  }
593
612
  }
594
613
  else if (event.wasClean === false) {
595
- updateConnection({ state: "unavailable" });
596
614
  state.numberOfRetry++;
597
- state.timeoutHandles.reconnect = effects.scheduleReconnect(getRetryDelay());
615
+ const delay = getRetryDelay();
616
+ if (process.env.NODE_ENV !== "production") {
617
+ console.warn(`Connection to Liveblocks websocket server closed (code: ${event.code}). Retrying in ${delay}ms.`);
618
+ }
619
+ updateConnection({ state: "unavailable" });
620
+ state.timeoutHandles.reconnect = effects.scheduleReconnect(delay);
598
621
  }
599
622
  else {
600
623
  updateConnection({ state: "closed" });
@@ -618,6 +641,10 @@ See v0.13 release notes for more information.
618
641
  if (state.connection.state === "connecting") {
619
642
  updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
620
643
  state.numberOfRetry = 0;
644
+ state.lastConnectionId = state.connection.id;
645
+ if (state.root) {
646
+ state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
647
+ }
621
648
  tryFlushing();
622
649
  }
623
650
  else {
@@ -657,11 +684,29 @@ See v0.13 release notes for more information.
657
684
  clearInterval(state.intervalHandles.heartbeat);
658
685
  connect();
659
686
  }
660
- function tryFlushing() {
661
- if (state.socket == null) {
687
+ function applyAndSendOfflineOps() {
688
+ if (state.offlineOperations.size === 0) {
662
689
  return;
663
690
  }
664
- if (state.socket.readyState !== WebSocket.OPEN) {
691
+ const messages = [];
692
+ const ops = Array.from(state.offlineOperations.values());
693
+ const result = apply(ops, true);
694
+ messages.push({
695
+ type: ClientMessageType.UpdateStorage,
696
+ ops: ops,
697
+ });
698
+ notify(result.updates);
699
+ effects.send(messages);
700
+ }
701
+ function tryFlushing() {
702
+ const storageOps = state.buffer.storageOperations;
703
+ if (storageOps.length > 0) {
704
+ storageOps.forEach((op) => {
705
+ state.offlineOperations.set(op.opId, op);
706
+ });
707
+ }
708
+ if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) {
709
+ state.buffer.storageOperations = [];
665
710
  return;
666
711
  }
667
712
  const now = Date.now();
@@ -736,8 +781,10 @@ See v0.13 release notes for more information.
736
781
  function getOthers() {
737
782
  return state.others;
738
783
  }
739
- function broadcastEvent(event) {
740
- if (state.socket == null) {
784
+ function broadcastEvent(event, options = {
785
+ shouldQueueEventIfNotReady: false,
786
+ }) {
787
+ if (state.socket == null && options.shouldQueueEventIfNotReady == false) {
741
788
  return;
742
789
  }
743
790
  state.buffer.messages.push({
@@ -779,7 +826,7 @@ See v0.13 release notes for more information.
779
826
  return;
780
827
  }
781
828
  state.isHistoryPaused = false;
782
- const result = apply(historyItem);
829
+ const result = apply(historyItem, true);
783
830
  notify(result.updates);
784
831
  state.redoStack.push(result.reverse);
785
832
  for (const op of historyItem) {
@@ -798,7 +845,7 @@ See v0.13 release notes for more information.
798
845
  return;
799
846
  }
800
847
  state.isHistoryPaused = false;
801
- const result = apply(historyItem);
848
+ const result = apply(historyItem, true);
802
849
  notify(result.updates);
803
850
  state.undoStack.push(result.reverse);
804
851
  for (const op of historyItem) {
@@ -821,8 +868,11 @@ See v0.13 release notes for more information.
821
868
  if (state.batch.reverseOps.length > 0) {
822
869
  addToUndoStack(state.batch.reverseOps);
823
870
  }
824
- // Clear the redo stack because batch is always called from a local operation
825
- state.redoStack = [];
871
+ if (state.batch.ops.length > 0) {
872
+ // Only clear the redo stack if something has changed during a batch
873
+ // Clear the redo stack because batch is always called from a local operation
874
+ state.redoStack = [];
875
+ }
826
876
  if (state.batch.ops.length > 0) {
827
877
  dispatch(state.batch.ops);
828
878
  }
@@ -832,7 +882,7 @@ See v0.13 release notes for more information.
832
882
  reverseOps: [],
833
883
  updates: {
834
884
  others: [],
835
- nodes: new Set(),
885
+ storageUpdates: new Map(),
836
886
  presence: false,
837
887
  },
838
888
  };
@@ -850,6 +900,16 @@ See v0.13 release notes for more information.
850
900
  }
851
901
  state.pausedHistory = [];
852
902
  }
903
+ function simulateSocketClose() {
904
+ if (state.socket) {
905
+ state.socket.close();
906
+ }
907
+ }
908
+ function simulateSendCloseEvent(event) {
909
+ if (state.socket) {
910
+ onClose(event);
911
+ }
912
+ }
853
913
  return {
854
914
  // Internal
855
915
  onOpen,
@@ -858,6 +918,9 @@ See v0.13 release notes for more information.
858
918
  authenticationSuccess,
859
919
  heartbeat,
860
920
  onNavigatorOnline,
921
+ // Internal dev tools
922
+ simulateSocketClose,
923
+ simulateSendCloseEvent,
861
924
  // onWakeUp,
862
925
  onVisibilityChange,
863
926
  getUndoStack: () => state.undoStack,
@@ -889,6 +952,7 @@ See v0.13 release notes for more information.
889
952
  export function defaultState(me, defaultStorageRoot) {
890
953
  return {
891
954
  connection: { state: "closed" },
955
+ lastConnectionId: null,
892
956
  socket: null,
893
957
  listeners: {
894
958
  event: [],
@@ -930,9 +994,14 @@ export function defaultState(me, defaultStorageRoot) {
930
994
  isBatching: false,
931
995
  batch: {
932
996
  ops: [],
933
- updates: { nodes: new Set(), presence: false, others: [] },
997
+ updates: {
998
+ storageUpdates: new Map(),
999
+ presence: false,
1000
+ others: [],
1001
+ },
934
1002
  reverseOps: [],
935
1003
  },
1004
+ offlineOperations: new Map(),
936
1005
  };
937
1006
  }
938
1007
  export function createRoom(name, options) {
@@ -978,6 +1047,11 @@ export function createRoom(name, options) {
978
1047
  pause: machine.pauseHistory,
979
1048
  resume: machine.resumeHistory,
980
1049
  },
1050
+ // @ts-ignore
1051
+ internalDevTools: {
1052
+ closeWebsocket: machine.simulateSocketClose,
1053
+ sendCloseEvent: machine.simulateSendCloseEvent,
1054
+ },
981
1055
  };
982
1056
  return {
983
1057
  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/esm/utils.js CHANGED
@@ -1,4 +1,4 @@
1
- import { CrdtType, } from "./live";
1
+ import { CrdtType, OpType, } from "./live";
2
2
  import { LiveList } from "./LiveList";
3
3
  import { LiveMap } from "./LiveMap";
4
4
  import { LiveObject } from "./LiveObject";
@@ -64,3 +64,101 @@ export function selfOrRegister(obj) {
64
64
  return new LiveRegister(obj);
65
65
  }
66
66
  }
67
+ export function getTreesDiffOperations(currentItems, newItems) {
68
+ const ops = [];
69
+ currentItems.forEach((_, id) => {
70
+ if (!newItems.get(id)) {
71
+ // Delete crdt
72
+ ops.push({
73
+ type: OpType.DeleteCrdt,
74
+ id: id,
75
+ });
76
+ }
77
+ });
78
+ newItems.forEach((crdt, id) => {
79
+ const currentCrdt = currentItems.get(id);
80
+ if (currentCrdt) {
81
+ if (crdt.type === CrdtType.Object) {
82
+ if (JSON.stringify(crdt.data) !==
83
+ JSON.stringify(currentCrdt.data)) {
84
+ ops.push({
85
+ type: OpType.UpdateObject,
86
+ id: id,
87
+ data: crdt.data,
88
+ });
89
+ }
90
+ }
91
+ if (crdt.parentKey !== currentCrdt.parentKey) {
92
+ ops.push({
93
+ type: OpType.SetParentKey,
94
+ id: id,
95
+ parentKey: crdt.parentKey,
96
+ });
97
+ }
98
+ }
99
+ else {
100
+ // new Crdt
101
+ switch (crdt.type) {
102
+ case CrdtType.Register:
103
+ ops.push({
104
+ type: OpType.CreateRegister,
105
+ id: id,
106
+ parentId: crdt.parentId,
107
+ parentKey: crdt.parentKey,
108
+ data: crdt.data,
109
+ });
110
+ break;
111
+ case CrdtType.List:
112
+ ops.push({
113
+ type: OpType.CreateList,
114
+ id: id,
115
+ parentId: crdt.parentId,
116
+ parentKey: crdt.parentKey,
117
+ });
118
+ break;
119
+ case CrdtType.Object:
120
+ ops.push({
121
+ type: OpType.CreateObject,
122
+ id: id,
123
+ parentId: crdt.parentId,
124
+ parentKey: crdt.parentKey,
125
+ data: crdt.data,
126
+ });
127
+ break;
128
+ case CrdtType.Map:
129
+ ops.push({
130
+ type: OpType.CreateMap,
131
+ id: id,
132
+ parentId: crdt.parentId,
133
+ parentKey: crdt.parentKey,
134
+ });
135
+ break;
136
+ }
137
+ }
138
+ });
139
+ return ops;
140
+ }
141
+ export function mergeStorageUpdates(first, second) {
142
+ if (!first) {
143
+ return second;
144
+ }
145
+ if (second.type === "LiveObject") {
146
+ const updates = first.updates;
147
+ for (const [key, value] of Object.entries(second.updates)) {
148
+ updates[key] = value;
149
+ }
150
+ return Object.assign(Object.assign({}, second), { updates: updates });
151
+ }
152
+ else if (second.type === "LiveMap") {
153
+ const updates = first.updates;
154
+ for (const [key, value] of Object.entries(second.updates)) {
155
+ updates[key] = value;
156
+ }
157
+ return Object.assign(Object.assign({}, second), { updates: updates });
158
+ }
159
+ else if (second.type === "LiveList") {
160
+ const updates = first.updates;
161
+ return Object.assign(Object.assign({}, second), { updates: updates.concat(second.updates) });
162
+ }
163
+ return second;
164
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@liveblocks/client",
3
- "version": "0.13.2",
3
+ "version": "0.15.0-alpha.1",
4
4
  "description": "",
5
5
  "main": "./lib/cjs/index.js",
6
6
  "module": "./lib/esm/index.js",