@databutton/liveblocks-client
Version:
**At [Liveblocks](https://liveblocks.io), we’re building tools to help companies create world-class collaborative products that attract, engage and retain users.** This repository is a set of open-source packages for building performant and reliable multi
399 lines (398 loc) • 15.2 kB
JavaScript
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
if (kind === "m") throw new TypeError("Private method is not writable");
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
};
var _LiveObject_instances, _LiveObject_map, _LiveObject_propToLastUpdate, _LiveObject_applyUpdate, _LiveObject_applyDeleteObjectKey;
Object.defineProperty(exports, "__esModule", { value: true });
exports.LiveObject = void 0;
const AbstractCrdt_1 = require("./AbstractCrdt");
const utils_1 = require("./utils");
const live_1 = require("./live");
/**
* The LiveObject class is similar to a JavaScript object that is synchronized on all clients.
* Keys should be a string, and values should be serializable to JSON.
* If multiple clients update the same property simultaneously, the last modification received by the Liveblocks servers is the winner.
*/
class LiveObject extends AbstractCrdt_1.AbstractCrdt {
constructor(object = {}) {
super();
_LiveObject_instances.add(this);
_LiveObject_map.set(this, void 0);
_LiveObject_propToLastUpdate.set(this, new Map());
for (const key in object) {
const value = object[key];
if (value instanceof AbstractCrdt_1.AbstractCrdt) {
value._setParentLink(this, key);
}
}
__classPrivateFieldSet(this, _LiveObject_map, new Map(Object.entries(object)), "f");
}
/**
* INTERNAL
*/
_serialize(parentId, parentKey, doc) {
if (this._id == null) {
throw new Error("Cannot serialize item is not attached");
}
const ops = [];
const op = {
id: this._id,
opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(),
type: live_1.OpType.CreateObject,
parentId,
parentKey,
data: {},
};
ops.push(op);
for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) {
if (value instanceof AbstractCrdt_1.AbstractCrdt) {
ops.push(...value._serialize(this._id, key, doc));
}
else {
op.data[key] = value;
}
}
return ops;
}
/**
* INTERNAL
*/
static _deserialize([id, item], parentToChildren, doc) {
if (item.type !== live_1.CrdtType.Object) {
throw new Error(`Tried to deserialize a record but item type is "${item.type}"`);
}
const object = new LiveObject(item.data);
object._attach(id, doc);
return this._deserializeChildren(object, parentToChildren, doc);
}
/**
* INTERNAL
*/
static _deserializeChildren(object, parentToChildren, doc) {
const children = parentToChildren.get(object._id);
if (children == null) {
return object;
}
for (const entry of children) {
const crdt = entry[1];
if (crdt.parentKey == null) {
throw new Error("Tried to deserialize a crdt but it does not have a parentKey and is not the root");
}
const child = (0, utils_1.deserialize)(entry, parentToChildren, doc);
child._setParentLink(object, crdt.parentKey);
__classPrivateFieldGet(object, _LiveObject_map, "f").set(crdt.parentKey, child);
}
return object;
}
/**
* INTERNAL
*/
_attach(id, doc) {
super._attach(id, doc);
for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) {
if (value instanceof AbstractCrdt_1.AbstractCrdt) {
value._attach(doc.generateId(), doc);
}
}
}
/**
* INTERNAL
*/
_attachChild(id, key, child, isLocal) {
if (this._doc == null) {
throw new Error("Can't attach child if doc is not present");
}
const previousValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
let reverse;
if ((0, utils_1.isCrdt)(previousValue)) {
reverse = previousValue._serialize(this._id, key);
previousValue._detach();
}
else if (previousValue === undefined) {
reverse = [
{ type: live_1.OpType.DeleteObjectKey, id: this._id, key: key },
];
}
else {
reverse = [
{
type: live_1.OpType.UpdateObject,
id: this._id,
data: { [key]: previousValue },
},
];
}
__classPrivateFieldGet(this, _LiveObject_map, "f").set(key, child);
child._setParentLink(this, key);
child._attach(id, this._doc);
return { reverse, modified: this };
}
/**
* INTERNAL
*/
_detachChild(child) {
for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) {
if (value === child) {
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(key);
}
}
if (child) {
child._detach();
}
}
/**
* INTERNAL
*/
_detachChildren() {
for (const [key, value] of __classPrivateFieldGet(this, _LiveObject_map, "f")) {
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(key);
value._detach();
}
}
/**
* INTERNAL
*/
_detach() {
super._detach();
for (const value of __classPrivateFieldGet(this, _LiveObject_map, "f").values()) {
if ((0, utils_1.isCrdt)(value)) {
value._detach();
}
}
}
/**
* INTERNAL
*/
_apply(op, isLocal) {
if (op.type === live_1.OpType.UpdateObject) {
return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyUpdate).call(this, op, isLocal);
}
else if (op.type === live_1.OpType.DeleteObjectKey) {
return __classPrivateFieldGet(this, _LiveObject_instances, "m", _LiveObject_applyDeleteObjectKey).call(this, 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
*/
toObject() {
return Object.fromEntries(__classPrivateFieldGet(this, _LiveObject_map, "f"));
}
/**
* Adds or updates a property with a specified key and a value.
* @param key The key of the property to add
* @param value The value of the property to add
*/
set(key, value) {
// TODO: Find out why typescript complains
this.update({ [key]: value });
}
/**
* Returns a specified property from the LiveObject.
* @param key The key of the property to get
*/
get(key) {
return __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
}
/**
* Deletes a key from the LiveObject
* @param key The key of the property to delete
*/
delete(key) {
const keyAsString = key;
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(keyAsString);
if (oldValue === undefined) {
return;
}
if (this._doc == null || this._id == null) {
if (oldValue instanceof AbstractCrdt_1.AbstractCrdt) {
oldValue._detach();
}
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString);
return;
}
let reverse;
if (oldValue instanceof AbstractCrdt_1.AbstractCrdt) {
oldValue._detach();
reverse = oldValue._serialize(this._id, keyAsString);
}
else {
reverse = [
{
type: live_1.OpType.UpdateObject,
data: { [keyAsString]: oldValue },
id: this._id,
},
];
}
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(keyAsString);
this._doc.dispatch([
{
type: live_1.OpType.DeleteObjectKey,
key: keyAsString,
id: this._id,
opId: this._doc.generateOpId(),
},
], reverse, [this]);
}
/**
* Adds or updates multiple properties at once with an object.
* @param overrides The object used to overrides properties
*/
update(overrides) {
if (this._doc == null || this._id == null) {
for (const key in overrides) {
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
if (oldValue instanceof AbstractCrdt_1.AbstractCrdt) {
oldValue._detach();
}
const newValue = overrides[key];
if (newValue instanceof AbstractCrdt_1.AbstractCrdt) {
newValue._setParentLink(this, key);
}
__classPrivateFieldGet(this, _LiveObject_map, "f").set(key, newValue);
}
return;
}
const ops = [];
const reverseOps = [];
const opId = this._doc.generateOpId();
const updatedProps = {};
const reverseUpdateOp = {
id: this._id,
type: live_1.OpType.UpdateObject,
data: {},
};
for (const key in overrides) {
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, opId);
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
if (oldValue instanceof AbstractCrdt_1.AbstractCrdt) {
reverseOps.push(...oldValue._serialize(this._id, key));
oldValue._detach();
}
else if (oldValue === undefined) {
reverseOps.push({ type: live_1.OpType.DeleteObjectKey, id: this._id, key });
}
else {
reverseUpdateOp.data[key] = oldValue;
}
const newValue = overrides[key];
if (newValue instanceof AbstractCrdt_1.AbstractCrdt) {
newValue._setParentLink(this, key);
newValue._attach(this._doc.generateId(), this._doc);
ops.push(...newValue._serialize(this._id, key, this._doc));
}
else {
updatedProps[key] = newValue;
}
__classPrivateFieldGet(this, _LiveObject_map, "f").set(key, newValue);
}
if (Object.keys(reverseUpdateOp.data).length !== 0) {
reverseOps.unshift(reverseUpdateOp);
}
if (Object.keys(updatedProps).length !== 0) {
ops.unshift({
opId,
id: this._id,
type: live_1.OpType.UpdateObject,
data: updatedProps,
});
}
this._doc.dispatch(ops, reverseOps, [this]);
}
}
exports.LiveObject = LiveObject;
_LiveObject_map = new WeakMap(), _LiveObject_propToLastUpdate = new WeakMap(), _LiveObject_instances = new WeakSet(), _LiveObject_applyUpdate = function _LiveObject_applyUpdate(op, isLocal) {
let isModified = false;
const reverse = [];
const reverseUpdate = {
type: live_1.OpType.UpdateObject,
id: this._id,
data: {},
};
reverse.push(reverseUpdate);
for (const key in op.data) {
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
if (oldValue instanceof AbstractCrdt_1.AbstractCrdt) {
reverse.push(...oldValue._serialize(this._id, key));
oldValue._detach();
}
else if (oldValue !== undefined) {
reverseUpdate.data[key] = oldValue;
}
else if (oldValue === undefined) {
reverse.push({ type: live_1.OpType.DeleteObjectKey, id: this._id, key });
}
}
for (const key in op.data) {
if (isLocal) {
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").set(key, op.opId);
}
else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) == null) {
// Not modified localy so we apply update
isModified = true;
}
else if (__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").get(key) === op.opId) {
// Acknowlegment from local operation
__classPrivateFieldGet(this, _LiveObject_propToLastUpdate, "f").delete(key);
continue;
}
else {
// Conflict, ignore remote operation
continue;
}
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
if ((0, utils_1.isCrdt)(oldValue)) {
oldValue._detach();
}
isModified = true;
__classPrivateFieldGet(this, _LiveObject_map, "f").set(key, op.data[key]);
}
if (Object.keys(reverseUpdate.data).length !== 0) {
reverse.unshift(reverseUpdate);
}
return isModified ? { modified: this, reverse } : { modified: false };
}, _LiveObject_applyDeleteObjectKey = function _LiveObject_applyDeleteObjectKey(op) {
const key = op.key;
// If property does not exist, exit without notifying
if (__classPrivateFieldGet(this, _LiveObject_map, "f").has(key) === false) {
return { modified: false };
}
const oldValue = __classPrivateFieldGet(this, _LiveObject_map, "f").get(key);
let reverse = [];
if ((0, utils_1.isCrdt)(oldValue)) {
reverse = oldValue._serialize(this._id, op.key);
oldValue._detach();
}
else if (oldValue !== undefined) {
reverse = [
{
type: live_1.OpType.UpdateObject,
id: this._id,
data: { [key]: oldValue },
},
];
}
__classPrivateFieldGet(this, _LiveObject_map, "f").delete(key);
return { modified: this, reverse };
};