UNPKG

@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

420 lines (419 loc) 18.3 kB
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 __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 _LiveList_items, _LiveListIterator_innerIterator; import { AbstractCrdt } from "./AbstractCrdt"; import { deserialize, selfOrRegister, selfOrRegisterValue } from "./utils"; import { OpType, CrdtType, } from "./live"; import { makePosition, compare } from "./position"; /** * The LiveList class represents an ordered collection of items that is synchorinized across clients. */ export class LiveList extends AbstractCrdt { constructor(items = []) { super(); // TODO: Naive array at first, find a better data structure. Maybe an Order statistics tree? _LiveList_items.set(this, []); let position = undefined; for (let i = 0; i < items.length; i++) { const newPosition = makePosition(position); const item = selfOrRegister(items[i]); __classPrivateFieldGet(this, _LiveList_items, "f").push([item, newPosition]); position = newPosition; } } /** * INTERNAL */ static _deserialize([id, item], parentToChildren, doc) { const list = new LiveList([]); list._attach(id, doc); const children = parentToChildren.get(id); if (children == null) { return list; } for (const entry of children) { const child = deserialize(entry, parentToChildren, doc); child._setParentLink(list, entry[1].parentKey); __classPrivateFieldGet(list, _LiveList_items, "f").push([child, entry[1].parentKey]); __classPrivateFieldGet(list, _LiveList_items, "f").sort((itemA, itemB) => compare(itemA[1], itemB[1])); } return list; } /** * INTERNAL */ _serialize(parentId, parentKey, doc) { if (this._id == null) { throw new Error("Cannot serialize item is not attached"); } if (parentId == null || parentKey == null) { throw new Error("Cannot serialize list if parentId or parentKey is undefined"); } const ops = []; const op = { id: this._id, opId: doc === null || doc === void 0 ? void 0 : doc.generateOpId(), type: OpType.CreateList, parentId, parentKey, }; ops.push(op); for (const [value, key] of __classPrivateFieldGet(this, _LiveList_items, "f")) { ops.push(...value._serialize(this._id, key, doc)); } return ops; } /** * INTERNAL */ _attach(id, doc) { super._attach(id, doc); for (const [item, position] of __classPrivateFieldGet(this, _LiveList_items, "f")) { item._attach(doc.generateId(), doc); } } /** * INTERNAL */ _detach() { super._detach(); for (const [value] of __classPrivateFieldGet(this, _LiveList_items, "f")) { value._detach(); } } /** * INTERNAL */ _attachChild(id, key, child, isLocal) { var _a; if (this._doc == null) { throw new Error("Can't attach child if doc is not present"); } child._attach(id, this._doc); child._setParentLink(this, key); const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key); let newKey = key; // If there is a conflict if (index !== -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, newKey]); __classPrivateFieldGet(this, _LiveList_items, "f").sort((itemA, itemB) => compare(itemA[1], itemB[1])); return { reverse: [{ type: OpType.DeleteCrdt, id }], modified: this }; } /** * INTERNAL */ _detachChild(child) { const indexToDelete = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((item) => item[0] === child); __classPrivateFieldGet(this, _LiveList_items, "f").splice(indexToDelete, 1); if (child) { child._detach(); } } /** * INTERNAL */ _setChildKey(key, child) { var _a; child._setParentLink(this, key); const index = __classPrivateFieldGet(this, _LiveList_items, "f").findIndex((entry) => entry[1] === key); // Assign a temporary position until we get the fix from the backend 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]); } const item = __classPrivateFieldGet(this, _LiveList_items, "f").find((item) => item[0] === child); if (item) { item[1] = key; } __classPrivateFieldGet(this, _LiveList_items, "f").sort((itemA, itemB) => compare(itemA[1], itemB[1])); } /** * INTERNAL */ _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. */ get length() { return __classPrivateFieldGet(this, _LiveList_items, "f").length; } /** * Adds one element to the end of the LiveList. * @param element The element to add to the end of the LiveList. */ push(element) { return this.insert(element, this.length); } /** * Inserts one element at a specified index. * @param element The element to insert. * @param index The index at which you want to insert the element. */ insert(element, index) { if (index < 0 || index > __classPrivateFieldGet(this, _LiveList_items, "f").length) { throw new Error(`Cannot delete list item at index "${index}". index should be between 0 and ${__classPrivateFieldGet(this, _LiveList_items, "f").length}`); } let before = __classPrivateFieldGet(this, _LiveList_items, "f")[index - 1] ? __classPrivateFieldGet(this, _LiveList_items, "f")[index - 1][1] : undefined; let after = __classPrivateFieldGet(this, _LiveList_items, "f")[index] ? __classPrivateFieldGet(this, _LiveList_items, "f")[index][1] : undefined; const position = makePosition(before, after); const value = selfOrRegister(element); value._setParentLink(this, position); __classPrivateFieldGet(this, _LiveList_items, "f").push([value, position]); __classPrivateFieldGet(this, _LiveList_items, "f").sort((itemA, itemB) => compare(itemA[1], itemB[1])); if (this._doc && this._id) { const id = this._doc.generateId(); value._attach(id, this._doc); this._doc.dispatch(value._serialize(this._id, position, this._doc), [{ type: OpType.DeleteCrdt, id }], [this]); } } /** * Move one element from one index to another. * @param index The index of the element to move * @param targetIndex The index where the element should be after moving. */ move(index, targetIndex) { if (targetIndex < 0) { throw new Error("targetIndex cannot be less than 0"); } if (targetIndex >= __classPrivateFieldGet(this, _LiveList_items, "f").length) { throw new Error("targetIndex cannot be greater or equal than the list length"); } if (index < 0) { throw new Error("index cannot be less than 0"); } if (index >= __classPrivateFieldGet(this, _LiveList_items, "f").length) { throw new Error("index cannot be greater or equal than the list length"); } let beforePosition = null; let afterPosition = null; if (index < targetIndex) { afterPosition = targetIndex === __classPrivateFieldGet(this, _LiveList_items, "f").length - 1 ? undefined : __classPrivateFieldGet(this, _LiveList_items, "f")[targetIndex + 1][1]; beforePosition = __classPrivateFieldGet(this, _LiveList_items, "f")[targetIndex][1]; } else { afterPosition = __classPrivateFieldGet(this, _LiveList_items, "f")[targetIndex][1]; beforePosition = targetIndex === 0 ? undefined : __classPrivateFieldGet(this, _LiveList_items, "f")[targetIndex - 1][1]; } const position = makePosition(beforePosition, afterPosition); const item = __classPrivateFieldGet(this, _LiveList_items, "f")[index]; const previousPosition = item[1]; item[1] = position; item[0]._setParentLink(this, position); __classPrivateFieldGet(this, _LiveList_items, "f").sort((itemA, itemB) => compare(itemA[1], itemB[1])); if (this._doc && this._id) { this._doc.dispatch([ { type: OpType.SetParentKey, id: item[0]._id, opId: this._doc.generateOpId(), parentKey: position, }, ], [ { type: OpType.SetParentKey, id: item[0]._id, parentKey: previousPosition, }, ], [this]); } } /** * Deletes an element at the specified index * @param index The index of the element to delete */ delete(index) { if (index < 0 || index >= __classPrivateFieldGet(this, _LiveList_items, "f").length) { throw new Error(`Cannot delete list item at index "${index}". index should be between 0 and ${__classPrivateFieldGet(this, _LiveList_items, "f").length - 1}`); } const item = __classPrivateFieldGet(this, _LiveList_items, "f")[index]; item[0]._detach(); __classPrivateFieldGet(this, _LiveList_items, "f").splice(index, 1); if (this._doc) { const childRecordId = item[0]._id; if (childRecordId) { this._doc.dispatch([ { id: childRecordId, opId: this._doc.generateOpId(), type: OpType.DeleteCrdt, }, ], item[0]._serialize(this._id, item[1]), [this]); } } } clear() { if (this._doc) { let ops = []; let reverseOps = []; for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) { item[0]._detach(); const childId = item[0]._id; if (childId) { ops.push({ id: childId, type: OpType.DeleteCrdt }); reverseOps.push(...item[0]._serialize(this._id, item[1])); } } __classPrivateFieldSet(this, _LiveList_items, [], "f"); this._doc.dispatch(ops, reverseOps, [this]); } else { for (const item of __classPrivateFieldGet(this, _LiveList_items, "f")) { item[0]._detach(); } __classPrivateFieldSet(this, _LiveList_items, [], "f"); } } /** * Returns an Array of all the elements in the LiveList. */ toArray() { return __classPrivateFieldGet(this, _LiveList_items, "f").map((entry) => selfOrRegisterValue(entry[0])); } /** * Tests whether all elements pass the test implemented by the provided function. * @param predicate Function to test for each element, taking two arguments (the element and its index). * @returns true if the predicate function returns a truthy value for every element. Otherwise, false. */ every(predicate) { return this.toArray().every(predicate); } /** * Creates an array with all elements that pass the test implemented by the provided function. * @param predicate Function to test each element of the LiveList. Return a value that coerces to true to keep the element, or to false otherwise. * @returns An array with the elements that pass the test. */ filter(predicate) { return this.toArray().filter(predicate); } /** * Returns the first element that satisfies the provided testing function. * @param predicate Function to execute on each value. * @returns The value of the first element in the LiveList that satisfies the provided testing function. Otherwise, undefined is returned. */ find(predicate) { return this.toArray().find(predicate); } /** * Returns the index of the first element in the LiveList that satisfies the provided testing function. * @param predicate Function to execute on each value until the function returns true, indicating that the satisfying element was found. * @returns The index of the first element in the LiveList that passes the test. Otherwise, -1. */ findIndex(predicate) { return this.toArray().findIndex(predicate); } /** * Executes a provided function once for each element. * @param callbackfn Function to execute on each element. */ forEach(callbackfn) { return this.toArray().forEach(callbackfn); } /** * Get the element at the specified index. * @param index The index on the element to get. * @returns The element at the specified index or undefined. */ get(index) { if (index < 0 || index >= __classPrivateFieldGet(this, _LiveList_items, "f").length) { return undefined; } return selfOrRegisterValue(__classPrivateFieldGet(this, _LiveList_items, "f")[index][0]); } /** * Returns the first index at which a given element can be found in the LiveList, or -1 if it is not present. * @param searchElement Element to locate. * @param fromIndex The index to start the search at. * @returns The first index of the element in the LiveList; -1 if not found. */ indexOf(searchElement, fromIndex) { return this.toArray().indexOf(searchElement, fromIndex); } /** * Returns the last index at which a given element can be found in the LiveList, or -1 if it is not present. The LiveLsit is searched backwards, starting at fromIndex. * @param searchElement Element to locate. * @param fromIndex The index at which to start searching backwards. * @returns */ lastIndexOf(searchElement, fromIndex) { return this.toArray().lastIndexOf(searchElement, fromIndex); } /** * Creates an array populated with the results of calling a provided function on every element. * @param callback Function that is called for every element. * @returns An array with each element being the result of the callback function. */ map(callback) { return __classPrivateFieldGet(this, _LiveList_items, "f").map((entry, i) => callback(selfOrRegisterValue(entry[0]), i)); } /** * Tests whether at least one element in the LiveList passes the test implemented by the provided function. * @param predicate Function to test for each element. * @returns true if the callback function returns a truthy value for at least one element. Otherwise, false. */ some(predicate) { return this.toArray().some(predicate); } [(_LiveList_items = new WeakMap(), Symbol.iterator)]() { return new LiveListIterator(__classPrivateFieldGet(this, _LiveList_items, "f")); } } class LiveListIterator { constructor(items) { _LiveListIterator_innerIterator.set(this, void 0); __classPrivateFieldSet(this, _LiveListIterator_innerIterator, items[Symbol.iterator](), "f"); } [(_LiveListIterator_innerIterator = new WeakMap(), Symbol.iterator)]() { return this; } next() { const result = __classPrivateFieldGet(this, _LiveListIterator_innerIterator, "f").next(); if (result.done) { return { done: true, value: undefined, }; } return { value: selfOrRegisterValue(result.value[0]), }; } }