@liveblocks/client 0.12.3 → 0.13.2

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 (48) hide show
  1. package/README.md +34 -6
  2. package/lib/cjs/AbstractCrdt.d.ts +61 -0
  3. package/lib/cjs/AbstractCrdt.js +98 -0
  4. package/lib/cjs/LiveList.d.ts +133 -0
  5. package/lib/cjs/LiveList.js +374 -0
  6. package/lib/cjs/LiveMap.d.ts +83 -0
  7. package/lib/cjs/LiveMap.js +272 -0
  8. package/lib/cjs/LiveObject.d.ts +66 -0
  9. package/lib/cjs/LiveObject.js +368 -0
  10. package/lib/cjs/LiveRegister.d.ts +21 -0
  11. package/lib/cjs/LiveRegister.js +69 -0
  12. package/lib/cjs/immutable/index.d.ts +7 -0
  13. package/lib/cjs/immutable/index.js +214 -0
  14. package/lib/cjs/index.d.ts +3 -1
  15. package/lib/cjs/index.js +7 -5
  16. package/lib/cjs/room.d.ts +50 -9
  17. package/lib/cjs/room.js +477 -85
  18. package/lib/cjs/types.d.ts +220 -40
  19. package/lib/cjs/utils.d.ts +7 -0
  20. package/lib/cjs/utils.js +64 -1
  21. package/lib/esm/AbstractCrdt.d.ts +61 -0
  22. package/lib/esm/AbstractCrdt.js +94 -0
  23. package/lib/esm/LiveList.d.ts +133 -0
  24. package/lib/esm/LiveList.js +370 -0
  25. package/lib/esm/LiveMap.d.ts +83 -0
  26. package/lib/esm/LiveMap.js +268 -0
  27. package/lib/esm/LiveObject.d.ts +66 -0
  28. package/lib/esm/LiveObject.js +364 -0
  29. package/lib/esm/LiveRegister.d.ts +21 -0
  30. package/lib/esm/LiveRegister.js +65 -0
  31. package/lib/esm/immutable/index.d.ts +7 -0
  32. package/lib/esm/immutable/index.js +207 -0
  33. package/lib/esm/index.d.ts +3 -1
  34. package/lib/esm/index.js +3 -1
  35. package/lib/esm/room.d.ts +50 -9
  36. package/lib/esm/room.js +479 -84
  37. package/lib/esm/types.d.ts +220 -40
  38. package/lib/esm/utils.d.ts +7 -0
  39. package/lib/esm/utils.js +58 -0
  40. package/package.json +3 -3
  41. package/lib/cjs/doc.d.ts +0 -347
  42. package/lib/cjs/doc.js +0 -1349
  43. package/lib/cjs/storage.d.ts +0 -21
  44. package/lib/cjs/storage.js +0 -68
  45. package/lib/esm/doc.d.ts +0 -347
  46. package/lib/esm/doc.js +0 -1342
  47. package/lib/esm/storage.d.ts +0 -21
  48. package/lib/esm/storage.js +0 -65
package/lib/cjs/room.js CHANGED
@@ -27,15 +27,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
27
27
  step((generator = generator.apply(thisArg, _arguments || [])).next());
28
28
  });
29
29
  };
30
- var __importDefault = (this && this.__importDefault) || function (mod) {
31
- return (mod && mod.__esModule) ? mod : { "default": mod };
32
- };
33
30
  Object.defineProperty(exports, "__esModule", { value: true });
34
31
  exports.createRoom = exports.defaultState = exports.makeStateMachine = void 0;
35
32
  const utils_1 = require("./utils");
36
33
  const authentication_1 = __importStar(require("./authentication"));
37
34
  const live_1 = require("./live");
38
- const storage_1 = __importDefault(require("./storage"));
35
+ const LiveMap_1 = require("./LiveMap");
36
+ const LiveObject_1 = require("./LiveObject");
37
+ const LiveList_1 = require("./LiveList");
38
+ const AbstractCrdt_1 = require("./AbstractCrdt");
39
+ const LiveRegister_1 = require("./LiveRegister");
39
40
  const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];
40
41
  const HEARTBEAT_INTERVAL = 30000;
41
42
  // const WAKE_UP_CHECK_INTERVAL = 2000;
@@ -107,13 +108,275 @@ function makeStateMachine(state, context, mockedEffects) {
107
108
  return setTimeout(connect, delay);
108
109
  },
109
110
  };
110
- function subscribe(type, listener) {
111
- if (!isValidRoomEventType(type)) {
112
- throw new Error(`"${type}" is not a valid event name`);
111
+ function genericSubscribe(callback) {
112
+ state.listeners.storage.push(callback);
113
+ return () => (0, utils_1.remove)(state.listeners.storage, callback);
114
+ }
115
+ function crdtSubscribe(crdt, innerCallback, options) {
116
+ const cb = (updates) => {
117
+ const relatedUpdates = [];
118
+ for (const update of updates) {
119
+ if ((options === null || options === void 0 ? void 0 : options.isDeep) && (0, utils_1.isSameNodeOrChildOf)(update.node, crdt)) {
120
+ relatedUpdates.push(update);
121
+ }
122
+ else if (update.node._id === crdt._id) {
123
+ innerCallback(update.node);
124
+ }
125
+ }
126
+ if ((options === null || options === void 0 ? void 0 : options.isDeep) && relatedUpdates.length > 0) {
127
+ innerCallback(relatedUpdates);
128
+ }
129
+ };
130
+ return genericSubscribe(cb);
131
+ }
132
+ function createRootFromMessage(message) {
133
+ state.root = load(message.items);
134
+ for (const key in state.defaultStorageRoot) {
135
+ if (state.root.get(key) == null) {
136
+ state.root.set(key, state.defaultStorageRoot[key]);
137
+ }
138
+ }
139
+ }
140
+ function load(items) {
141
+ if (items.length === 0) {
142
+ throw new Error("Internal error: cannot load storage without items");
143
+ }
144
+ const parentToChildren = new Map();
145
+ let root = null;
146
+ for (const tuple of items) {
147
+ const parentId = tuple[1].parentId;
148
+ if (parentId == null) {
149
+ root = tuple;
150
+ }
151
+ else {
152
+ const children = parentToChildren.get(parentId);
153
+ if (children != null) {
154
+ children.push(tuple);
155
+ }
156
+ else {
157
+ parentToChildren.set(parentId, [tuple]);
158
+ }
159
+ }
160
+ }
161
+ if (root == null) {
162
+ throw new Error("Root can't be null");
163
+ }
164
+ return LiveObject_1.LiveObject._deserialize(root, parentToChildren, {
165
+ addItem,
166
+ deleteItem,
167
+ generateId,
168
+ generateOpId,
169
+ dispatch: storageDispatch,
170
+ });
171
+ }
172
+ function addItem(id, item) {
173
+ state.items.set(id, item);
174
+ }
175
+ function deleteItem(id) {
176
+ state.items.delete(id);
177
+ }
178
+ function getItem(id) {
179
+ return state.items.get(id);
180
+ }
181
+ function addToUndoStack(historyItem) {
182
+ // If undo stack is too large, we remove the older item
183
+ if (state.undoStack.length >= 50) {
184
+ state.undoStack.shift();
185
+ }
186
+ if (state.isHistoryPaused) {
187
+ state.pausedHistory.unshift(...historyItem);
188
+ }
189
+ else {
190
+ state.undoStack.push(historyItem);
191
+ }
192
+ }
193
+ function storageDispatch(ops, reverse, modified) {
194
+ if (state.isBatching) {
195
+ state.batch.ops.push(...ops);
196
+ for (const item of modified) {
197
+ state.batch.updates.nodes.add(item);
198
+ }
199
+ state.batch.reverseOps.push(...reverse);
200
+ }
201
+ else {
202
+ addToUndoStack(reverse);
203
+ state.redoStack = [];
204
+ dispatch(ops);
205
+ notify({ nodes: new Set(modified) });
206
+ }
207
+ }
208
+ function notify({ nodes = new Set(), presence = false, others = [], }) {
209
+ if (others.length > 0) {
210
+ state.others = makeOthers(state.users);
211
+ for (const event of others) {
212
+ for (const listener of state.listeners["others"]) {
213
+ listener(state.others, event);
214
+ }
215
+ }
216
+ }
217
+ if (presence) {
218
+ for (const listener of state.listeners["my-presence"]) {
219
+ listener(state.me);
220
+ }
221
+ }
222
+ if (nodes.size > 0) {
223
+ 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
+ }));
244
+ }
245
+ }
246
+ }
247
+ function getConnectionId() {
248
+ if (state.connection.state === "open" ||
249
+ state.connection.state === "connecting") {
250
+ return state.connection.id;
251
+ }
252
+ throw new Error("Internal. Tried to get connection id but connection is not open");
253
+ }
254
+ function generateId() {
255
+ return `${getConnectionId()}:${state.clock++}`;
256
+ }
257
+ function generateOpId() {
258
+ return `${getConnectionId()}:${state.opClock++}`;
259
+ }
260
+ function apply(item) {
261
+ const result = {
262
+ reverse: [],
263
+ updates: { nodes: new Set(), presence: false },
264
+ };
265
+ for (const op of item) {
266
+ if (op.type === "presence") {
267
+ const reverse = {
268
+ type: "presence",
269
+ data: {},
270
+ };
271
+ for (const key in op.data) {
272
+ reverse.data[key] = state.me[key];
273
+ }
274
+ state.me = Object.assign(Object.assign({}, state.me), op.data);
275
+ if (state.buffer.presence == null) {
276
+ state.buffer.presence = op.data;
277
+ }
278
+ else {
279
+ for (const key in op.data) {
280
+ state.buffer.presence[key] = op.data;
281
+ }
282
+ }
283
+ result.reverse.unshift(reverse);
284
+ result.updates.presence = true;
285
+ }
286
+ else {
287
+ const applyOpResult = applyOp(op);
288
+ if (applyOpResult.modified) {
289
+ result.updates.nodes.add(applyOpResult.modified);
290
+ result.reverse.unshift(...applyOpResult.reverse);
291
+ }
292
+ }
293
+ }
294
+ return result;
295
+ }
296
+ function applyOp(op) {
297
+ switch (op.type) {
298
+ case live_1.OpType.DeleteObjectKey:
299
+ case live_1.OpType.UpdateObject:
300
+ case live_1.OpType.DeleteCrdt: {
301
+ const item = state.items.get(op.id);
302
+ if (item == null) {
303
+ return { modified: false };
304
+ }
305
+ return item._apply(op);
306
+ }
307
+ case live_1.OpType.SetParentKey: {
308
+ const item = state.items.get(op.id);
309
+ if (item == null) {
310
+ return { modified: false };
311
+ }
312
+ if (item._parent instanceof LiveList_1.LiveList) {
313
+ 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
+ };
325
+ }
326
+ return { modified: false };
327
+ }
328
+ case live_1.OpType.CreateObject: {
329
+ const parent = state.items.get(op.parentId);
330
+ if (parent == null || getItem(op.id) != null) {
331
+ return { modified: false };
332
+ }
333
+ return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data));
334
+ }
335
+ case live_1.OpType.CreateList: {
336
+ const parent = state.items.get(op.parentId);
337
+ if (parent == null || getItem(op.id) != null) {
338
+ return { modified: false };
339
+ }
340
+ return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList());
341
+ }
342
+ case live_1.OpType.CreateRegister: {
343
+ const parent = state.items.get(op.parentId);
344
+ if (parent == null || getItem(op.id) != null) {
345
+ return { modified: false };
346
+ }
347
+ return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data));
348
+ }
349
+ case live_1.OpType.CreateMap: {
350
+ const parent = state.items.get(op.parentId);
351
+ if (parent == null || getItem(op.id) != null) {
352
+ return { modified: false };
353
+ }
354
+ return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap());
355
+ }
113
356
  }
114
- state.listeners[type].push(listener);
357
+ return { modified: false };
358
+ }
359
+ function subscribe(firstParam, listener, options) {
360
+ if (firstParam instanceof AbstractCrdt_1.AbstractCrdt) {
361
+ return crdtSubscribe(firstParam, listener, options);
362
+ }
363
+ else if (typeof firstParam === "function") {
364
+ return genericSubscribe(firstParam);
365
+ }
366
+ else if (!isValidRoomEventType(firstParam)) {
367
+ throw new Error(`"${firstParam}" is not a valid event name`);
368
+ }
369
+ state.listeners[firstParam].push(listener);
370
+ return () => {
371
+ const callbacks = state.listeners[firstParam];
372
+ (0, utils_1.remove)(callbacks, listener);
373
+ };
115
374
  }
116
375
  function unsubscribe(event, callback) {
376
+ console.warn(`unsubscribe is depreacted and will be removed in a future version.
377
+ use the callback returned by subscribe instead.
378
+ See v0.13 release notes for more information.
379
+ `);
117
380
  if (!isValidRoomEventType(event)) {
118
381
  throw new Error(`"${event}" is not a valid event name`);
119
382
  }
@@ -145,20 +408,28 @@ function makeStateMachine(state, context, mockedEffects) {
145
408
  updateConnection({ state: "authenticating" });
146
409
  effects.authenticate();
147
410
  }
148
- function updatePresence(overrides) {
149
- const newPresence = Object.assign(Object.assign({}, state.me), overrides);
150
- if (state.flushData.presence == null) {
151
- state.flushData.presence = overrides;
411
+ function updatePresence(overrides, options) {
412
+ const oldValues = {};
413
+ if (state.buffer.presence == null) {
414
+ state.buffer.presence = {};
152
415
  }
153
- else {
154
- for (const key in overrides) {
155
- state.flushData.presence[key] = overrides[key];
416
+ for (const key in overrides) {
417
+ state.buffer.presence[key] = overrides[key];
418
+ oldValues[key] = state.me[key];
419
+ }
420
+ state.me = Object.assign(Object.assign({}, state.me), overrides);
421
+ if (state.isBatching) {
422
+ if (options === null || options === void 0 ? void 0 : options.addToHistory) {
423
+ state.batch.reverseOps.push({ type: "presence", data: oldValues });
156
424
  }
425
+ state.batch.updates.presence = true;
157
426
  }
158
- state.me = newPresence;
159
- tryFlushing();
160
- for (const listener of state.listeners["my-presence"]) {
161
- listener(state.me);
427
+ else {
428
+ tryFlushing();
429
+ if (options === null || options === void 0 ? void 0 : options.addToHistory) {
430
+ addToUndoStack([{ type: "presence", data: oldValues }]);
431
+ }
432
+ notify({ presence: true });
162
433
  }
163
434
  }
164
435
  function authenticationSuccess(token, socket) {
@@ -199,25 +470,20 @@ function makeStateMachine(state, context, mockedEffects) {
199
470
  presence: Object.assign(Object.assign({}, user.presence), message.data),
200
471
  };
201
472
  }
202
- updateUsers({
473
+ return {
203
474
  type: "update",
204
475
  updates: message.data,
205
476
  user: state.users[message.actor],
206
- });
207
- }
208
- function updateUsers(event) {
209
- state.others = makeOthers(state.users);
210
- for (const listener of state.listeners["others"]) {
211
- listener(state.others, event);
212
- }
477
+ };
213
478
  }
214
479
  function onUserLeftMessage(message) {
215
480
  const userLeftMessage = message;
216
481
  const user = state.users[userLeftMessage.actor];
217
482
  if (user) {
218
483
  delete state.users[userLeftMessage.actor];
219
- updateUsers({ type: "leave", user });
484
+ return { type: "leave", user };
220
485
  }
486
+ return null;
221
487
  }
222
488
  function onRoomStateMessage(message) {
223
489
  const newUsers = {};
@@ -231,7 +497,7 @@ function makeStateMachine(state, context, mockedEffects) {
231
497
  };
232
498
  }
233
499
  state.users = newUsers;
234
- updateUsers({ type: "reset" });
500
+ return { type: "reset" };
235
501
  }
236
502
  function onNavigatorOnline() {
237
503
  if (state.connection.state === "unavailable") {
@@ -250,17 +516,17 @@ function makeStateMachine(state, context, mockedEffects) {
250
516
  info: message.info,
251
517
  id: message.id,
252
518
  };
253
- updateUsers({ type: "enter", user: state.users[message.actor] });
254
519
  if (state.me) {
255
520
  // Send current presence to new user
256
521
  // TODO: Consider storing it on the backend
257
- state.flushData.messages.push({
522
+ state.buffer.messages.push({
258
523
  type: live_1.ClientMessageType.UpdatePresence,
259
524
  data: state.me,
260
525
  targetActor: message.actor,
261
526
  });
262
527
  tryFlushing();
263
528
  }
529
+ return { type: "enter", user: state.users[message.actor] };
264
530
  }
265
531
  function onMessage(event) {
266
532
  if (event.data === "pong") {
@@ -268,29 +534,57 @@ function makeStateMachine(state, context, mockedEffects) {
268
534
  return;
269
535
  }
270
536
  const message = JSON.parse(event.data);
271
- switch (message.type) {
272
- case live_1.ServerMessageType.UserJoined: {
273
- onUserJoinedMessage(message);
274
- break;
275
- }
276
- case live_1.ServerMessageType.UpdatePresence: {
277
- onUpdatePresenceMessage(message);
278
- break;
279
- }
280
- case live_1.ServerMessageType.Event: {
281
- onEvent(message);
282
- break;
283
- }
284
- case live_1.ServerMessageType.UserLeft: {
285
- onUserLeftMessage(message);
286
- break;
287
- }
288
- case live_1.ServerMessageType.RoomState: {
289
- onRoomStateMessage(message);
290
- break;
537
+ let subMessages = [];
538
+ if (Array.isArray(message)) {
539
+ subMessages = message;
540
+ }
541
+ else {
542
+ subMessages.push(message);
543
+ }
544
+ const updates = {
545
+ nodes: new Set(),
546
+ others: [],
547
+ };
548
+ for (const subMessage of subMessages) {
549
+ switch (subMessage.type) {
550
+ case live_1.ServerMessageType.UserJoined: {
551
+ updates.others.push(onUserJoinedMessage(message));
552
+ break;
553
+ }
554
+ case live_1.ServerMessageType.UpdatePresence: {
555
+ updates.others.push(onUpdatePresenceMessage(subMessage));
556
+ break;
557
+ }
558
+ case live_1.ServerMessageType.Event: {
559
+ onEvent(subMessage);
560
+ break;
561
+ }
562
+ case live_1.ServerMessageType.UserLeft: {
563
+ const event = onUserLeftMessage(subMessage);
564
+ if (event) {
565
+ updates.others.push(event);
566
+ }
567
+ break;
568
+ }
569
+ case live_1.ServerMessageType.RoomState: {
570
+ updates.others.push(onRoomStateMessage(subMessage));
571
+ break;
572
+ }
573
+ case live_1.ServerMessageType.InitialStorageState: {
574
+ createRootFromMessage(subMessage);
575
+ _getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver();
576
+ break;
577
+ }
578
+ 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
+ }
583
+ break;
584
+ }
291
585
  }
292
586
  }
293
- storage.onMessage(message);
587
+ notify(updates);
294
588
  }
295
589
  // function onWakeUp() {
296
590
  // // Sometimes, the browser can put the webpage on pause (computer is on sleep mode for example)
@@ -310,11 +604,12 @@ function makeStateMachine(state, context, mockedEffects) {
310
604
  }
311
605
  clearTimeout(state.timeoutHandles.reconnect);
312
606
  state.users = {};
313
- updateUsers({ type: "reset" });
607
+ notify({ others: [{ type: "reset" }] });
314
608
  if (event.code >= 4000 && event.code <= 4100) {
315
609
  updateConnection({ state: "failed" });
316
610
  const error = new LiveblocksError(event.reason, event.code);
317
611
  for (const listener of state.listeners.error) {
612
+ console.error(`Liveblocks WebSocket connection closed. Reason: ${error.message} (code: ${error.code})`);
318
613
  listener(error);
319
614
  }
320
615
  }
@@ -399,7 +694,7 @@ function makeStateMachine(state, context, mockedEffects) {
399
694
  return;
400
695
  }
401
696
  effects.send(messages);
402
- state.flushData = {
697
+ state.buffer = {
403
698
  messages: [],
404
699
  storageOperations: [],
405
700
  presence: null,
@@ -415,19 +710,19 @@ function makeStateMachine(state, context, mockedEffects) {
415
710
  }
416
711
  function flushDataToMessages(state) {
417
712
  const messages = [];
418
- if (state.flushData.presence) {
713
+ if (state.buffer.presence) {
419
714
  messages.push({
420
715
  type: live_1.ClientMessageType.UpdatePresence,
421
- data: state.flushData.presence,
716
+ data: state.buffer.presence,
422
717
  });
423
718
  }
424
- for (const event of state.flushData.messages) {
719
+ for (const event of state.buffer.messages) {
425
720
  messages.push(event);
426
721
  }
427
- if (state.flushData.storageOperations.length > 0) {
722
+ if (state.buffer.storageOperations.length > 0) {
428
723
  messages.push({
429
724
  type: live_1.ClientMessageType.UpdateStorage,
430
- ops: state.flushData.storageOperations,
725
+ ops: state.buffer.storageOperations,
431
726
  });
432
727
  }
433
728
  return messages;
@@ -449,7 +744,7 @@ function makeStateMachine(state, context, mockedEffects) {
449
744
  clearTimeout(state.timeoutHandles.pongTimeout);
450
745
  clearInterval(state.intervalHandles.heartbeat);
451
746
  state.users = {};
452
- updateUsers({ type: "reset" });
747
+ notify({ others: [{ type: "reset" }] });
453
748
  clearListeners();
454
749
  }
455
750
  function clearListeners() {
@@ -467,44 +762,115 @@ function makeStateMachine(state, context, mockedEffects) {
467
762
  if (state.socket == null) {
468
763
  return;
469
764
  }
470
- state.flushData.messages.push({
765
+ state.buffer.messages.push({
471
766
  type: live_1.ClientMessageType.ClientEvent,
472
767
  event,
473
768
  });
474
769
  tryFlushing();
475
770
  }
476
771
  function dispatch(ops) {
477
- state.flushData.storageOperations.push(...ops);
772
+ state.buffer.storageOperations.push(...ops);
478
773
  tryFlushing();
479
774
  }
480
- const storage = new storage_1.default({
481
- fetchStorage: () => {
482
- state.flushData.messages.push({ type: live_1.ClientMessageType.FetchStorage });
483
- tryFlushing();
484
- },
485
- dispatch,
486
- getConnectionId: () => {
487
- const me = getSelf();
488
- if (me) {
489
- return me.connectionId;
490
- }
491
- throw new Error("Unexpected");
492
- },
493
- defaultRoot: state.defaultStorageRoot,
494
- });
775
+ let _getInitialStatePromise = null;
776
+ let _getInitialStateResolver = null;
495
777
  function getStorage() {
496
778
  return __awaiter(this, void 0, void 0, function* () {
497
- const doc = yield storage.getDocument();
779
+ if (state.root) {
780
+ return {
781
+ root: state.root,
782
+ };
783
+ }
784
+ if (_getInitialStatePromise == null) {
785
+ state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage });
786
+ tryFlushing();
787
+ _getInitialStatePromise = new Promise((resolve) => (_getInitialStateResolver = resolve));
788
+ }
789
+ yield _getInitialStatePromise;
498
790
  return {
499
- root: doc.root,
791
+ root: state.root,
500
792
  };
501
793
  });
502
794
  }
503
795
  function undo() {
504
- storage.undo();
796
+ if (state.isBatching) {
797
+ throw new Error("undo is not allowed during a batch");
798
+ }
799
+ const historyItem = state.undoStack.pop();
800
+ if (historyItem == null) {
801
+ return;
802
+ }
803
+ state.isHistoryPaused = false;
804
+ const result = apply(historyItem);
805
+ notify(result.updates);
806
+ state.redoStack.push(result.reverse);
807
+ for (const op of historyItem) {
808
+ if (op.type !== "presence") {
809
+ state.buffer.storageOperations.push(op);
810
+ }
811
+ }
812
+ tryFlushing();
505
813
  }
506
814
  function redo() {
507
- storage.redo();
815
+ if (state.isBatching) {
816
+ throw new Error("redo is not allowed during a batch");
817
+ }
818
+ const historyItem = state.redoStack.pop();
819
+ if (historyItem == null) {
820
+ return;
821
+ }
822
+ state.isHistoryPaused = false;
823
+ const result = apply(historyItem);
824
+ notify(result.updates);
825
+ state.undoStack.push(result.reverse);
826
+ for (const op of historyItem) {
827
+ if (op.type !== "presence") {
828
+ state.buffer.storageOperations.push(op);
829
+ }
830
+ }
831
+ tryFlushing();
832
+ }
833
+ function batch(callback) {
834
+ if (state.isBatching) {
835
+ throw new Error("batch should not be called during a batch");
836
+ }
837
+ state.isBatching = true;
838
+ try {
839
+ callback();
840
+ }
841
+ finally {
842
+ state.isBatching = false;
843
+ if (state.batch.reverseOps.length > 0) {
844
+ addToUndoStack(state.batch.reverseOps);
845
+ }
846
+ // Clear the redo stack because batch is always called from a local operation
847
+ state.redoStack = [];
848
+ if (state.batch.ops.length > 0) {
849
+ dispatch(state.batch.ops);
850
+ }
851
+ notify(state.batch.updates);
852
+ state.batch = {
853
+ ops: [],
854
+ reverseOps: [],
855
+ updates: {
856
+ others: [],
857
+ nodes: new Set(),
858
+ presence: false,
859
+ },
860
+ };
861
+ tryFlushing();
862
+ }
863
+ }
864
+ function pauseHistory() {
865
+ state.pausedHistory = [];
866
+ state.isHistoryPaused = true;
867
+ }
868
+ function resumeHistory() {
869
+ state.isHistoryPaused = false;
870
+ if (state.pausedHistory.length > 0) {
871
+ addToUndoStack(state.pausedHistory);
872
+ }
873
+ state.pausedHistory = [];
508
874
  }
509
875
  return {
510
876
  // Internal
@@ -516,6 +882,8 @@ function makeStateMachine(state, context, mockedEffects) {
516
882
  onNavigatorOnline,
517
883
  // onWakeUp,
518
884
  onVisibilityChange,
885
+ getUndoStack: () => state.undoStack,
886
+ getItemsCount: () => state.items.size,
519
887
  // Core
520
888
  connect,
521
889
  disconnect,
@@ -524,8 +892,11 @@ function makeStateMachine(state, context, mockedEffects) {
524
892
  // Presence
525
893
  updatePresence,
526
894
  broadcastEvent,
895
+ batch,
527
896
  undo,
528
897
  redo,
898
+ pauseHistory,
899
+ resumeHistory,
529
900
  getStorage,
530
901
  selectors: {
531
902
  // Core
@@ -548,6 +919,7 @@ function defaultState(me, defaultStorageRoot) {
548
919
  "my-presence": [],
549
920
  error: [],
550
921
  connection: [],
922
+ storage: [],
551
923
  },
552
924
  numberOfRetry: 0,
553
925
  lastFlushTime: 0,
@@ -556,7 +928,7 @@ function defaultState(me, defaultStorageRoot) {
556
928
  reconnect: 0,
557
929
  pongTimeout: 0,
558
930
  },
559
- flushData: {
931
+ buffer: {
560
932
  presence: me == null ? {} : me,
561
933
  messages: [],
562
934
  storageOperations: [],
@@ -569,12 +941,27 @@ function defaultState(me, defaultStorageRoot) {
569
941
  others: makeOthers({}),
570
942
  defaultStorageRoot,
571
943
  idFactory: null,
944
+ // Storage
945
+ clock: 0,
946
+ opClock: 0,
947
+ items: new Map(),
948
+ root: undefined,
949
+ undoStack: [],
950
+ redoStack: [],
951
+ isHistoryPaused: false,
952
+ pausedHistory: [],
953
+ isBatching: false,
954
+ batch: {
955
+ ops: [],
956
+ updates: { nodes: new Set(), presence: false, others: [] },
957
+ reverseOps: [],
958
+ },
572
959
  };
573
960
  }
574
961
  exports.defaultState = defaultState;
575
962
  function createRoom(name, options) {
576
963
  const throttleDelay = options.throttle || 100;
577
- const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v4";
964
+ const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v5";
578
965
  let authEndpoint;
579
966
  if (options.authEndpoint) {
580
967
  authEndpoint = options.authEndpoint;
@@ -608,8 +995,13 @@ function createRoom(name, options) {
608
995
  getOthers: machine.selectors.getOthers,
609
996
  broadcastEvent: machine.broadcastEvent,
610
997
  getStorage: machine.getStorage,
611
- undo: machine.undo,
612
- redo: machine.redo,
998
+ batch: machine.batch,
999
+ history: {
1000
+ undo: machine.undo,
1001
+ redo: machine.redo,
1002
+ pause: machine.pauseHistory,
1003
+ resume: machine.resumeHistory,
1004
+ },
613
1005
  };
614
1006
  return {
615
1007
  connect: machine.connect,