@liveblocks/client
Advanced tools
Comparing version 0.13.2 to 0.14.0-beta.1
@@ -1,2 +0,2 @@ | ||
import { Op } from "./live"; | ||
import { Op, SerializedCrdt } from "./live"; | ||
export declare type ApplyResult = { | ||
@@ -36,3 +36,3 @@ reverse: Op[]; | ||
*/ | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -49,3 +49,3 @@ * INTERNAL | ||
*/ | ||
abstract _attachChild(id: string, key: string, crdt: AbstractCrdt): ApplyResult; | ||
abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -62,3 +62,7 @@ * INTERNAL | ||
*/ | ||
abstract _serialize(parentId: string, parentKey: string): Op[]; | ||
abstract _serialize(parentId: string, parentKey: string, doc?: Doc): Op[]; | ||
/** | ||
* INTERNAL | ||
*/ | ||
abstract _toSerializedCrdt(): SerializedCrdt; | ||
} |
@@ -51,3 +51,3 @@ "use strict"; | ||
*/ | ||
_apply(op) { | ||
_apply(op, isLocal) { | ||
switch (op.type) { | ||
@@ -57,3 +57,3 @@ case live_1.OpType.DeleteCrdt: { | ||
const parent = this._parent; | ||
const reverse = this._serialize(this._parent._id, this._parentKey); | ||
const reverse = this._serialize(this._parent._id, this._parentKey, __classPrivateFieldGet(this, _AbstractCrdt_doc, "f")); | ||
this._parent._detachChild(this); | ||
@@ -60,0 +60,0 @@ return { modified: parent, reverse }; |
@@ -125,2 +125,3 @@ import { Presence } from "./types"; | ||
export declare type CreateObjectOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -135,2 +136,3 @@ type: OpType.CreateObject; | ||
export declare type CreateListOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -142,2 +144,3 @@ type: OpType.CreateList; | ||
export declare type CreateMapOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -149,2 +152,3 @@ type: OpType.CreateMap; | ||
export declare type CreateRegisterOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -157,2 +161,3 @@ type: OpType.CreateRegister; | ||
export declare type DeleteCrdtOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -162,2 +167,3 @@ type: OpType.DeleteCrdt; | ||
export declare type SetParentKeyOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -168,2 +174,3 @@ type: OpType.SetParentKey; | ||
export declare type DeleteObjectKeyOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -170,0 +177,0 @@ type: OpType.DeleteObjectKey; |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { SerializedList, SerializedCrdtWithId, Op } from "./live"; | ||
import { SerializedList, SerializedCrdtWithId, Op, SerializedCrdt } from "./live"; | ||
/** | ||
@@ -16,3 +16,3 @@ * The LiveList class represents an ordered collection of items that is synchorinized across clients. | ||
*/ | ||
_serialize(parentId?: string, parentKey?: string): Op[]; | ||
_serialize(parentId?: string, parentKey?: string, doc?: Doc): Op[]; | ||
/** | ||
@@ -29,3 +29,3 @@ * INTERNAL | ||
*/ | ||
_attachChild(id: string, key: string, child: AbstractCrdt): ApplyResult; | ||
_attachChild(id: string, key: string, child: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -42,4 +42,8 @@ * INTERNAL | ||
*/ | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
/** | ||
* Returns the number of elements. | ||
@@ -46,0 +50,0 @@ */ |
@@ -57,3 +57,3 @@ "use strict"; | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null) { | ||
@@ -68,2 +68,3 @@ throw new Error("Cannot serialize item is not attached"); | ||
id: this._id, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
type: live_1.OpType.CreateList, | ||
@@ -75,3 +76,3 @@ parentId, | ||
for (const [value, key] of __classPrivateFieldGet(this, _LiveList_items, "f")) { | ||
ops.push(...value._serialize(this._id, key)); | ||
ops.push(...value._serialize(this._id, key, doc)); | ||
} | ||
@@ -101,3 +102,3 @@ return ops; | ||
*/ | ||
_attachChild(id, key, child) { | ||
_attachChild(id, key, child, isLocal) { | ||
var _a; | ||
@@ -110,7 +111,20 @@ if (this._doc == null) { | ||
const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key); | ||
// Assign a temporary position until we get the fix from the backend | ||
let newKey = key; | ||
// If there is a conflict | ||
if (index !== -1) { | ||
__classPrivateFieldGet(this, _LiveList_items, "f")[index][1] = (0, position_1.makePosition)(key, (_a = __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1]) === null || _a === void 0 ? void 0 : _a[1]); | ||
if (isLocal) { | ||
// If change is local => assign a temporary position to newly attached child | ||
let before = __classPrivateFieldGet(this, _LiveList_items, "f")[index] ? __classPrivateFieldGet(this, _LiveList_items, "f")[index][1] : undefined; | ||
let after = __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1] | ||
? __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1][1] | ||
: undefined; | ||
newKey = (0, position_1.makePosition)(before, after); | ||
child._setParentLink(this, newKey); | ||
} | ||
else { | ||
// If change is remote => assign a temporary position to existing child until we get the fix from the backend | ||
__classPrivateFieldGet(this, _LiveList_items, "f")[index][1] = (0, position_1.makePosition)(key, (_a = __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1]) === null || _a === void 0 ? void 0 : _a[1]); | ||
} | ||
} | ||
__classPrivateFieldGet(this, _LiveList_items, "f").push([child, key]); | ||
__classPrivateFieldGet(this, _LiveList_items, "f").push([child, newKey]); | ||
__classPrivateFieldGet(this, _LiveList_items, "f").sort((itemA, itemB) => (0, position_1.compare)(itemA[1], itemB[1])); | ||
@@ -149,6 +163,17 @@ return { reverse: [{ type: live_1.OpType.DeleteCrdt, id }], modified: this }; | ||
*/ | ||
_apply(op) { | ||
return super._apply(op); | ||
_apply(op, isLocal) { | ||
return super._apply(op, isLocal); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: live_1.CrdtType.List, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
}; | ||
} | ||
/** | ||
* Returns the number of elements. | ||
@@ -185,3 +210,3 @@ */ | ||
value._attach(id, this._doc); | ||
this._doc.dispatch(value._serialize(this._id, position), [{ type: live_1.OpType.DeleteCrdt, id }], [this]); | ||
this._doc.dispatch(value._serialize(this._id, position, this._doc), [{ type: live_1.OpType.DeleteCrdt, id }], [this]); | ||
} | ||
@@ -232,2 +257,3 @@ } | ||
id: item[0]._id, | ||
opId: this._doc.generateOpId(), | ||
parentKey: position, | ||
@@ -261,2 +287,3 @@ }, | ||
id: childRecordId, | ||
opId: this._doc.generateOpId(), | ||
type: live_1.OpType.DeleteCrdt, | ||
@@ -263,0 +290,0 @@ }, |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { Op, SerializedCrdtWithId } from "./live"; | ||
import { Op, SerializedCrdtWithId, SerializedCrdt } from "./live"; | ||
/** | ||
@@ -14,3 +14,3 @@ * The LiveMap class is similar to a JavaScript Map that is synchronized on all clients. | ||
*/ | ||
_serialize(parentId?: string, parentKey?: string): Op[]; | ||
_serialize(parentId?: string, parentKey?: string, doc?: Doc): Op[]; | ||
/** | ||
@@ -27,3 +27,3 @@ * INTERNAL | ||
*/ | ||
_attachChild(id: string, key: TKey, child: AbstractCrdt): ApplyResult; | ||
_attachChild(id: string, key: TKey, child: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -38,2 +38,6 @@ * INTERNAL | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
/** | ||
* Returns a specified element from the LiveMap. | ||
@@ -40,0 +44,0 @@ * @param key The key of the element to return. |
@@ -44,3 +44,3 @@ "use strict"; | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null) { | ||
@@ -55,2 +55,3 @@ throw new Error("Cannot serialize item is not attached"); | ||
id: this._id, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
type: live_1.OpType.CreateMap, | ||
@@ -62,3 +63,3 @@ parentId, | ||
for (const [key, value] of __classPrivateFieldGet(this, _LiveMap_map, "f")) { | ||
ops.push(...value._serialize(this._id, key)); | ||
ops.push(...value._serialize(this._id, key, doc)); | ||
} | ||
@@ -105,3 +106,3 @@ return ops; | ||
*/ | ||
_attachChild(id, key, child) { | ||
_attachChild(id, key, child, isLocal) { | ||
if (this._doc == null) { | ||
@@ -145,2 +146,13 @@ throw new Error("Can't attach child if doc is not present"); | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: live_1.CrdtType.Map, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
}; | ||
} | ||
/** | ||
* Returns a specified element from the LiveMap. | ||
@@ -173,3 +185,3 @@ * @param key The key of the element to return. | ||
item._attach(id, this._doc); | ||
this._doc.dispatch(item._serialize(this._id, key), oldValue | ||
this._doc.dispatch(item._serialize(this._id, key, this._doc), oldValue | ||
? oldValue._serialize(this._id, key) | ||
@@ -204,3 +216,9 @@ : [{ type: live_1.OpType.DeleteCrdt, id }], [this]); | ||
if (this._doc && item._id) { | ||
this._doc.dispatch([{ type: live_1.OpType.DeleteCrdt, id: item._id }], item._serialize(this._id, key), [this]); | ||
this._doc.dispatch([ | ||
{ | ||
type: live_1.OpType.DeleteCrdt, | ||
id: item._id, | ||
opId: this._doc.generateOpId(), | ||
}, | ||
], item._serialize(this._id, key), [this]); | ||
} | ||
@@ -207,0 +225,0 @@ __classPrivateFieldGet(this, _LiveMap_map, "f").delete(key); |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { Op, SerializedCrdtWithId } from "./live"; | ||
import { Op, SerializedCrdt, SerializedCrdtWithId } from "./live"; | ||
/** | ||
@@ -14,12 +14,14 @@ * The LiveObject class is similar to a JavaScript object that is synchronized on all clients. | ||
*/ | ||
_serialize(parentId?: string, parentKey?: string): Op[]; | ||
_serialize(parentId?: string, parentKey?: string, doc?: Doc): Op[]; | ||
/** | ||
* INTERNAL | ||
*/ | ||
static _deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<{ | ||
[key: string]: any; | ||
}>; | ||
static _deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<Record<string, any>>; | ||
/** | ||
* INTERNAL | ||
*/ | ||
static _deserializeChildren(object: LiveObject, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<Record<string, any>>; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_attach(id: string, doc: Doc): void; | ||
@@ -29,3 +31,3 @@ /** | ||
*/ | ||
_attachChild(id: string, key: keyof T, child: AbstractCrdt): ApplyResult; | ||
_attachChild(id: string, key: keyof T, child: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -38,2 +40,6 @@ * INTERNAL | ||
*/ | ||
_detachChildren(): void; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_detach(): void; | ||
@@ -43,4 +49,8 @@ /** | ||
*/ | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
/** | ||
* Transform the LiveObject into a javascript object | ||
@@ -47,0 +57,0 @@ */ |
@@ -41,3 +41,3 @@ "use strict"; | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null) { | ||
@@ -49,2 +49,3 @@ throw new Error("Cannot serialize item is not attached"); | ||
id: this._id, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
type: live_1.OpType.CreateObject, | ||
@@ -58,3 +59,3 @@ parentId, | ||
if (value instanceof AbstractCrdt_1.AbstractCrdt) { | ||
ops.push(...value._serialize(this._id, key)); | ||
ops.push(...value._serialize(this._id, key, doc)); | ||
} | ||
@@ -76,3 +77,9 @@ else { | ||
object._attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
return this._deserializeChildren(object, parentToChildren, doc); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
static _deserializeChildren(object, parentToChildren, doc) { | ||
const children = parentToChildren.get(object._id); | ||
if (children == null) { | ||
@@ -106,3 +113,3 @@ return object; | ||
*/ | ||
_attachChild(id, key, child) { | ||
_attachChild(id, key, child, isLocal) { | ||
if (this._doc == null) { | ||
@@ -152,2 +159,11 @@ throw new Error("Can't attach child if doc is not present"); | ||
*/ | ||
_detachChildren() { | ||
for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) { | ||
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(key); | ||
value._detach(); | ||
} | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
_detach() { | ||
@@ -164,5 +180,5 @@ super._detach(); | ||
*/ | ||
_apply(op) { | ||
_apply(op, isLocal) { | ||
if (op.type === live_1.OpType.UpdateObject) { | ||
return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyUpdate).call(this, op); | ||
return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyUpdate).call(this, op, isLocal); | ||
} | ||
@@ -172,5 +188,17 @@ else if (op.type === live_1.OpType.DeleteObjectKey) { | ||
} | ||
return super._apply(op); | ||
return super._apply(op, isLocal); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: live_1.CrdtType.Object, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
data: this.toObject(), | ||
}; | ||
} | ||
/** | ||
* Transform the LiveObject into a javascript object | ||
@@ -229,3 +257,10 @@ */ | ||
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString); | ||
this._doc.dispatch([{ type: live_1.OpType.DeleteObjectKey, key: keyAsString, id: this._id }], reverse, [this]); | ||
this._doc.dispatch([ | ||
{ | ||
type: live_1.OpType.DeleteObjectKey, | ||
key: keyAsString, | ||
id: this._id, | ||
opId: this._doc.generateOpId(), | ||
}, | ||
], reverse, [this]); | ||
} | ||
@@ -277,3 +312,3 @@ /** | ||
newValue._attach(this._doc.generateId(), this._doc); | ||
ops.push(...newValue._serialize(this._id, key)); | ||
ops.push(...newValue._serialize(this._id, key, this._doc)); | ||
} | ||
@@ -300,3 +335,3 @@ else { | ||
exports.LiveObject = LiveObject; | ||
_LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _LiveObject_instances = new WeakSet(), _LiveObject_applyUpdate = function _LiveObject_applyUpdate(op) { | ||
_LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _LiveObject_instances = new WeakSet(), _LiveObject_applyUpdate = function _LiveObject_applyUpdate(op, isLocal) { | ||
let isModified = false; | ||
@@ -323,7 +358,2 @@ const reverse = []; | ||
} | ||
let isLocal = false; | ||
if (op.opId == null) { | ||
isLocal = true; | ||
op.opId = this._doc.generateOpId(); | ||
} | ||
for (const key in op.data) { | ||
@@ -330,0 +360,0 @@ if (isLocal) { |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { SerializedCrdtWithId, Op } from "./live"; | ||
import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live"; | ||
/** | ||
@@ -17,6 +17,10 @@ * INTERNAL | ||
*/ | ||
_serialize(parentId: string, parentKey: string): Op[]; | ||
_attachChild(id: string, key: string, crdt: AbstractCrdt): ApplyResult; | ||
_serialize(parentId: string, parentKey: string, doc?: Doc): Op[]; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
_attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
_detachChild(crdt: AbstractCrdt): void; | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
} |
@@ -44,3 +44,3 @@ "use strict"; | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null || parentId == null || parentKey == null) { | ||
@@ -52,2 +52,3 @@ throw new Error("Cannot serialize register if parentId or parentKey is undefined"); | ||
type: live_1.OpType.CreateRegister, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
id: this._id, | ||
@@ -60,3 +61,15 @@ parentId, | ||
} | ||
_attachChild(id, key, crdt) { | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: live_1.CrdtType.Register, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
data: this.data, | ||
}; | ||
} | ||
_attachChild(id, key, crdt, isLocal) { | ||
throw new Error("Method not implemented."); | ||
@@ -67,4 +80,4 @@ } | ||
} | ||
_apply(op) { | ||
return super._apply(op); | ||
_apply(op, isLocal) { | ||
return super._apply(op, isLocal); | ||
} | ||
@@ -71,0 +84,0 @@ } |
@@ -14,2 +14,3 @@ import { Others, Presence, ClientOptions, Room, MyPresenceCallback, OthersEventCallback, AuthEndpoint, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate } from "./types"; | ||
connection: Connection; | ||
lastConnectionId: number | null; | ||
socket: WebSocket | null; | ||
@@ -66,2 +67,3 @@ lastFlushTime: number; | ||
}; | ||
offlineOperations: Map<string, Op>; | ||
}; | ||
@@ -94,2 +96,8 @@ export declare type Effects = { | ||
onNavigatorOnline: () => void; | ||
simulateSocketClose: () => void; | ||
simulateSendCloseEvent: (event: { | ||
code: number; | ||
wasClean: boolean; | ||
reason: any; | ||
}) => void; | ||
onVisibilityChange: (visibilityState: VisibilityState) => void; | ||
@@ -96,0 +104,0 @@ getUndoStack: () => HistoryItem[]; |
@@ -132,4 +132,12 @@ "use strict"; | ||
} | ||
function createRootFromMessage(message) { | ||
state.root = load(message.items); | ||
function createOrUpdateRootFromMessage(message) { | ||
if (message.items.length === 0) { | ||
throw new Error("Internal error: cannot load storage without items"); | ||
} | ||
if (state.root) { | ||
updateRoot(message.items); | ||
} | ||
else { | ||
state.root = load(message.items); | ||
} | ||
for (const key in state.defaultStorageRoot) { | ||
@@ -141,6 +149,3 @@ if (state.root.get(key) == null) { | ||
} | ||
function load(items) { | ||
if (items.length === 0) { | ||
throw new Error("Internal error: cannot load storage without items"); | ||
} | ||
function buildRootAndParentToChildren(items) { | ||
const parentToChildren = new Map(); | ||
@@ -166,2 +171,19 @@ let root = null; | ||
} | ||
return [root, parentToChildren]; | ||
} | ||
function updateRoot(items) { | ||
if (!state.root) { | ||
return; | ||
} | ||
const currentItems = new Map(); | ||
state.items.forEach((liveCrdt, id) => { | ||
currentItems.set(id, liveCrdt._toSerializedCrdt()); | ||
}); | ||
// Get operations that represent the diff between 2 states. | ||
const ops = (0, utils_1.getTreesDiffOperations)(currentItems, new Map(items)); | ||
const result = apply(ops, false); | ||
notify(result.updates); | ||
} | ||
function load(items) { | ||
const [root, parentToChildren] = buildRootAndParentToChildren(items); | ||
return LiveObject_1.LiveObject._deserialize(root, parentToChildren, { | ||
@@ -255,3 +277,6 @@ addItem, | ||
} | ||
throw new Error("Internal. Tried to get connection id but connection is not open"); | ||
else if (state.lastConnectionId !== null) { | ||
return state.lastConnectionId; | ||
} | ||
throw new Error("Internal. Tried to get connection id but connection was never open"); | ||
} | ||
@@ -264,3 +289,3 @@ function generateId() { | ||
} | ||
function apply(item) { | ||
function apply(item, isLocal) { | ||
const result = { | ||
@@ -292,3 +317,7 @@ reverse: [], | ||
else { | ||
const applyOpResult = applyOp(op); | ||
// Ops applied after undo/redo don't have an opId. | ||
if (isLocal && !op.opId) { | ||
op.opId = generateOpId(); | ||
} | ||
const applyOpResult = applyOp(op, isLocal); | ||
if (applyOpResult.modified) { | ||
@@ -302,3 +331,6 @@ result.updates.nodes.add(applyOpResult.modified); | ||
} | ||
function applyOp(op) { | ||
function applyOp(op, isLocal) { | ||
if (op.opId) { | ||
state.offlineOperations.delete(op.opId); | ||
} | ||
switch (op.type) { | ||
@@ -312,3 +344,3 @@ case live_1.OpType.DeleteObjectKey: | ||
} | ||
return item._apply(op); | ||
return item._apply(op, isLocal); | ||
} | ||
@@ -341,3 +373,3 @@ case live_1.OpType.SetParentKey: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data)); | ||
return parent._attachChild(op.id, op.parentKey, new LiveObject_1.LiveObject(op.data), isLocal); | ||
} | ||
@@ -349,3 +381,3 @@ case live_1.OpType.CreateList: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList()); | ||
return parent._attachChild(op.id, op.parentKey, new LiveList_1.LiveList(), isLocal); | ||
} | ||
@@ -357,3 +389,3 @@ case live_1.OpType.CreateRegister: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data)); | ||
return parent._attachChild(op.id, op.parentKey, new LiveRegister_1.LiveRegister(op.data), isLocal); | ||
} | ||
@@ -365,3 +397,3 @@ case live_1.OpType.CreateMap: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap()); | ||
return parent._attachChild(op.id, op.parentKey, new LiveMap_1.LiveMap(), isLocal); | ||
} | ||
@@ -586,3 +618,4 @@ } | ||
case live_1.ServerMessageType.InitialStorageState: { | ||
createRootFromMessage(subMessage); | ||
createOrUpdateRootFromMessage(subMessage); | ||
applyAndSendOfflineOps(); | ||
_getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver(); | ||
@@ -592,3 +625,3 @@ break; | ||
case live_1.ServerMessageType.UpdateStorage: { | ||
const applyResult = apply(subMessage.ops); | ||
const applyResult = apply(subMessage.ops, false); | ||
for (const node of applyResult.updates.nodes) { | ||
@@ -657,2 +690,6 @@ updates.nodes.add(node); | ||
state.numberOfRetry = 0; | ||
state.lastConnectionId = state.connection.id; | ||
if (state.root) { | ||
state.buffer.messages.push({ type: live_1.ClientMessageType.FetchStorage }); | ||
} | ||
tryFlushing(); | ||
@@ -697,7 +734,25 @@ } | ||
} | ||
function tryFlushing() { | ||
if (state.socket == null) { | ||
function applyAndSendOfflineOps() { | ||
if (state.offlineOperations.size === 0) { | ||
return; | ||
} | ||
if (state.socket.readyState !== WebSocket.OPEN) { | ||
const messages = []; | ||
const ops = Array.from(state.offlineOperations.values()); | ||
const result = apply(ops, true); | ||
messages.push({ | ||
type: live_1.ClientMessageType.UpdateStorage, | ||
ops: ops, | ||
}); | ||
notify(result.updates); | ||
effects.send(messages); | ||
} | ||
function tryFlushing() { | ||
const storageOps = state.buffer.storageOperations; | ||
if (storageOps.length > 0) { | ||
storageOps.forEach((op) => { | ||
state.offlineOperations.set(op.opId, op); | ||
}); | ||
} | ||
if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) { | ||
state.buffer.storageOperations = []; | ||
return; | ||
@@ -820,3 +875,3 @@ } | ||
state.isHistoryPaused = false; | ||
const result = apply(historyItem); | ||
const result = apply(historyItem, true); | ||
notify(result.updates); | ||
@@ -840,3 +895,3 @@ state.redoStack.push(result.reverse); | ||
state.isHistoryPaused = false; | ||
const result = apply(historyItem); | ||
const result = apply(historyItem, true); | ||
notify(result.updates); | ||
@@ -893,2 +948,12 @@ state.undoStack.push(result.reverse); | ||
} | ||
function simulateSocketClose() { | ||
if (state.socket) { | ||
state.socket.close(); | ||
} | ||
} | ||
function simulateSendCloseEvent(event) { | ||
if (state.socket) { | ||
onClose(event); | ||
} | ||
} | ||
return { | ||
@@ -902,2 +967,5 @@ // Internal | ||
onNavigatorOnline, | ||
// Internal dev tools | ||
simulateSocketClose, | ||
simulateSendCloseEvent, | ||
// onWakeUp, | ||
@@ -935,2 +1003,3 @@ onVisibilityChange, | ||
connection: { state: "closed" }, | ||
lastConnectionId: null, | ||
socket: null, | ||
@@ -980,2 +1049,3 @@ listeners: { | ||
}, | ||
offlineOperations: new Map(), | ||
}; | ||
@@ -1027,2 +1097,7 @@ } | ||
}, | ||
// @ts-ignore | ||
internalDevTools: { | ||
closeWebsocket: machine.simulateSocketClose, | ||
sendCloseEvent: machine.simulateSendCloseEvent, | ||
}, | ||
}; | ||
@@ -1029,0 +1104,0 @@ return { |
import { AbstractCrdt, Doc } from "./AbstractCrdt"; | ||
import { SerializedCrdtWithId } from "./live"; | ||
import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live"; | ||
export declare function remove<T>(array: T[], item: T): void; | ||
@@ -9,1 +9,2 @@ export declare function isSameNodeOrChildOf(node: AbstractCrdt, parent: AbstractCrdt): boolean; | ||
export declare function selfOrRegister(obj: any): AbstractCrdt; | ||
export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[]; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0; | ||
exports.getTreesDiffOperations = exports.selfOrRegister = exports.selfOrRegisterValue = exports.isCrdt = exports.deserialize = exports.isSameNodeOrChildOf = exports.remove = void 0; | ||
const live_1 = require("./live"); | ||
@@ -76,1 +76,76 @@ const LiveList_1 = require("./LiveList"); | ||
exports.selfOrRegister = selfOrRegister; | ||
function getTreesDiffOperations(currentItems, newItems) { | ||
const ops = []; | ||
currentItems.forEach((_, id) => { | ||
if (!newItems.get(id)) { | ||
// Delete crdt | ||
ops.push({ | ||
type: live_1.OpType.DeleteCrdt, | ||
id: id, | ||
}); | ||
} | ||
}); | ||
newItems.forEach((crdt, id) => { | ||
const currentCrdt = currentItems.get(id); | ||
if (currentCrdt) { | ||
if (crdt.type === live_1.CrdtType.Object) { | ||
if (JSON.stringify(crdt.data) !== | ||
JSON.stringify(currentCrdt.data)) { | ||
ops.push({ | ||
type: live_1.OpType.UpdateObject, | ||
id: id, | ||
data: crdt.data, | ||
}); | ||
} | ||
} | ||
if (crdt.parentKey !== currentCrdt.parentKey) { | ||
ops.push({ | ||
type: live_1.OpType.SetParentKey, | ||
id: id, | ||
parentKey: crdt.parentKey, | ||
}); | ||
} | ||
} | ||
else { | ||
// new Crdt | ||
switch (crdt.type) { | ||
case live_1.CrdtType.Register: | ||
ops.push({ | ||
type: live_1.OpType.CreateRegister, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
data: crdt.data, | ||
}); | ||
break; | ||
case live_1.CrdtType.List: | ||
ops.push({ | ||
type: live_1.OpType.CreateList, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
}); | ||
break; | ||
case live_1.CrdtType.Object: | ||
ops.push({ | ||
type: live_1.OpType.CreateObject, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
data: crdt.data, | ||
}); | ||
break; | ||
case live_1.CrdtType.Map: | ||
ops.push({ | ||
type: live_1.OpType.CreateMap, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
}); | ||
break; | ||
} | ||
} | ||
}); | ||
return ops; | ||
} | ||
exports.getTreesDiffOperations = getTreesDiffOperations; |
@@ -1,2 +0,2 @@ | ||
import { Op } from "./live"; | ||
import { Op, SerializedCrdt } from "./live"; | ||
export declare type ApplyResult = { | ||
@@ -36,3 +36,3 @@ reverse: Op[]; | ||
*/ | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -49,3 +49,3 @@ * INTERNAL | ||
*/ | ||
abstract _attachChild(id: string, key: string, crdt: AbstractCrdt): ApplyResult; | ||
abstract _attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -62,3 +62,7 @@ * INTERNAL | ||
*/ | ||
abstract _serialize(parentId: string, parentKey: string): Op[]; | ||
abstract _serialize(parentId: string, parentKey: string, doc?: Doc): Op[]; | ||
/** | ||
* INTERNAL | ||
*/ | ||
abstract _toSerializedCrdt(): SerializedCrdt; | ||
} |
@@ -48,3 +48,3 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
*/ | ||
_apply(op) { | ||
_apply(op, isLocal) { | ||
switch (op.type) { | ||
@@ -54,3 +54,3 @@ case OpType.DeleteCrdt: { | ||
const parent = this._parent; | ||
const reverse = this._serialize(this._parent._id, this._parentKey); | ||
const reverse = this._serialize(this._parent._id, this._parentKey, __classPrivateFieldGet(this, _AbstractCrdt_doc, "f")); | ||
this._parent._detachChild(this); | ||
@@ -57,0 +57,0 @@ return { modified: parent, reverse }; |
@@ -125,2 +125,3 @@ import { Presence } from "./types"; | ||
export declare type CreateObjectOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -135,2 +136,3 @@ type: OpType.CreateObject; | ||
export declare type CreateListOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -142,2 +144,3 @@ type: OpType.CreateList; | ||
export declare type CreateMapOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -149,2 +152,3 @@ type: OpType.CreateMap; | ||
export declare type CreateRegisterOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -157,2 +161,3 @@ type: OpType.CreateRegister; | ||
export declare type DeleteCrdtOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -162,2 +167,3 @@ type: OpType.DeleteCrdt; | ||
export declare type SetParentKeyOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -168,2 +174,3 @@ type: OpType.SetParentKey; | ||
export declare type DeleteObjectKeyOp = { | ||
opId?: string; | ||
id: string; | ||
@@ -170,0 +177,0 @@ type: OpType.DeleteObjectKey; |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { SerializedList, SerializedCrdtWithId, Op } from "./live"; | ||
import { SerializedList, SerializedCrdtWithId, Op, SerializedCrdt } from "./live"; | ||
/** | ||
@@ -16,3 +16,3 @@ * The LiveList class represents an ordered collection of items that is synchorinized across clients. | ||
*/ | ||
_serialize(parentId?: string, parentKey?: string): Op[]; | ||
_serialize(parentId?: string, parentKey?: string, doc?: Doc): Op[]; | ||
/** | ||
@@ -29,3 +29,3 @@ * INTERNAL | ||
*/ | ||
_attachChild(id: string, key: string, child: AbstractCrdt): ApplyResult; | ||
_attachChild(id: string, key: string, child: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -42,4 +42,8 @@ * INTERNAL | ||
*/ | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
/** | ||
* Returns the number of elements. | ||
@@ -46,0 +50,0 @@ */ |
@@ -15,3 +15,3 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { | ||
import { deserialize, selfOrRegister, selfOrRegisterValue } from "./utils"; | ||
import { OpType, } from "./live"; | ||
import { OpType, CrdtType, } from "./live"; | ||
import { makePosition, compare } from "./position"; | ||
@@ -55,3 +55,3 @@ /** | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null) { | ||
@@ -66,2 +66,3 @@ throw new Error("Cannot serialize item is not attached"); | ||
id: this._id, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
type: OpType.CreateList, | ||
@@ -73,3 +74,3 @@ parentId, | ||
for (const [value, key] of __classPrivateFieldGet(this, _LiveList_items, "f")) { | ||
ops.push(...value._serialize(this._id, key)); | ||
ops.push(...value._serialize(this._id, key, doc)); | ||
} | ||
@@ -99,3 +100,3 @@ return ops; | ||
*/ | ||
_attachChild(id, key, child) { | ||
_attachChild(id, key, child, isLocal) { | ||
var _a; | ||
@@ -108,7 +109,20 @@ if (this._doc == null) { | ||
const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key); | ||
// Assign a temporary position until we get the fix from the backend | ||
let newKey = key; | ||
// If there is a conflict | ||
if (index !== -1) { | ||
__classPrivateFieldGet(this, _LiveList_items, "f")[index][1] = makePosition(key, (_a = __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1]) === null || _a === void 0 ? void 0 : _a[1]); | ||
if (isLocal) { | ||
// If change is local => assign a temporary position to newly attached child | ||
let before = __classPrivateFieldGet(this, _LiveList_items, "f")[index] ? __classPrivateFieldGet(this, _LiveList_items, "f")[index][1] : undefined; | ||
let after = __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1] | ||
? __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1][1] | ||
: undefined; | ||
newKey = makePosition(before, after); | ||
child._setParentLink(this, newKey); | ||
} | ||
else { | ||
// If change is remote => assign a temporary position to existing child until we get the fix from the backend | ||
__classPrivateFieldGet(this, _LiveList_items, "f")[index][1] = makePosition(key, (_a = __classPrivateFieldGet(this, _LiveList_items, "f")[index + 1]) === null || _a === void 0 ? void 0 : _a[1]); | ||
} | ||
} | ||
__classPrivateFieldGet(this, _LiveList_items, "f").push([child, key]); | ||
__classPrivateFieldGet(this, _LiveList_items, "f").push([child, newKey]); | ||
__classPrivateFieldGet(this, _LiveList_items, "f").sort((itemA, itemB) => compare(itemA[1], itemB[1])); | ||
@@ -147,6 +161,17 @@ return { reverse: [{ type: OpType.DeleteCrdt, id }], modified: this }; | ||
*/ | ||
_apply(op) { | ||
return super._apply(op); | ||
_apply(op, isLocal) { | ||
return super._apply(op, isLocal); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: CrdtType.List, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
}; | ||
} | ||
/** | ||
* Returns the number of elements. | ||
@@ -183,3 +208,3 @@ */ | ||
value._attach(id, this._doc); | ||
this._doc.dispatch(value._serialize(this._id, position), [{ type: OpType.DeleteCrdt, id }], [this]); | ||
this._doc.dispatch(value._serialize(this._id, position, this._doc), [{ type: OpType.DeleteCrdt, id }], [this]); | ||
} | ||
@@ -230,2 +255,3 @@ } | ||
id: item[0]._id, | ||
opId: this._doc.generateOpId(), | ||
parentKey: position, | ||
@@ -259,2 +285,3 @@ }, | ||
id: childRecordId, | ||
opId: this._doc.generateOpId(), | ||
type: OpType.DeleteCrdt, | ||
@@ -261,0 +288,0 @@ }, |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { Op, SerializedCrdtWithId } from "./live"; | ||
import { Op, SerializedCrdtWithId, SerializedCrdt } from "./live"; | ||
/** | ||
@@ -14,3 +14,3 @@ * The LiveMap class is similar to a JavaScript Map that is synchronized on all clients. | ||
*/ | ||
_serialize(parentId?: string, parentKey?: string): Op[]; | ||
_serialize(parentId?: string, parentKey?: string, doc?: Doc): Op[]; | ||
/** | ||
@@ -27,3 +27,3 @@ * INTERNAL | ||
*/ | ||
_attachChild(id: string, key: TKey, child: AbstractCrdt): ApplyResult; | ||
_attachChild(id: string, key: TKey, child: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -38,2 +38,6 @@ * INTERNAL | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
/** | ||
* Returns a specified element from the LiveMap. | ||
@@ -40,0 +44,0 @@ * @param key The key of the element to return. |
@@ -41,3 +41,3 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null) { | ||
@@ -52,2 +52,3 @@ throw new Error("Cannot serialize item is not attached"); | ||
id: this._id, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
type: OpType.CreateMap, | ||
@@ -59,3 +60,3 @@ parentId, | ||
for (const [key, value] of __classPrivateFieldGet(this, _LiveMap_map, "f")) { | ||
ops.push(...value._serialize(this._id, key)); | ||
ops.push(...value._serialize(this._id, key, doc)); | ||
} | ||
@@ -102,3 +103,3 @@ return ops; | ||
*/ | ||
_attachChild(id, key, child) { | ||
_attachChild(id, key, child, isLocal) { | ||
if (this._doc == null) { | ||
@@ -142,2 +143,13 @@ throw new Error("Can't attach child if doc is not present"); | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: CrdtType.Map, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
}; | ||
} | ||
/** | ||
* Returns a specified element from the LiveMap. | ||
@@ -170,3 +182,3 @@ * @param key The key of the element to return. | ||
item._attach(id, this._doc); | ||
this._doc.dispatch(item._serialize(this._id, key), oldValue | ||
this._doc.dispatch(item._serialize(this._id, key, this._doc), oldValue | ||
? oldValue._serialize(this._id, key) | ||
@@ -201,3 +213,9 @@ : [{ type: OpType.DeleteCrdt, id }], [this]); | ||
if (this._doc && item._id) { | ||
this._doc.dispatch([{ type: OpType.DeleteCrdt, id: item._id }], item._serialize(this._id, key), [this]); | ||
this._doc.dispatch([ | ||
{ | ||
type: OpType.DeleteCrdt, | ||
id: item._id, | ||
opId: this._doc.generateOpId(), | ||
}, | ||
], item._serialize(this._id, key), [this]); | ||
} | ||
@@ -204,0 +222,0 @@ __classPrivateFieldGet(this, _LiveMap_map, "f").delete(key); |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { Op, SerializedCrdtWithId } from "./live"; | ||
import { Op, SerializedCrdt, SerializedCrdtWithId } from "./live"; | ||
/** | ||
@@ -14,12 +14,14 @@ * The LiveObject class is similar to a JavaScript object that is synchronized on all clients. | ||
*/ | ||
_serialize(parentId?: string, parentKey?: string): Op[]; | ||
_serialize(parentId?: string, parentKey?: string, doc?: Doc): Op[]; | ||
/** | ||
* INTERNAL | ||
*/ | ||
static _deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<{ | ||
[key: string]: any; | ||
}>; | ||
static _deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<Record<string, any>>; | ||
/** | ||
* INTERNAL | ||
*/ | ||
static _deserializeChildren(object: LiveObject, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<Record<string, any>>; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_attach(id: string, doc: Doc): void; | ||
@@ -29,3 +31,3 @@ /** | ||
*/ | ||
_attachChild(id: string, key: keyof T, child: AbstractCrdt): ApplyResult; | ||
_attachChild(id: string, key: keyof T, child: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
/** | ||
@@ -38,2 +40,6 @@ * INTERNAL | ||
*/ | ||
_detachChildren(): void; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_detach(): void; | ||
@@ -43,4 +49,8 @@ /** | ||
*/ | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
/** | ||
* Transform the LiveObject into a javascript object | ||
@@ -47,0 +57,0 @@ */ |
@@ -38,3 +38,3 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null) { | ||
@@ -46,2 +46,3 @@ throw new Error("Cannot serialize item is not attached"); | ||
id: this._id, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
type: OpType.CreateObject, | ||
@@ -55,3 +56,3 @@ parentId, | ||
if (value instanceof AbstractCrdt) { | ||
ops.push(...value._serialize(this._id, key)); | ||
ops.push(...value._serialize(this._id, key, doc)); | ||
} | ||
@@ -73,3 +74,9 @@ else { | ||
object._attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
return this._deserializeChildren(object, parentToChildren, doc); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
static _deserializeChildren(object, parentToChildren, doc) { | ||
const children = parentToChildren.get(object._id); | ||
if (children == null) { | ||
@@ -103,3 +110,3 @@ return object; | ||
*/ | ||
_attachChild(id, key, child) { | ||
_attachChild(id, key, child, isLocal) { | ||
if (this._doc == null) { | ||
@@ -149,2 +156,11 @@ throw new Error("Can't attach child if doc is not present"); | ||
*/ | ||
_detachChildren() { | ||
for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) { | ||
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(key); | ||
value._detach(); | ||
} | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
_detach() { | ||
@@ -161,5 +177,5 @@ super._detach(); | ||
*/ | ||
_apply(op) { | ||
_apply(op, isLocal) { | ||
if (op.type === OpType.UpdateObject) { | ||
return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyUpdate).call(this, op); | ||
return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyUpdate).call(this, op, isLocal); | ||
} | ||
@@ -169,5 +185,17 @@ else if (op.type === OpType.DeleteObjectKey) { | ||
} | ||
return super._apply(op); | ||
return super._apply(op, isLocal); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: CrdtType.Object, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
data: this.toObject(), | ||
}; | ||
} | ||
/** | ||
* Transform the LiveObject into a javascript object | ||
@@ -226,3 +254,10 @@ */ | ||
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString); | ||
this._doc.dispatch([{ type: OpType.DeleteObjectKey, key: keyAsString, id: this._id }], reverse, [this]); | ||
this._doc.dispatch([ | ||
{ | ||
type: OpType.DeleteObjectKey, | ||
key: keyAsString, | ||
id: this._id, | ||
opId: this._doc.generateOpId(), | ||
}, | ||
], reverse, [this]); | ||
} | ||
@@ -274,3 +309,3 @@ /** | ||
newValue._attach(this._doc.generateId(), this._doc); | ||
ops.push(...newValue._serialize(this._id, key)); | ||
ops.push(...newValue._serialize(this._id, key, this._doc)); | ||
} | ||
@@ -296,3 +331,3 @@ else { | ||
} | ||
_LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _LiveObject_instances = new WeakSet(), _LiveObject_applyUpdate = function _LiveObject_applyUpdate(op) { | ||
_LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _LiveObject_instances = new WeakSet(), _LiveObject_applyUpdate = function _LiveObject_applyUpdate(op, isLocal) { | ||
let isModified = false; | ||
@@ -319,7 +354,2 @@ const reverse = []; | ||
} | ||
let isLocal = false; | ||
if (op.opId == null) { | ||
isLocal = true; | ||
op.opId = this._doc.generateOpId(); | ||
} | ||
for (const key in op.data) { | ||
@@ -326,0 +356,0 @@ if (isLocal) { |
import { AbstractCrdt, Doc, ApplyResult } from "./AbstractCrdt"; | ||
import { SerializedCrdtWithId, Op } from "./live"; | ||
import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live"; | ||
/** | ||
@@ -17,6 +17,10 @@ * INTERNAL | ||
*/ | ||
_serialize(parentId: string, parentKey: string): Op[]; | ||
_attachChild(id: string, key: string, crdt: AbstractCrdt): ApplyResult; | ||
_serialize(parentId: string, parentKey: string, doc?: Doc): Op[]; | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt(): SerializedCrdt; | ||
_attachChild(id: string, key: string, crdt: AbstractCrdt, isLocal: boolean): ApplyResult; | ||
_detachChild(crdt: AbstractCrdt): void; | ||
_apply(op: Op): ApplyResult; | ||
_apply(op: Op, isLocal: boolean): ApplyResult; | ||
} |
@@ -14,3 +14,3 @@ var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { | ||
import { AbstractCrdt } from "./AbstractCrdt"; | ||
import { CrdtType, OpType } from "./live"; | ||
import { CrdtType, OpType, } from "./live"; | ||
/** | ||
@@ -42,3 +42,3 @@ * INTERNAL | ||
*/ | ||
_serialize(parentId, parentKey) { | ||
_serialize(parentId, parentKey, doc) { | ||
if (this._id == null || parentId == null || parentKey == null) { | ||
@@ -50,2 +50,3 @@ throw new Error("Cannot serialize register if parentId or parentKey is undefined"); | ||
type: OpType.CreateRegister, | ||
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), | ||
id: this._id, | ||
@@ -58,3 +59,15 @@ parentId, | ||
} | ||
_attachChild(id, key, crdt) { | ||
/** | ||
* INTERNAL | ||
*/ | ||
_toSerializedCrdt() { | ||
var _a; | ||
return { | ||
type: CrdtType.Register, | ||
parentId: (_a = this._parent) === null || _a === void 0 ? void 0 : _a._id, | ||
parentKey: this._parentKey, | ||
data: this.data, | ||
}; | ||
} | ||
_attachChild(id, key, crdt, isLocal) { | ||
throw new Error("Method not implemented."); | ||
@@ -65,6 +78,6 @@ } | ||
} | ||
_apply(op) { | ||
return super._apply(op); | ||
_apply(op, isLocal) { | ||
return super._apply(op, isLocal); | ||
} | ||
} | ||
_LiveRegister_data = new WeakMap(); |
@@ -14,2 +14,3 @@ import { Others, Presence, ClientOptions, Room, MyPresenceCallback, OthersEventCallback, AuthEndpoint, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback, StorageCallback, StorageUpdate } from "./types"; | ||
connection: Connection; | ||
lastConnectionId: number | null; | ||
socket: WebSocket | null; | ||
@@ -66,2 +67,3 @@ lastFlushTime: number; | ||
}; | ||
offlineOperations: Map<string, Op>; | ||
}; | ||
@@ -94,2 +96,8 @@ export declare type Effects = { | ||
onNavigatorOnline: () => void; | ||
simulateSocketClose: () => void; | ||
simulateSendCloseEvent: (event: { | ||
code: number; | ||
wasClean: boolean; | ||
reason: any; | ||
}) => void; | ||
onVisibilityChange: (visibilityState: VisibilityState) => void; | ||
@@ -96,0 +104,0 @@ getUndoStack: () => HistoryItem[]; |
@@ -10,3 +10,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
import { isSameNodeOrChildOf, remove } from "./utils"; | ||
import { getTreesDiffOperations, isSameNodeOrChildOf, remove } from "./utils"; | ||
import auth, { parseToken } from "./authentication"; | ||
@@ -111,4 +111,12 @@ import { ClientMessageType, ServerMessageType, OpType, } from "./live"; | ||
} | ||
function createRootFromMessage(message) { | ||
state.root = load(message.items); | ||
function createOrUpdateRootFromMessage(message) { | ||
if (message.items.length === 0) { | ||
throw new Error("Internal error: cannot load storage without items"); | ||
} | ||
if (state.root) { | ||
updateRoot(message.items); | ||
} | ||
else { | ||
state.root = load(message.items); | ||
} | ||
for (const key in state.defaultStorageRoot) { | ||
@@ -120,6 +128,3 @@ if (state.root.get(key) == null) { | ||
} | ||
function load(items) { | ||
if (items.length === 0) { | ||
throw new Error("Internal error: cannot load storage without items"); | ||
} | ||
function buildRootAndParentToChildren(items) { | ||
const parentToChildren = new Map(); | ||
@@ -145,2 +150,19 @@ let root = null; | ||
} | ||
return [root, parentToChildren]; | ||
} | ||
function updateRoot(items) { | ||
if (!state.root) { | ||
return; | ||
} | ||
const currentItems = new Map(); | ||
state.items.forEach((liveCrdt, id) => { | ||
currentItems.set(id, liveCrdt._toSerializedCrdt()); | ||
}); | ||
// Get operations that represent the diff between 2 states. | ||
const ops = getTreesDiffOperations(currentItems, new Map(items)); | ||
const result = apply(ops, false); | ||
notify(result.updates); | ||
} | ||
function load(items) { | ||
const [root, parentToChildren] = buildRootAndParentToChildren(items); | ||
return LiveObject._deserialize(root, parentToChildren, { | ||
@@ -234,3 +256,6 @@ addItem, | ||
} | ||
throw new Error("Internal. Tried to get connection id but connection is not open"); | ||
else if (state.lastConnectionId !== null) { | ||
return state.lastConnectionId; | ||
} | ||
throw new Error("Internal. Tried to get connection id but connection was never open"); | ||
} | ||
@@ -243,3 +268,3 @@ function generateId() { | ||
} | ||
function apply(item) { | ||
function apply(item, isLocal) { | ||
const result = { | ||
@@ -271,3 +296,7 @@ reverse: [], | ||
else { | ||
const applyOpResult = applyOp(op); | ||
// Ops applied after undo/redo don't have an opId. | ||
if (isLocal && !op.opId) { | ||
op.opId = generateOpId(); | ||
} | ||
const applyOpResult = applyOp(op, isLocal); | ||
if (applyOpResult.modified) { | ||
@@ -281,3 +310,6 @@ result.updates.nodes.add(applyOpResult.modified); | ||
} | ||
function applyOp(op) { | ||
function applyOp(op, isLocal) { | ||
if (op.opId) { | ||
state.offlineOperations.delete(op.opId); | ||
} | ||
switch (op.type) { | ||
@@ -291,3 +323,3 @@ case OpType.DeleteObjectKey: | ||
} | ||
return item._apply(op); | ||
return item._apply(op, isLocal); | ||
} | ||
@@ -320,3 +352,3 @@ case OpType.SetParentKey: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data)); | ||
return parent._attachChild(op.id, op.parentKey, new LiveObject(op.data), isLocal); | ||
} | ||
@@ -328,3 +360,3 @@ case OpType.CreateList: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveList()); | ||
return parent._attachChild(op.id, op.parentKey, new LiveList(), isLocal); | ||
} | ||
@@ -336,3 +368,3 @@ case OpType.CreateRegister: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data)); | ||
return parent._attachChild(op.id, op.parentKey, new LiveRegister(op.data), isLocal); | ||
} | ||
@@ -344,3 +376,3 @@ case OpType.CreateMap: { | ||
} | ||
return parent._attachChild(op.id, op.parentKey, new LiveMap()); | ||
return parent._attachChild(op.id, op.parentKey, new LiveMap(), isLocal); | ||
} | ||
@@ -565,3 +597,4 @@ } | ||
case ServerMessageType.InitialStorageState: { | ||
createRootFromMessage(subMessage); | ||
createOrUpdateRootFromMessage(subMessage); | ||
applyAndSendOfflineOps(); | ||
_getInitialStateResolver === null || _getInitialStateResolver === void 0 ? void 0 : _getInitialStateResolver(); | ||
@@ -571,3 +604,3 @@ break; | ||
case ServerMessageType.UpdateStorage: { | ||
const applyResult = apply(subMessage.ops); | ||
const applyResult = apply(subMessage.ops, false); | ||
for (const node of applyResult.updates.nodes) { | ||
@@ -636,2 +669,6 @@ updates.nodes.add(node); | ||
state.numberOfRetry = 0; | ||
state.lastConnectionId = state.connection.id; | ||
if (state.root) { | ||
state.buffer.messages.push({ type: ClientMessageType.FetchStorage }); | ||
} | ||
tryFlushing(); | ||
@@ -676,7 +713,25 @@ } | ||
} | ||
function tryFlushing() { | ||
if (state.socket == null) { | ||
function applyAndSendOfflineOps() { | ||
if (state.offlineOperations.size === 0) { | ||
return; | ||
} | ||
if (state.socket.readyState !== WebSocket.OPEN) { | ||
const messages = []; | ||
const ops = Array.from(state.offlineOperations.values()); | ||
const result = apply(ops, true); | ||
messages.push({ | ||
type: ClientMessageType.UpdateStorage, | ||
ops: ops, | ||
}); | ||
notify(result.updates); | ||
effects.send(messages); | ||
} | ||
function tryFlushing() { | ||
const storageOps = state.buffer.storageOperations; | ||
if (storageOps.length > 0) { | ||
storageOps.forEach((op) => { | ||
state.offlineOperations.set(op.opId, op); | ||
}); | ||
} | ||
if (state.socket == null || state.socket.readyState !== WebSocket.OPEN) { | ||
state.buffer.storageOperations = []; | ||
return; | ||
@@ -799,3 +854,3 @@ } | ||
state.isHistoryPaused = false; | ||
const result = apply(historyItem); | ||
const result = apply(historyItem, true); | ||
notify(result.updates); | ||
@@ -819,3 +874,3 @@ state.redoStack.push(result.reverse); | ||
state.isHistoryPaused = false; | ||
const result = apply(historyItem); | ||
const result = apply(historyItem, true); | ||
notify(result.updates); | ||
@@ -872,2 +927,12 @@ state.undoStack.push(result.reverse); | ||
} | ||
function simulateSocketClose() { | ||
if (state.socket) { | ||
state.socket.close(); | ||
} | ||
} | ||
function simulateSendCloseEvent(event) { | ||
if (state.socket) { | ||
onClose(event); | ||
} | ||
} | ||
return { | ||
@@ -881,2 +946,5 @@ // Internal | ||
onNavigatorOnline, | ||
// Internal dev tools | ||
simulateSocketClose, | ||
simulateSendCloseEvent, | ||
// onWakeUp, | ||
@@ -913,2 +981,3 @@ onVisibilityChange, | ||
connection: { state: "closed" }, | ||
lastConnectionId: null, | ||
socket: null, | ||
@@ -958,2 +1027,3 @@ listeners: { | ||
}, | ||
offlineOperations: new Map(), | ||
}; | ||
@@ -1004,2 +1074,7 @@ } | ||
}, | ||
// @ts-ignore | ||
internalDevTools: { | ||
closeWebsocket: machine.simulateSocketClose, | ||
sendCloseEvent: machine.simulateSendCloseEvent, | ||
}, | ||
}; | ||
@@ -1006,0 +1081,0 @@ return { |
import { AbstractCrdt, Doc } from "./AbstractCrdt"; | ||
import { SerializedCrdtWithId } from "./live"; | ||
import { SerializedCrdtWithId, Op, SerializedCrdt } from "./live"; | ||
export declare function remove<T>(array: T[], item: T): void; | ||
@@ -9,1 +9,2 @@ export declare function isSameNodeOrChildOf(node: AbstractCrdt, parent: AbstractCrdt): boolean; | ||
export declare function selfOrRegister(obj: any): AbstractCrdt; | ||
export declare function getTreesDiffOperations(currentItems: Map<string, SerializedCrdt>, newItems: Map<string, SerializedCrdt>): Op[]; |
@@ -1,2 +0,2 @@ | ||
import { CrdtType, } from "./live"; | ||
import { CrdtType, OpType, } from "./live"; | ||
import { LiveList } from "./LiveList"; | ||
@@ -67,1 +67,75 @@ import { LiveMap } from "./LiveMap"; | ||
} | ||
export function getTreesDiffOperations(currentItems, newItems) { | ||
const ops = []; | ||
currentItems.forEach((_, id) => { | ||
if (!newItems.get(id)) { | ||
// Delete crdt | ||
ops.push({ | ||
type: OpType.DeleteCrdt, | ||
id: id, | ||
}); | ||
} | ||
}); | ||
newItems.forEach((crdt, id) => { | ||
const currentCrdt = currentItems.get(id); | ||
if (currentCrdt) { | ||
if (crdt.type === CrdtType.Object) { | ||
if (JSON.stringify(crdt.data) !== | ||
JSON.stringify(currentCrdt.data)) { | ||
ops.push({ | ||
type: OpType.UpdateObject, | ||
id: id, | ||
data: crdt.data, | ||
}); | ||
} | ||
} | ||
if (crdt.parentKey !== currentCrdt.parentKey) { | ||
ops.push({ | ||
type: OpType.SetParentKey, | ||
id: id, | ||
parentKey: crdt.parentKey, | ||
}); | ||
} | ||
} | ||
else { | ||
// new Crdt | ||
switch (crdt.type) { | ||
case CrdtType.Register: | ||
ops.push({ | ||
type: OpType.CreateRegister, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
data: crdt.data, | ||
}); | ||
break; | ||
case CrdtType.List: | ||
ops.push({ | ||
type: OpType.CreateList, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
}); | ||
break; | ||
case CrdtType.Object: | ||
ops.push({ | ||
type: OpType.CreateObject, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
data: crdt.data, | ||
}); | ||
break; | ||
case CrdtType.Map: | ||
ops.push({ | ||
type: OpType.CreateMap, | ||
id: id, | ||
parentId: crdt.parentId, | ||
parentKey: crdt.parentKey, | ||
}); | ||
break; | ||
} | ||
} | ||
}); | ||
return ops; | ||
} |
{ | ||
"name": "@liveblocks/client", | ||
"version": "0.13.2", | ||
"version": "0.14.0-beta.1", | ||
"description": "", | ||
@@ -41,2 +41,2 @@ "main": "./lib/cjs/index.js", | ||
} | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
301755
8062
54