UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

191 lines (190 loc) 5.46 kB
import { ConNode } from '../const/ConNode'; import { CRDT_CONSTANTS } from '../../constants'; import { printTree } from 'tree-dump/lib/printTree'; import { compare, printTs } from '../../../json-crdt-patch/clock'; /** * Represents a `vec` JSON CRDT node, which is a LWW array. * * Vector is, usually a fixed length, last-write-wins array. Each element * in the array is a reference to another JSON CRDT node. The vector * can be extended by adding new elements to the end of the array. * * @category CRDT Node */ export class VecNode { doc; id; /** * @ignore */ elements = []; constructor( /** * @ignore */ doc, id) { this.doc = doc; this.id = id; } /** * Retrieves the ID of an element at the given index. * * @param index Index of the element to get. * @returns ID of the element at the given index, if any. */ val(index) { return this.elements[index]; } /** * Retrieves the JSON CRDT node at the given index. * * @param index Index of the element to get. * @returns JSON CRDT node at the given index, if any. */ get(index) { const id = this.elements[index]; if (!id) return undefined; return this.doc.index.get(id); } /** * @ignore */ put(index, id) { if (index > CRDT_CONSTANTS.MAX_TUPLE_LENGTH) throw new Error('OUT_OF_BOUNDS'); const currentId = this.val(index); if (currentId && compare(currentId, id) >= 0) return; if (index > this.elements.length) for (let i = this.elements.length; i < index; i++) this.elements.push(undefined); if (index < this.elements.length) this.elements[index] = id; else this.elements.push(id); return currentId; } // ----------------------------------------------------------------- extension /** * @ignore */ __extNode = undefined; /** * @ignore * @returns Returns the extension data node if this is an extension node, * otherwise `undefined`. The node is cached after the first access. */ ext() { if (this.__extNode) return this.__extNode; const extensionId = this.getExtId(); const isExtension = extensionId >= 0; if (!isExtension) return undefined; const extension = this.doc.ext.get(extensionId); if (!extension) return undefined; this.__extNode = new extension.Node(this.get(1)); return this.__extNode; } /** * @ignore */ isExt() { return !!this.ext(); } /** * @ignore * @returns Returns extension ID if this is an extension node, otherwise -1. */ getExtId() { if (this.elements.length !== 2) return -1; const type = this.get(0); if (!(type instanceof ConNode)) return -1; const buf = type.val; const id = this.id; if (!(buf instanceof Uint8Array) || buf.length !== 3 || buf[1] !== id.sid % 256 || buf[2] !== id.time % 256) return -1; return buf[0]; } /** ------------------------------------------------------ {@link JsonNode} */ /** * @ignore */ child() { return this.ext(); } /** * @ignore */ container() { return this; } /** * @ignore */ children(callback) { const elements = this.elements; const length = elements.length; const index = this.doc.index; for (let i = 0; i < length; i++) { const id = elements[i]; if (!id) continue; const node = index.get(id); if (node) callback(node); } } /** * @ignore */ _view = []; /** * @ignore */ view() { const extNode = this.ext(); if (extNode) return extNode.view(); let useCache = true; const _view = this._view; const arr = []; const index = this.doc.index; const elements = this.elements; const length = elements.length; for (let i = 0; i < length; i++) { const id = elements[i]; const node = id ? index.get(id) : undefined; const value = node ? node.view() : undefined; if (_view[i] !== value) useCache = false; arr.push(value); } return useCache ? _view : (this._view = arr); } /** * @ignore */ api = undefined; name() { return 'vec'; } /** ----------------------------------------------------- {@link Printable} */ toString(tab = '') { const extNode = this.ext(); const header = this.name() + ' ' + printTs(this.id) + (extNode ? ` { extension = ${this.getExtId()} }` : ''); if (extNode) { return this.child().toString(tab, this.id); } const index = this.doc.index; return (header + printTree(tab, [ ...this.elements.map((id, i) => (tab) => `${i}: ${!id ? 'nil' : index.get(id) ? index.get(id).toString(tab + ' ' + ' '.repeat(('' + i).length)) : 'nil'}`), ...(extNode ? [(tab) => `${this.child().toString(tab)}`] : []), ])); } }