UNPKG

loro-crdt

Version:

Loro CRDTs is a high-performance CRDT framework that makes your app state synchronized, collaborative and maintainable effortlessly.

319 lines (315 loc) 9.25 kB
'use strict'; var loroWasm = require("./loro_wasm"); /** * @deprecated Please use LoroDoc */ class Loro extends loroWasm.LoroDoc { } const CONTAINER_TYPES = [ "Map", "Text", "List", "Tree", "MovableList", "Counter", ]; function isContainerId(s) { return s.startsWith("cid:"); } /** Whether the value is a container. * * # Example * * ```ts * const doc = new LoroDoc(); * const map = doc.getMap("map"); * const list = doc.getList("list"); * const text = doc.getText("text"); * isContainer(map); // true * isContainer(list); // true * isContainer(text); // true * isContainer(123); // false * isContainer("123"); // false * isContainer({}); // false * ``` */ function isContainer(value) { if (typeof value !== "object" || value == null) { return false; } const p = Object.getPrototypeOf(value); if (p == null || typeof p !== "object" || typeof p["kind"] !== "function") { return false; } return CONTAINER_TYPES.includes(value.kind()); } /** Get the type of a value that may be a container. * * # Example * * ```ts * const doc = new LoroDoc(); * const map = doc.getMap("map"); * const list = doc.getList("list"); * const text = doc.getText("text"); * getType(map); // "Map" * getType(list); // "List" * getType(text); // "Text" * getType(123); // "Json" * getType("123"); // "Json" * getType({}); // "Json" * ``` */ function getType(value) { if (isContainer(value)) { return value.kind(); } return "Json"; } function newContainerID(id, type) { return `cid:${id.counter}@${id.peer}:${type}`; } function newRootContainerID(name, type) { return `cid:root-${name}:${type}`; } /** * @deprecated Please use `EphemeralStore` instead. * * Awareness is a structure that allows to track the ephemeral state of the peers. * * If we don't receive a state update from a peer within the timeout, we will remove their state. * The timeout is in milliseconds. This can be used to handle the offline state of a peer. */ class Awareness { constructor(peer, timeout = 30000) { this.listeners = new Set(); this.inner = new loroWasm.AwarenessWasm(peer, timeout); this.peer = peer; this.timeout = timeout; } apply(bytes, origin = "remote") { const { updated, added } = this.inner.apply(bytes); this.listeners.forEach((listener) => { listener({ updated, added, removed: [] }, origin); }); this.startTimerIfNotEmpty(); } setLocalState(state) { const wasEmpty = this.inner.getState(this.peer) == null; this.inner.setLocalState(state); if (wasEmpty) { this.listeners.forEach((listener) => { listener({ updated: [], added: [this.inner.peer()], removed: [] }, "local"); }); } else { this.listeners.forEach((listener) => { listener({ updated: [this.inner.peer()], added: [], removed: [] }, "local"); }); } this.startTimerIfNotEmpty(); } getLocalState() { return this.inner.getState(this.peer); } getAllStates() { return this.inner.getAllStates(); } encode(peers) { return this.inner.encode(peers); } encodeAll() { return this.inner.encodeAll(); } addListener(listener) { this.listeners.add(listener); } removeListener(listener) { this.listeners.delete(listener); } peers() { return this.inner.peers(); } destroy() { clearInterval(this.timer); this.listeners.clear(); } startTimerIfNotEmpty() { if (this.inner.isEmpty() || this.timer != null) { return; } this.timer = setInterval(() => { const removed = this.inner.removeOutdated(); if (removed.length > 0) { this.listeners.forEach((listener) => { listener({ updated: [], added: [], removed }, "timeout"); }); } if (this.inner.isEmpty()) { clearInterval(this.timer); this.timer = undefined; } }, this.timeout / 2); } } /** * EphemeralStore is a structure that allows to track the ephemeral state of the peers. * * It can be used to synchronize cursor positions, selections, and the names of the peers. * Each entry uses timestamp-based LWW (Last-Write-Wins) for conflict resolution. * * If we don't receive a state update from a peer within the timeout, we will remove their state. * The timeout is in milliseconds. This can be used to handle the offline state of a peer. * * @example * * ```ts * const store = new EphemeralStore(); * const store2 = new EphemeralStore(); * // Subscribe to local updates * store.subscribeLocalUpdates((data)=>{ * store2.apply(data); * }) * // Subscribe to all updates * store2.subscribe((event)=>{ * console.log("event: ", event); * }) * // Set a value * store.set("key", "value"); * // Encode the value * const encoded = store.encode("key"); * // Apply the encoded value * store2.apply(encoded); * ``` */ class EphemeralStore { constructor(timeout = 30000) { this.inner = new loroWasm.EphemeralStoreWasm(timeout); this.timeout = timeout; } apply(bytes) { this.inner.apply(bytes); this.startTimerIfNotEmpty(); } set(key, value) { this.inner.set(key, value); this.startTimerIfNotEmpty(); } delete(key) { this.inner.delete(key); } get(key) { return this.inner.get(key); } getAllStates() { return this.inner.getAllStates(); } encode(key) { return this.inner.encode(key); } encodeAll() { return this.inner.encodeAll(); } keys() { return this.inner.keys(); } destroy() { clearInterval(this.timer); } subscribe(listener) { return this.inner.subscribe(listener); } subscribeLocalUpdates(listener) { return this.inner.subscribeLocalUpdates(listener); } startTimerIfNotEmpty() { if (this.inner.isEmpty() || this.timer != null) { return; } this.timer = setInterval(() => { this.inner.removeOutdated(); if (this.inner.isEmpty()) { clearInterval(this.timer); this.timer = undefined; } }, this.timeout / 2); } } loroWasm.LoroDoc.prototype.toJsonWithReplacer = function (replacer) { const processed = new Set(); const doc = this; const m = (key, value) => { if (typeof value === "string") { if (isContainerId(value) && !processed.has(value)) { processed.add(value); const container = doc.getContainerById(value); if (container == null) { throw new Error(`ContainerID not found: ${value}`); } const ans = replacer(key, container); if (ans === container) { const ans = container.getShallowValue(); if (typeof ans === "object") { return run(ans); } return ans; } if (isContainer(ans)) { throw new Error("Using new container is not allowed in toJsonWithReplacer"); } if (typeof ans === "object" && ans != null) { return run(ans); } return ans; } } if (typeof value === "object" && value != null) { return run(value); } const ans = replacer(key, value); if (isContainer(ans)) { throw new Error("Using new container is not allowed in toJsonWithReplacer"); } return ans; }; const run = (layer) => { if (Array.isArray(layer)) { return layer.map((item, index) => { return m(index, item); }).filter((item) => item !== undefined); } const result = {}; for (const [key, value] of Object.entries(layer)) { const ans = m(key, value); if (ans !== undefined) { result[key] = ans; } } return result; }; const layer = doc.getShallowValue(); return run(layer); }; function idStrToId(idStr) { const [counter, peer] = idStr.split("@"); return { counter: parseInt(counter), peer: peer, }; } exports.Awareness = Awareness; exports.EphemeralStore = EphemeralStore; exports.Loro = Loro; exports.getType = getType; exports.idStrToId = idStrToId; exports.isContainer = isContainer; exports.isContainerId = isContainerId; exports.newContainerID = newContainerID; exports.newRootContainerID = newRootContainerID; Object.keys(loroWasm).forEach(function (k) { if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, { enumerable: true, get: function () { return loroWasm[k]; } }); }); //# sourceMappingURL=index.js.map