@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.
- package/lib/cjs/AbstractCrdt.d.ts +13 -7
- package/lib/cjs/AbstractCrdt.js +2 -5
- package/lib/cjs/LiveList.d.ts +17 -6
- package/lib/cjs/LiveList.js +140 -15
- package/lib/cjs/LiveMap.d.ts +12 -4
- package/lib/cjs/LiveMap.js +57 -7
- package/lib/cjs/LiveObject.d.ts +22 -8
- package/lib/cjs/LiveObject.js +109 -24
- package/lib/cjs/LiveRegister.d.ts +13 -5
- package/lib/cjs/LiveRegister.js +23 -4
- package/lib/cjs/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/cjs/{immutable/index.js → immutable.js} +77 -19
- package/lib/cjs/index.d.ts +2 -1
- package/lib/cjs/index.js +8 -1
- package/lib/cjs/live.d.ts +7 -0
- package/lib/cjs/room.d.ts +11 -3
- package/lib/cjs/room.js +150 -76
- package/lib/cjs/types.d.ts +33 -1
- package/lib/cjs/utils.d.ts +4 -1
- package/lib/cjs/utils.js +101 -1
- package/lib/esm/AbstractCrdt.d.ts +13 -7
- package/lib/esm/AbstractCrdt.js +2 -5
- package/lib/esm/LiveList.d.ts +17 -6
- package/lib/esm/LiveList.js +141 -16
- package/lib/esm/LiveMap.d.ts +12 -4
- package/lib/esm/LiveMap.js +57 -7
- package/lib/esm/LiveObject.d.ts +22 -8
- package/lib/esm/LiveObject.js +109 -24
- package/lib/esm/LiveRegister.d.ts +13 -5
- package/lib/esm/LiveRegister.js +24 -5
- package/lib/esm/{immutable/index.d.ts → immutable.d.ts} +5 -3
- package/lib/esm/{immutable/index.js → immutable.js} +75 -19
- package/lib/esm/index.d.ts +2 -1
- package/lib/esm/index.js +1 -0
- package/lib/esm/live.d.ts +7 -0
- package/lib/esm/room.d.ts +11 -3
- package/lib/esm/room.js +151 -77
- package/lib/esm/types.d.ts +33 -1
- package/lib/esm/utils.d.ts +4 -1
- package/lib/esm/utils.js +99 -1
- 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
|
|
133
|
-
|
|
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
|
|
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,
|
|
218
|
+
function storageDispatch(ops, reverse, storageUpdates) {
|
|
194
219
|
if (state.isBatching) {
|
|
195
220
|
state.batch.ops.push(...ops);
|
|
196
|
-
|
|
197
|
-
state.batch.updates.
|
|
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({
|
|
230
|
+
notify({ storageUpdates: storageUpdates });
|
|
206
231
|
}
|
|
207
232
|
}
|
|
208
|
-
function notify({
|
|
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 (
|
|
247
|
+
if (storageUpdates.size > 0) {
|
|
223
248
|
for (const subscriber of state.listeners.storage) {
|
|
224
|
-
subscriber(Array.from(
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
581
|
-
updates.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
683
|
-
if (state.
|
|
709
|
+
function applyAndSendOfflineOps() {
|
|
710
|
+
if (state.offlineOperations.size === 0) {
|
|
684
711
|
return;
|
|
685
712
|
}
|
|
686
|
-
|
|
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
|
-
|
|
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
|
-
|
|
847
|
-
|
|
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
|
-
|
|
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: {
|
|
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,
|
package/lib/cjs/types.d.ts
CHANGED
|
@@ -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}.
|
package/lib/cjs/utils.d.ts
CHANGED
|
@@ -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;
|