@liveblocks/client 0.14.4 → 0.15.0-alpha.3
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 +5 -3
- package/lib/cjs/AbstractCrdt.js +1 -4
- package/lib/cjs/LiveList.d.ts +8 -2
- package/lib/cjs/LiveList.js +113 -9
- package/lib/cjs/LiveMap.d.ts +5 -1
- package/lib/cjs/LiveMap.js +35 -3
- package/lib/cjs/LiveObject.d.ts +5 -1
- package/lib/cjs/LiveObject.js +65 -10
- package/lib/cjs/LiveRegister.d.ts +5 -1
- package/lib/cjs/LiveRegister.js +6 -0
- package/lib/cjs/immutable.d.ts +9 -0
- package/lib/cjs/immutable.js +299 -0
- package/lib/cjs/index.d.ts +1 -0
- package/lib/cjs/index.js +8 -1
- package/lib/cjs/room.d.ts +1 -1
- package/lib/cjs/room.js +34 -54
- package/lib/cjs/types.d.ts +25 -4
- package/lib/cjs/utils.d.ts +6 -0
- package/lib/cjs/utils.js +76 -1
- package/lib/esm/AbstractCrdt.d.ts +5 -3
- package/lib/esm/AbstractCrdt.js +1 -4
- package/lib/esm/LiveList.d.ts +8 -2
- package/lib/esm/LiveList.js +113 -9
- package/lib/esm/LiveMap.d.ts +5 -1
- package/lib/esm/LiveMap.js +35 -3
- package/lib/esm/LiveObject.d.ts +5 -1
- package/lib/esm/LiveObject.js +65 -10
- package/lib/esm/LiveRegister.d.ts +5 -1
- package/lib/esm/LiveRegister.js +6 -0
- package/lib/esm/immutable.d.ts +9 -0
- package/lib/esm/immutable.js +290 -0
- package/lib/esm/index.d.ts +1 -0
- package/lib/esm/index.js +1 -0
- package/lib/esm/room.d.ts +1 -1
- package/lib/esm/room.js +35 -55
- package/lib/esm/types.d.ts +25 -4
- package/lib/esm/utils.d.ts +6 -0
- package/lib/esm/utils.js +73 -0
- package/package.json +3 -2
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { LiveList } from "./LiveList";
|
|
2
|
+
import { LiveMap } from "./LiveMap";
|
|
3
|
+
import { LiveObject } from "./LiveObject";
|
|
4
|
+
import { LiveRegister } from "./LiveRegister";
|
|
5
|
+
import { findNonSerializableValue } from "./utils";
|
|
6
|
+
export function liveObjectToJson(liveObject) {
|
|
7
|
+
const result = {};
|
|
8
|
+
const obj = liveObject.toObject();
|
|
9
|
+
for (const key in obj) {
|
|
10
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
function liveMapToJson(map) {
|
|
15
|
+
const result = {};
|
|
16
|
+
const obj = Object.fromEntries(map);
|
|
17
|
+
for (const key in obj) {
|
|
18
|
+
result[key] = liveNodeToJson(obj[key]);
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
function liveListToJson(value) {
|
|
23
|
+
return value.toArray().map(liveNodeToJson);
|
|
24
|
+
}
|
|
25
|
+
export function liveNodeToJson(value) {
|
|
26
|
+
if (value instanceof LiveObject) {
|
|
27
|
+
return liveObjectToJson(value);
|
|
28
|
+
}
|
|
29
|
+
else if (value instanceof LiveList) {
|
|
30
|
+
return liveListToJson(value);
|
|
31
|
+
}
|
|
32
|
+
else if (value instanceof LiveMap) {
|
|
33
|
+
return liveMapToJson(value);
|
|
34
|
+
}
|
|
35
|
+
else if (value instanceof LiveRegister) {
|
|
36
|
+
return value.data;
|
|
37
|
+
}
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
function isPlainObject(obj) {
|
|
41
|
+
return Object.prototype.toString.call(obj) === "[object Object]";
|
|
42
|
+
}
|
|
43
|
+
function anyToCrdt(obj) {
|
|
44
|
+
if (obj == null) {
|
|
45
|
+
return obj;
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(obj)) {
|
|
48
|
+
return new LiveList(obj.map(anyToCrdt));
|
|
49
|
+
}
|
|
50
|
+
if (isPlainObject(obj)) {
|
|
51
|
+
const init = {};
|
|
52
|
+
for (const key in obj) {
|
|
53
|
+
init[key] = anyToCrdt(obj[key]);
|
|
54
|
+
}
|
|
55
|
+
return new LiveObject(init);
|
|
56
|
+
}
|
|
57
|
+
return obj;
|
|
58
|
+
}
|
|
59
|
+
export function patchLiveList(liveList, prev, next) {
|
|
60
|
+
let i = 0;
|
|
61
|
+
let prevEnd = prev.length - 1;
|
|
62
|
+
let nextEnd = next.length - 1;
|
|
63
|
+
let prevNode = prev[0];
|
|
64
|
+
let nextNode = next[0];
|
|
65
|
+
/**
|
|
66
|
+
* For A,B,C => A,B,C,D
|
|
67
|
+
* i = 3, prevEnd = 2, nextEnd = 3
|
|
68
|
+
*
|
|
69
|
+
* For A,B,C => B,C
|
|
70
|
+
* i = 2, prevEnd = 2, nextEnd = 1
|
|
71
|
+
*
|
|
72
|
+
* For B,C => A,B,C
|
|
73
|
+
* i = 0, pre
|
|
74
|
+
*/
|
|
75
|
+
outer: {
|
|
76
|
+
while (prevNode === nextNode) {
|
|
77
|
+
++i;
|
|
78
|
+
if (i > prevEnd || i > nextEnd) {
|
|
79
|
+
break outer;
|
|
80
|
+
}
|
|
81
|
+
prevNode = prev[i];
|
|
82
|
+
nextNode = next[i];
|
|
83
|
+
}
|
|
84
|
+
prevNode = prev[prevEnd];
|
|
85
|
+
nextNode = next[nextEnd];
|
|
86
|
+
while (prevNode === nextNode) {
|
|
87
|
+
prevEnd--;
|
|
88
|
+
nextEnd--;
|
|
89
|
+
if (i > prevEnd || i > nextEnd) {
|
|
90
|
+
break outer;
|
|
91
|
+
}
|
|
92
|
+
prevNode = prev[prevEnd];
|
|
93
|
+
nextNode = next[nextEnd];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (i > prevEnd) {
|
|
97
|
+
if (i <= nextEnd) {
|
|
98
|
+
while (i <= nextEnd) {
|
|
99
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
100
|
+
i++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else if (i > nextEnd) {
|
|
105
|
+
let localI = i;
|
|
106
|
+
while (localI <= prevEnd) {
|
|
107
|
+
liveList.delete(i);
|
|
108
|
+
localI++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
while (i <= prevEnd && i <= nextEnd) {
|
|
113
|
+
prevNode = prev[i];
|
|
114
|
+
nextNode = next[i];
|
|
115
|
+
const liveListNode = liveList.get(i);
|
|
116
|
+
if (liveListNode instanceof LiveObject &&
|
|
117
|
+
isPlainObject(prevNode) &&
|
|
118
|
+
isPlainObject(nextNode)) {
|
|
119
|
+
patchLiveObject(liveListNode, prevNode, nextNode);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
liveList.delete(i);
|
|
123
|
+
liveList.insert(anyToCrdt(nextNode), i);
|
|
124
|
+
}
|
|
125
|
+
i++;
|
|
126
|
+
}
|
|
127
|
+
while (i <= nextEnd) {
|
|
128
|
+
liveList.insert(anyToCrdt(next[i]), i);
|
|
129
|
+
i++;
|
|
130
|
+
}
|
|
131
|
+
while (i <= prevEnd) {
|
|
132
|
+
liveList.delete(i);
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export function patchLiveObjectKey(liveObject, key, prev, next) {
|
|
138
|
+
if (process.env.NODE_ENV !== "production") {
|
|
139
|
+
const nonSerializableValue = findNonSerializableValue(next);
|
|
140
|
+
if (nonSerializableValue) {
|
|
141
|
+
console.error(`New state path: '${nonSerializableValue.path}' value: '${nonSerializableValue.value}' is not serializable.\nOnly serializable value can be synced with Liveblocks.`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const value = liveObject.get(key);
|
|
146
|
+
if (next === undefined) {
|
|
147
|
+
liveObject.delete(key);
|
|
148
|
+
}
|
|
149
|
+
else if (value === undefined) {
|
|
150
|
+
liveObject.set(key, anyToCrdt(next));
|
|
151
|
+
}
|
|
152
|
+
else if (prev === next) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
else if (value instanceof LiveList &&
|
|
156
|
+
Array.isArray(prev) &&
|
|
157
|
+
Array.isArray(next)) {
|
|
158
|
+
patchLiveList(value, prev, next);
|
|
159
|
+
}
|
|
160
|
+
else if (value instanceof LiveObject &&
|
|
161
|
+
isPlainObject(prev) &&
|
|
162
|
+
isPlainObject(next)) {
|
|
163
|
+
patchLiveObject(value, prev, next);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
liveObject.set(key, anyToCrdt(next));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export function patchLiveObject(root, prev, next) {
|
|
170
|
+
const updates = {};
|
|
171
|
+
for (const key in next) {
|
|
172
|
+
patchLiveObjectKey(root, key, prev[key], next[key]);
|
|
173
|
+
}
|
|
174
|
+
for (const key in prev) {
|
|
175
|
+
if (next[key] === undefined) {
|
|
176
|
+
root.delete(key);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (Object.keys(updates).length > 0) {
|
|
180
|
+
root.update(updates);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function getParentsPath(node) {
|
|
184
|
+
const path = [];
|
|
185
|
+
while (node._parentKey != null && node._parent != null) {
|
|
186
|
+
if (node._parent instanceof LiveList) {
|
|
187
|
+
path.push(node._parent._indexOfPosition(node._parentKey));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
path.push(node._parentKey);
|
|
191
|
+
}
|
|
192
|
+
node = node._parent;
|
|
193
|
+
}
|
|
194
|
+
return path;
|
|
195
|
+
}
|
|
196
|
+
export function patchImmutableObject(state, updates) {
|
|
197
|
+
return updates.reduce((state, update) => patchImmutableObjectWithUpdate(state, update), state);
|
|
198
|
+
}
|
|
199
|
+
function patchImmutableObjectWithUpdate(state, update) {
|
|
200
|
+
const path = getParentsPath(update.node);
|
|
201
|
+
return patchImmutableNode(state, path, update);
|
|
202
|
+
}
|
|
203
|
+
function patchImmutableNode(state, path, update) {
|
|
204
|
+
var _a, _b, _c, _d;
|
|
205
|
+
const pathItem = path.pop();
|
|
206
|
+
if (pathItem === undefined) {
|
|
207
|
+
switch (update.type) {
|
|
208
|
+
case "LiveObject": {
|
|
209
|
+
if (typeof state !== "object") {
|
|
210
|
+
throw new Error("Internal: received update on LiveObject but state was not an object");
|
|
211
|
+
}
|
|
212
|
+
let newState = Object.assign({}, state);
|
|
213
|
+
for (const key in update.updates) {
|
|
214
|
+
if (((_a = update.updates[key]) === null || _a === void 0 ? void 0 : _a.type) === "update") {
|
|
215
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
216
|
+
}
|
|
217
|
+
else if (((_b = update.updates[key]) === null || _b === void 0 ? void 0 : _b.type) === "delete") {
|
|
218
|
+
delete newState[key];
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return newState;
|
|
222
|
+
}
|
|
223
|
+
case "LiveList": {
|
|
224
|
+
if (Array.isArray(state) === false) {
|
|
225
|
+
throw new Error("Internal: received update on LiveList but state was not an array");
|
|
226
|
+
}
|
|
227
|
+
let newState = state.map((x) => x);
|
|
228
|
+
for (const listUpdate of update.updates) {
|
|
229
|
+
if (listUpdate.type === "insert") {
|
|
230
|
+
if (listUpdate.index === newState.length) {
|
|
231
|
+
newState.push(liveNodeToJson(listUpdate.item));
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
newState = [
|
|
235
|
+
...newState.slice(0, listUpdate.index),
|
|
236
|
+
liveNodeToJson(listUpdate.item),
|
|
237
|
+
...newState.slice(listUpdate.index),
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (listUpdate.type === "delete") {
|
|
242
|
+
newState.splice(listUpdate.index, 1);
|
|
243
|
+
}
|
|
244
|
+
else if (listUpdate.type === "move") {
|
|
245
|
+
if (listUpdate.previousIndex > listUpdate.index) {
|
|
246
|
+
newState = [
|
|
247
|
+
...newState.slice(0, listUpdate.index),
|
|
248
|
+
liveNodeToJson(listUpdate.item),
|
|
249
|
+
...newState.slice(listUpdate.index, listUpdate.previousIndex),
|
|
250
|
+
...newState.slice(listUpdate.previousIndex + 1),
|
|
251
|
+
];
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
newState = [
|
|
255
|
+
...newState.slice(0, listUpdate.previousIndex),
|
|
256
|
+
...newState.slice(listUpdate.previousIndex + 1, listUpdate.index + 1),
|
|
257
|
+
liveNodeToJson(listUpdate.item),
|
|
258
|
+
...newState.slice(listUpdate.index + 1),
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return newState;
|
|
264
|
+
}
|
|
265
|
+
case "LiveMap": {
|
|
266
|
+
if (typeof state !== "object") {
|
|
267
|
+
throw new Error("Internal: received update on LiveMap but state was not an object");
|
|
268
|
+
}
|
|
269
|
+
let newState = Object.assign({}, state);
|
|
270
|
+
for (const key in update.updates) {
|
|
271
|
+
if (((_c = update.updates[key]) === null || _c === void 0 ? void 0 : _c.type) === "update") {
|
|
272
|
+
newState[key] = liveNodeToJson(update.node.get(key));
|
|
273
|
+
}
|
|
274
|
+
else if (((_d = update.updates[key]) === null || _d === void 0 ? void 0 : _d.type) === "delete") {
|
|
275
|
+
delete newState[key];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return newState;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (Array.isArray(state)) {
|
|
283
|
+
const newArray = [...state];
|
|
284
|
+
newArray[pathItem] = patchImmutableNode(state[pathItem], path, update);
|
|
285
|
+
return newArray;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
return Object.assign(Object.assign({}, state), { [pathItem]: patchImmutableNode(state[pathItem], path, update) });
|
|
289
|
+
}
|
|
290
|
+
}
|
package/lib/esm/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { LiveMap } from "./LiveMap";
|
|
|
3
3
|
export { LiveList } from "./LiveList";
|
|
4
4
|
export type { Others, Presence, Room, Client, User, BroadcastOptions, } from "./types";
|
|
5
5
|
export { createClient } from "./client";
|
|
6
|
+
export { liveObjectToJson, liveNodeToJson, patchLiveList, patchImmutableObject, patchLiveObject, patchLiveObjectKey, } from "./immutable";
|
package/lib/esm/index.js
CHANGED
|
@@ -2,3 +2,4 @@ export { LiveObject } from "./LiveObject";
|
|
|
2
2
|
export { LiveMap } from "./LiveMap";
|
|
3
3
|
export { LiveList } from "./LiveList";
|
|
4
4
|
export { createClient } from "./client";
|
|
5
|
+
export { liveObjectToJson, liveNodeToJson, patchLiveList, patchImmutableObject, patchLiveObject, patchLiveObjectKey, } from "./immutable";
|
package/lib/esm/room.d.ts
CHANGED
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 { getTreesDiffOperations, isSameNodeOrChildOf, remove } from "./utils";
|
|
10
|
+
import { getTreesDiffOperations, isSameNodeOrChildOf, remove, mergeStorageUpdates, } from "./utils";
|
|
11
11
|
import { ClientMessageType, ServerMessageType, OpType, } from "./live";
|
|
12
12
|
import { LiveMap } from "./LiveMap";
|
|
13
13
|
import { LiveObject } from "./LiveObject";
|
|
@@ -189,22 +189,22 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
189
189
|
state.undoStack.push(historyItem);
|
|
190
190
|
}
|
|
191
191
|
}
|
|
192
|
-
function storageDispatch(ops, reverse,
|
|
192
|
+
function storageDispatch(ops, reverse, storageUpdates) {
|
|
193
193
|
if (state.isBatching) {
|
|
194
194
|
state.batch.ops.push(...ops);
|
|
195
|
-
|
|
196
|
-
state.batch.updates.
|
|
197
|
-
}
|
|
195
|
+
storageUpdates.forEach((value, key) => {
|
|
196
|
+
state.batch.updates.storageUpdates.set(key, mergeStorageUpdates(state.batch.updates.storageUpdates.get(key), value));
|
|
197
|
+
});
|
|
198
198
|
state.batch.reverseOps.push(...reverse);
|
|
199
199
|
}
|
|
200
200
|
else {
|
|
201
201
|
addToUndoStack(reverse);
|
|
202
202
|
state.redoStack = [];
|
|
203
203
|
dispatch(ops);
|
|
204
|
-
notify({
|
|
204
|
+
notify({ storageUpdates: storageUpdates });
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
|
-
function notify({
|
|
207
|
+
function notify({ storageUpdates = new Map(), presence = false, others = [], }) {
|
|
208
208
|
if (others.length > 0) {
|
|
209
209
|
state.others = makeOthers(state.users);
|
|
210
210
|
for (const event of others) {
|
|
@@ -218,28 +218,9 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
218
218
|
listener(state.me);
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
|
-
if (
|
|
221
|
+
if (storageUpdates.size > 0) {
|
|
222
222
|
for (const subscriber of state.listeners.storage) {
|
|
223
|
-
subscriber(Array.from(
|
|
224
|
-
if (m instanceof LiveObject) {
|
|
225
|
-
return {
|
|
226
|
-
type: "LiveObject",
|
|
227
|
-
node: m,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
else if (m instanceof LiveList) {
|
|
231
|
-
return {
|
|
232
|
-
type: "LiveList",
|
|
233
|
-
node: m,
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
return {
|
|
238
|
-
type: "LiveMap",
|
|
239
|
-
node: m,
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
}));
|
|
223
|
+
subscriber(Array.from(storageUpdates.values()));
|
|
243
224
|
}
|
|
244
225
|
}
|
|
245
226
|
}
|
|
@@ -262,7 +243,10 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
262
243
|
function apply(item, isLocal) {
|
|
263
244
|
const result = {
|
|
264
245
|
reverse: [],
|
|
265
|
-
updates: {
|
|
246
|
+
updates: {
|
|
247
|
+
storageUpdates: new Map(),
|
|
248
|
+
presence: false,
|
|
249
|
+
},
|
|
266
250
|
};
|
|
267
251
|
for (const op of item) {
|
|
268
252
|
if (op.type === "presence") {
|
|
@@ -292,7 +276,7 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
292
276
|
}
|
|
293
277
|
const applyOpResult = applyOp(op, isLocal);
|
|
294
278
|
if (applyOpResult.modified) {
|
|
295
|
-
result.updates.
|
|
279
|
+
result.updates.storageUpdates.set(applyOpResult.modified.node._id, mergeStorageUpdates(result.updates.storageUpdates.get(applyOpResult.modified.node._id), applyOpResult.modified));
|
|
296
280
|
result.reverse.unshift(...applyOpResult.reverse);
|
|
297
281
|
}
|
|
298
282
|
}
|
|
@@ -320,17 +304,12 @@ export function makeStateMachine(state, context, mockedEffects) {
|
|
|
320
304
|
}
|
|
321
305
|
if (item._parent instanceof LiveList) {
|
|
322
306
|
const previousKey = item._parentKey;
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
parentKey: previousKey,
|
|
330
|
-
},
|
|
331
|
-
],
|
|
332
|
-
modified: item._parent,
|
|
333
|
-
};
|
|
307
|
+
if (previousKey === op.parentKey) {
|
|
308
|
+
return { modified: false };
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
return item._parent._setChildKey(op.parentKey, item, previousKey);
|
|
312
|
+
}
|
|
334
313
|
}
|
|
335
314
|
return { modified: false };
|
|
336
315
|
}
|
|
@@ -556,7 +535,7 @@ See v0.13 release notes for more information.
|
|
|
556
535
|
subMessages.push(message);
|
|
557
536
|
}
|
|
558
537
|
const updates = {
|
|
559
|
-
|
|
538
|
+
storageUpdates: new Map(),
|
|
560
539
|
others: [],
|
|
561
540
|
};
|
|
562
541
|
for (const subMessage of subMessages) {
|
|
@@ -592,9 +571,9 @@ See v0.13 release notes for more information.
|
|
|
592
571
|
}
|
|
593
572
|
case ServerMessageType.UpdateStorage: {
|
|
594
573
|
const applyResult = apply(subMessage.ops, false);
|
|
595
|
-
|
|
596
|
-
updates.
|
|
597
|
-
}
|
|
574
|
+
applyResult.updates.storageUpdates.forEach((value, key) => {
|
|
575
|
+
updates.storageUpdates.set(key, mergeStorageUpdates(updates.storageUpdates.get(key), value));
|
|
576
|
+
});
|
|
598
577
|
break;
|
|
599
578
|
}
|
|
600
579
|
}
|
|
@@ -661,11 +640,6 @@ See v0.13 release notes for more information.
|
|
|
661
640
|
if (state.connection.state === "connecting") {
|
|
662
641
|
updateConnection(Object.assign(Object.assign({}, state.connection), { state: "open" }));
|
|
663
642
|
state.numberOfRetry = 0;
|
|
664
|
-
// Re-broadcast the user presence during a reconnect.
|
|
665
|
-
if (state.lastConnectionId !== undefined) {
|
|
666
|
-
state.buffer.presence = state.me;
|
|
667
|
-
tryFlushing();
|
|
668
|
-
}
|
|
669
643
|
state.lastConnectionId = state.connection.id;
|
|
670
644
|
if (state.root) {
|
|
671
645
|
state.buffer.messages.push({ type: ClientMessageType.FetchStorage });
|
|
@@ -893,8 +867,11 @@ See v0.13 release notes for more information.
|
|
|
893
867
|
if (state.batch.reverseOps.length > 0) {
|
|
894
868
|
addToUndoStack(state.batch.reverseOps);
|
|
895
869
|
}
|
|
896
|
-
|
|
897
|
-
|
|
870
|
+
if (state.batch.ops.length > 0) {
|
|
871
|
+
// Only clear the redo stack if something has changed during a batch
|
|
872
|
+
// Clear the redo stack because batch is always called from a local operation
|
|
873
|
+
state.redoStack = [];
|
|
874
|
+
}
|
|
898
875
|
if (state.batch.ops.length > 0) {
|
|
899
876
|
dispatch(state.batch.ops);
|
|
900
877
|
}
|
|
@@ -904,7 +881,7 @@ See v0.13 release notes for more information.
|
|
|
904
881
|
reverseOps: [],
|
|
905
882
|
updates: {
|
|
906
883
|
others: [],
|
|
907
|
-
|
|
884
|
+
storageUpdates: new Map(),
|
|
908
885
|
presence: false,
|
|
909
886
|
},
|
|
910
887
|
};
|
|
@@ -1015,7 +992,11 @@ export function defaultState(me, defaultStorageRoot) {
|
|
|
1015
992
|
isBatching: false,
|
|
1016
993
|
batch: {
|
|
1017
994
|
ops: [],
|
|
1018
|
-
updates: {
|
|
995
|
+
updates: {
|
|
996
|
+
storageUpdates: new Map(),
|
|
997
|
+
presence: false,
|
|
998
|
+
others: [],
|
|
999
|
+
},
|
|
1019
1000
|
reverseOps: [],
|
|
1020
1001
|
},
|
|
1021
1002
|
offlineOperations: new Map(),
|
|
@@ -1025,7 +1006,6 @@ export function createRoom(options, context) {
|
|
|
1025
1006
|
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
|
|
1026
1007
|
const machine = makeStateMachine(state, context);
|
|
1027
1008
|
const room = {
|
|
1028
|
-
id: context.room,
|
|
1029
1009
|
/////////////
|
|
1030
1010
|
// Core //
|
|
1031
1011
|
/////////////
|
package/lib/esm/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AbstractCrdt } from "./AbstractCrdt";
|
|
1
2
|
import type { LiveList } from "./LiveList";
|
|
2
3
|
import type { LiveMap } from "./LiveMap";
|
|
3
4
|
import type { LiveObject } from "./LiveObject";
|
|
@@ -16,17 +17,41 @@ export declare type RoomEventCallbackMap = {
|
|
|
16
17
|
error: ErrorCallback;
|
|
17
18
|
connection: ConnectionCallback;
|
|
18
19
|
};
|
|
20
|
+
export declare type UpdateDelta = {
|
|
21
|
+
type: "update";
|
|
22
|
+
} | {
|
|
23
|
+
type: "delete";
|
|
24
|
+
};
|
|
19
25
|
export declare type LiveMapUpdates<TKey extends string = string, TValue = any> = {
|
|
20
26
|
type: "LiveMap";
|
|
21
27
|
node: LiveMap<TKey, TValue>;
|
|
28
|
+
updates: Record<TKey, UpdateDelta>;
|
|
22
29
|
};
|
|
30
|
+
export declare type LiveObjectUpdateDelta<T> = Partial<{
|
|
31
|
+
[Property in keyof T]: UpdateDelta;
|
|
32
|
+
}>;
|
|
23
33
|
export declare type LiveObjectUpdates<TData = any> = {
|
|
24
34
|
type: "LiveObject";
|
|
25
35
|
node: LiveObject<TData>;
|
|
36
|
+
updates: LiveObjectUpdateDelta<TData>;
|
|
37
|
+
};
|
|
38
|
+
export declare type LiveListUpdateDelta = {
|
|
39
|
+
index: number;
|
|
40
|
+
item: AbstractCrdt;
|
|
41
|
+
type: "insert";
|
|
42
|
+
} | {
|
|
43
|
+
index: number;
|
|
44
|
+
type: "delete";
|
|
45
|
+
} | {
|
|
46
|
+
index: number;
|
|
47
|
+
previousIndex: number;
|
|
48
|
+
item: AbstractCrdt;
|
|
49
|
+
type: "move";
|
|
26
50
|
};
|
|
27
51
|
export declare type LiveListUpdates<TItem = any> = {
|
|
28
52
|
type: "LiveList";
|
|
29
53
|
node: LiveList<TItem>;
|
|
54
|
+
updates: LiveListUpdateDelta[];
|
|
30
55
|
};
|
|
31
56
|
export declare type BroadcastOptions = {
|
|
32
57
|
/**
|
|
@@ -167,10 +192,6 @@ export declare type OthersEvent<T extends Presence = Presence> = {
|
|
|
167
192
|
type: "reset";
|
|
168
193
|
};
|
|
169
194
|
export declare type Room = {
|
|
170
|
-
/**
|
|
171
|
-
* The id of the room.
|
|
172
|
-
*/
|
|
173
|
-
readonly id: string;
|
|
174
195
|
getConnectionState(): ConnectionState;
|
|
175
196
|
subscribe: {
|
|
176
197
|
/**
|
package/lib/esm/utils.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractCrdt, Doc } from "./AbstractCrdt";
|
|
2
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;
|
|
@@ -7,3 +8,8 @@ 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;
|
|
9
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;
|
|
12
|
+
export declare function findNonSerializableValue(value: any, path?: string): {
|
|
13
|
+
path: string;
|
|
14
|
+
value: any;
|
|
15
|
+
} | false;
|
package/lib/esm/utils.js
CHANGED
|
@@ -138,3 +138,76 @@ export function getTreesDiffOperations(currentItems, newItems) {
|
|
|
138
138
|
});
|
|
139
139
|
return ops;
|
|
140
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
|
+
}
|
|
165
|
+
function isPlain(value) {
|
|
166
|
+
const type = typeof value;
|
|
167
|
+
return (type === "undefined" ||
|
|
168
|
+
value === null ||
|
|
169
|
+
type === "string" ||
|
|
170
|
+
type === "boolean" ||
|
|
171
|
+
type === "number" ||
|
|
172
|
+
Array.isArray(value) ||
|
|
173
|
+
isPlainObject(value));
|
|
174
|
+
}
|
|
175
|
+
function isPlainObject(value) {
|
|
176
|
+
if (typeof value !== "object" || value === null)
|
|
177
|
+
return false;
|
|
178
|
+
let proto = Object.getPrototypeOf(value);
|
|
179
|
+
if (proto === null)
|
|
180
|
+
return true;
|
|
181
|
+
let baseProto = proto;
|
|
182
|
+
while (Object.getPrototypeOf(baseProto) !== null) {
|
|
183
|
+
baseProto = Object.getPrototypeOf(baseProto);
|
|
184
|
+
}
|
|
185
|
+
return proto === baseProto;
|
|
186
|
+
}
|
|
187
|
+
export function findNonSerializableValue(value, path = "") {
|
|
188
|
+
if (!isPlain) {
|
|
189
|
+
return {
|
|
190
|
+
path: path || "root",
|
|
191
|
+
value: value,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
if (typeof value !== "object" || value === null) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
198
|
+
const nestedPath = path ? path + "." + key : key;
|
|
199
|
+
if (!isPlain(nestedValue)) {
|
|
200
|
+
return {
|
|
201
|
+
path: nestedPath,
|
|
202
|
+
value: nestedValue,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
if (typeof nestedValue === "object") {
|
|
206
|
+
const nonSerializableNestedValue = findNonSerializableValue(nestedValue, nestedPath);
|
|
207
|
+
if (nonSerializableNestedValue) {
|
|
208
|
+
return nonSerializableNestedValue;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liveblocks/client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0-alpha.3",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./lib/cjs/index.js",
|
|
6
6
|
"module": "./lib/esm/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@types/ws": "^8.2.2",
|
|
34
34
|
"babel-jest": "^26.6.3",
|
|
35
35
|
"jest": "^26.6.3",
|
|
36
|
+
"jest-each": "^27.5.1",
|
|
36
37
|
"node-fetch": "2.6.7",
|
|
37
38
|
"typescript": "^4.4.0",
|
|
38
39
|
"ws": "^8.5.0"
|
|
@@ -42,4 +43,4 @@
|
|
|
42
43
|
"url": "https://github.com/liveblocks/liveblocks.git",
|
|
43
44
|
"directory": "packages/liveblocks"
|
|
44
45
|
}
|
|
45
|
-
}
|
|
46
|
+
}
|