UNPKG

loro-wasm

Version:

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

1,830 lines (1,794 loc) 75.3 kB
/* tslint:disable */ /* eslint-disable */ /** */ export function run(): void; /** * @param {({ peer: PeerID, counter: number })[]} frontiers * @returns {Uint8Array} */ export function encodeFrontiers(frontiers: ({ peer: PeerID, counter: number })[]): Uint8Array; /** * @param {Uint8Array} bytes * @returns {{ peer: PeerID, counter: number }[]} */ export function decodeFrontiers(bytes: Uint8Array): { peer: PeerID, counter: number }[]; /** * Enable debug info of Loro */ export function setDebug(): void; /** * Decode the metadata of the import blob. * * This method is useful to get the following metadata of the import blob: * * - startVersionVector * - endVersionVector * - startTimestamp * - endTimestamp * - isSnapshot * - changeNum * @param {Uint8Array} blob * @returns {ImportBlobMetadata} */ export function decodeImportBlobMeta(blob: Uint8Array): ImportBlobMetadata; /** * Container types supported by loro. * * It is most commonly used to specify the type of sub-container to be created. * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * const containerType = "Text"; * const text = list.insertContainer(1, containerType); * ``` */ export type ContainerType = "Text" | "Map" | "List"| "Tree" | "MovableList"; export type PeerID = `${number}`; /** * The unique id of each container. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * const containerId = list.id; * ``` */ export type ContainerID = | `cid:root-${string}:${ContainerType}` | `cid:${number}@${PeerID}:${ContainerType}`; /** * The unique id of each tree node. */ export type TreeID = `${number}@${PeerID}`; interface LoroDoc { /** * Export updates from the specific version to the current version * * @deprecated Use `export({mode: "update", from: version})` instead * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "Hello"); * // get all updates of the doc * const updates = doc.exportFrom(); * const version = doc.oplogVersion(); * text.insert(5, " World"); * // get updates from specific version to the latest version * const updates2 = doc.exportFrom(version); * ``` */ exportFrom(version?: VersionVector): Uint8Array; /** * * Get the container corresponding to the container id * * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * let text = doc.getText("text"); * const textId = text.id; * text = doc.getContainerById(textId); * ``` */ getContainerById(id: ContainerID): Container; /** * Subscribe to updates from local edits. * * This method allows you to listen for local changes made to the document. * It's useful for syncing changes with other instances or saving updates. * * @param f - A callback function that receives a Uint8Array containing the update data. * @returns A function to unsubscribe from the updates. * * @example * ```ts * const loro = new Loro(); * const text = loro.getText("text"); * * const unsubscribe = loro.subscribeLocalUpdates((update) => { * console.log("Local update received:", update); * // You can send this update to other Loro instances * }); * * text.insert(0, "Hello"); * loro.commit(); * * // Later, when you want to stop listening: * unsubscribe(); * ``` * * @example * ```ts * const loro1 = new Loro(); * const loro2 = new Loro(); * * // Set up two-way sync * loro1.subscribeLocalUpdates((updates) => { * loro2.import(updates); * }); * * loro2.subscribeLocalUpdates((updates) => { * loro1.import(updates); * }); * * // Now changes in loro1 will be reflected in loro2 and vice versa * ``` */ subscribeLocalUpdates(f: (bytes: Uint8Array) => void): () => void } /** * Represents a `Delta` type which is a union of different operations that can be performed. * * @typeparam T - The data type for the `insert` operation. * * The `Delta` type can be one of three distinct shapes: * * 1. Insert Operation: * - `insert`: The item to be inserted, of type T. * - `attributes`: (Optional) A dictionary of attributes, describing styles in richtext * * 2. Delete Operation: * - `delete`: The number of elements to delete. * * 3. Retain Operation: * - `retain`: The number of elements to retain. * - `attributes`: (Optional) A dictionary of attributes, describing styles in richtext */ export type Delta<T> = | { insert: T; attributes?: { [key in string]: {} }; retain?: undefined; delete?: undefined; } | { delete: number; attributes?: undefined; retain?: undefined; insert?: undefined; } | { retain: number; attributes?: { [key in string]: {} }; delete?: undefined; insert?: undefined; }; /** * The unique id of each operation. */ export type OpId = { peer: PeerID, counter: number }; /** * Change is a group of continuous operations */ export interface Change { peer: PeerID, counter: number, lamport: number, length: number, /** * The timestamp in seconds. * * [Unix time](https://en.wikipedia.org/wiki/Unix_time) * It is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970. */ timestamp: number, deps: OpId[], message: string | undefined, } /** * Data types supported by loro */ export type Value = | ContainerID | string | number | boolean | null | { [key: string]: Value } | Uint8Array | Value[]; export type UndoConfig = { mergeInterval?: number, maxUndoSteps?: number, excludeOriginPrefixes?: string[], onPush?: (isUndo: boolean, counterRange: { start: number, end: number }) => { value: Value, cursors: Cursor[] }, onPop?: (isUndo: boolean, value: { value: Value, cursors: Cursor[] }, counterRange: { start: number, end: number }) => void }; export type Container = LoroList | LoroMap | LoroText | LoroTree | LoroMovableList; export interface ImportBlobMetadata { /** * The version vector of the start of the import. * * Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`. * However, it does not constitute a complete version vector, as it only contains counters * from peers included within the import blob. */ partialStartVersionVector: VersionVector; /** * The version vector of the end of the import. * * Import blob includes all the ops from `partial_start_vv` to `partial_end_vv`. * However, it does not constitute a complete version vector, as it only contains counters * from peers included within the import blob. */ partialEndVersionVector: VersionVector; startFrontiers: OpId[], startTimestamp: number; endTimestamp: number; isSnapshot: boolean; changeNum: number; } interface LoroText { /** * Get the cursor position at the given pos. * * When expressing the position of a cursor, using "index" can be unstable * because the cursor's position may change due to other deletions and insertions, * requiring updates with each edit. To stably represent a position or range within * a list structure, we can utilize the ID of each item/character on List CRDT or * Text CRDT for expression. * * Loro optimizes State metadata by not storing the IDs of deleted elements. This * approach complicates tracking cursors since they rely on these IDs. The solution * recalculates position by replaying relevant history to update cursors * accurately. To minimize the performance impact of history replay, the system * updates cursor info to reference only the IDs of currently present elements, * thereby reducing the need for replay. * * @example * ```ts * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "123"); * const pos0 = text.getCursor(0, 0); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(0); * } * text.insert(0, "1"); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(1); * } * ``` */ getCursor(pos: number, side?: Side): Cursor | undefined; } interface LoroList { /** * Get the cursor position at the given pos. * * When expressing the position of a cursor, using "index" can be unstable * because the cursor's position may change due to other deletions and insertions, * requiring updates with each edit. To stably represent a position or range within * a list structure, we can utilize the ID of each item/character on List CRDT or * Text CRDT for expression. * * Loro optimizes State metadata by not storing the IDs of deleted elements. This * approach complicates tracking cursors since they rely on these IDs. The solution * recalculates position by replaying relevant history to update cursors * accurately. To minimize the performance impact of history replay, the system * updates cursor info to reference only the IDs of currently present elements, * thereby reducing the need for replay. * * @example * ```ts * * const doc = new LoroDoc(); * const text = doc.getList("list"); * text.insert(0, "1"); * const pos0 = text.getCursor(0, 0); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(0); * } * text.insert(0, "1"); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(1); * } * ``` */ getCursor(pos: number, side?: Side): Cursor | undefined; } export type TreeNodeValue = { id: TreeID, parent: TreeID | undefined, index: number, fractionalIndex: string, meta: LoroMap, children: TreeNodeValue[], } interface LoroTree{ toArray(): TreeNodeValue[]; getNodes(options?: { withDeleted: boolean = false }): LoroTreeNode[]; } interface LoroMovableList { /** * Get the cursor position at the given pos. * * When expressing the position of a cursor, using "index" can be unstable * because the cursor's position may change due to other deletions and insertions, * requiring updates with each edit. To stably represent a position or range within * a list structure, we can utilize the ID of each item/character on List CRDT or * Text CRDT for expression. * * Loro optimizes State metadata by not storing the IDs of deleted elements. This * approach complicates tracking cursors since they rely on these IDs. The solution * recalculates position by replaying relevant history to update cursors * accurately. To minimize the performance impact of history replay, the system * updates cursor info to reference only the IDs of currently present elements, * thereby reducing the need for replay. * * @example * ```ts * * const doc = new LoroDoc(); * const text = doc.getMovableList("text"); * text.insert(0, "1"); * const pos0 = text.getCursor(0, 0); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(0); * } * text.insert(0, "1"); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(1); * } * ``` */ getCursor(pos: number, side?: Side): Cursor | undefined; } export type Side = -1 | 0 | 1; export type JsonOpID = `${number}@${PeerID}`; export type JsonContainerID = `🦜:${ContainerID}` ; export type JsonValue = | JsonContainerID | string | number | boolean | null | { [key: string]: JsonValue } | Uint8Array | JsonValue[]; export type JsonSchema = { schema_version: number; start_version: Map<string, number>, peers: PeerID[], changes: JsonChange[] }; export type JsonChange = { id: JsonOpID /** * The timestamp in seconds. * * [Unix time](https://en.wikipedia.org/wiki/Unix_time) * It is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970. */ timestamp: number, deps: JsonOpID[], lamport: number, msg: string | null, ops: JsonOp[] } export type ExportMode = { mode: "update", from?: VersionVector, } | { mode: "snapshot", } | { mode: "shallow-snapshot", frontiers: Frontiers, } | { mode: "updates-in-range", spans: { id: ID, len: number, }[], }; export type JsonOp = { container: ContainerID, counter: number, content: ListOp | TextOp | MapOp | TreeOp | MovableListOp | UnknownOp } export type ListOp = { type: "insert", pos: number, value: JsonValue } | { type: "delete", pos: number, len: number, start_id: JsonOpID, }; export type MovableListOp = { type: "insert", pos: number, value: JsonValue } | { type: "delete", pos: number, len: number, start_id: JsonOpID, }| { type: "move", from: number, to: number, elem_id: JsonOpID, }|{ type: "set", elem_id: JsonOpID, value: JsonValue } export type TextOp = { type: "insert", pos: number, text: string } | { type: "delete", pos: number, len: number, start_id: JsonOpID, } | { type: "mark", start: number, end: number, style_key: string, style_value: JsonValue, info: number }|{ type: "mark_end" }; export type MapOp = { type: "insert", key: string, value: JsonValue } | { type: "delete", key: string, }; export type TreeOp = { type: "create", target: TreeID, parent: TreeID | undefined, fractional_index: string }|{ type: "move", target: TreeID, parent: TreeID | undefined, fractional_index: string }|{ type: "delete", target: TreeID }; export type UnknownOp = { type: "unknown" prop: number, value_type: "unknown", value: { kind: number, data: Uint8Array } }; export type CounterSpan = { start: number, end: number }; export type ImportStatus = { success: Map<PeerID, CounterSpan>, pending: Map<PeerID, CounterSpan> | null } /** * `Awareness` is a structure that tracks the ephemeral state of peers. * * It can be used to synchronize cursor positions, selections, and the names of the peers. * * The state of a specific peer is expected to be removed after a specified timeout. Use * `remove_outdated` to eliminate outdated states. */ export class AwarenessWasm { free(): void; /** * Creates a new `Awareness` instance. * * The `timeout` parameter specifies the duration in milliseconds. * A state of a peer is considered outdated, if the last update of the state of the peer * is older than the `timeout`. * @param {number | bigint | `${number}`} peer * @param {number} timeout */ constructor(peer: number | bigint | `${number}`, timeout: number); /** * Encodes the state of the given peers. * @param {Array<any>} peers * @returns {Uint8Array} */ encode(peers: Array<any>): Uint8Array; /** * Encodes the state of all peers. * @returns {Uint8Array} */ encodeAll(): Uint8Array; /** * Applies the encoded state of peers. * * Each peer's deletion countdown will be reset upon update, requiring them to pass through the `timeout` * interval again before being eligible for deletion. * @param {Uint8Array} encoded_peers_info * @returns {{ updated: PeerID[], added: PeerID[] }} */ apply(encoded_peers_info: Uint8Array): { updated: PeerID[], added: PeerID[] }; /** * Get the PeerID of the local peer. * @returns {PeerID} */ peer(): PeerID; /** * Get the timestamp of the state of a given peer. * @param {number | bigint | `${number}`} peer * @returns {number | undefined} */ getTimestamp(peer: number | bigint | `${number}`): number | undefined; /** * Remove the states of outdated peers. * @returns {(PeerID)[]} */ removeOutdated(): (PeerID)[]; /** * Get the number of peers. * @returns {number} */ length(): number; /** * If the state is empty. * @returns {boolean} */ isEmpty(): boolean; /** * Get all the peers * @returns {(PeerID)[]} */ peers(): (PeerID)[]; } /** * Cursor is a stable position representation in the doc. * When expressing the position of a cursor, using "index" can be unstable * because the cursor's position may change due to other deletions and insertions, * requiring updates with each edit. To stably represent a position or range within * a list structure, we can utilize the ID of each item/character on List CRDT or * Text CRDT for expression. * * Loro optimizes State metadata by not storing the IDs of deleted elements. This * approach complicates tracking cursors since they rely on these IDs. The solution * recalculates position by replaying relevant history to update cursors * accurately. To minimize the performance impact of history replay, the system * updates cursor info to reference only the IDs of currently present elements, * thereby reducing the need for replay. * * @example * ```ts * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "123"); * const pos0 = text.getCursor(0, 0); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(0); * } * text.insert(0, "1"); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(1); * } * ``` */ export class Cursor { free(): void; /** * Get the id of the given container. * @returns {ContainerID} */ containerId(): ContainerID; /** * Get the ID that represents the position. * * It can be undefined if it's not bind into a specific ID. * @returns {{ peer: PeerID, counter: number } | undefined} */ pos(): { peer: PeerID, counter: number } | undefined; /** * Get which side of the character/list item the cursor is on. * @returns {Side} */ side(): Side; /** * Encode the cursor into a Uint8Array. * @returns {Uint8Array} */ encode(): Uint8Array; /** * Decode the cursor from a Uint8Array. * @param {Uint8Array} data * @returns {Cursor} */ static decode(data: Uint8Array): Cursor; /** * "Cursor" * @returns {any} */ kind(): any; } /** * The handler of a tree(forest) container. */ export class LoroCounter { free(): void; /** * Create a new LoroCounter. */ constructor(); /** * Increment the counter by the given value. * @param {number} value */ increment(value: number): void; /** * Decrement the counter by the given value. * @param {number} value */ decrement(value: number): void; /** * Subscribe to the changes of the counter. * @param {Function} f * @returns {any} */ subscribe(f: Function): any; /** * Get the parent container of the counter container. * * - The parent container of the root counter is `undefined`. * - The object returned is a new js object each time because it need to cross * the WASM boundary. * @returns {Container | undefined} */ parent(): Container | undefined; /** * Whether the container is attached to a docuemnt. * * If it's detached, the operations on the container will not be persisted. * @returns {boolean} */ isAttached(): boolean; /** * Get the attached container associated with this. * * Returns an attached `Container` that equals to this or created by this, otherwise `undefined`. * @returns {LoroTree | undefined} */ getAttached(): LoroTree | undefined; /** * Get the value of the counter. */ readonly value: number; } /** * The CRDTs document. Loro supports different CRDTs include [**List**](LoroList), * [**RichText**](LoroText), [**Map**](LoroMap) and [**Movable Tree**](LoroTree), * you could build all kind of applications by these. * * @example * ```ts * import { LoroDoc } import "loro-crdt" * * const loro = new LoroDoc(); * const text = loro.getText("text"); * const list = loro.getList("list"); * const map = loro.getMap("Map"); * const tree = loro.getTree("tree"); * ``` */ export class LoroDoc { free(): void; /** * Create a new loro document. * * New document will have random peer id. */ constructor(); /** * Enables editing in detached mode, which is disabled by default. * * The doc enter detached mode after calling `detach` or checking out a non-latest version. * * # Important Notes: * * - This mode uses a different PeerID for each checkout. * - Ensure no concurrent operations share the same PeerID if set manually. * - Importing does not affect the document's state or version; changes are * recorded in the [OpLog] only. Call `checkout` to apply changes. * @param {boolean} enable */ setDetachedEditing(enable: boolean): void; /** * Whether the editing is enabled in detached mode. * * The doc enter detached mode after calling `detach` or checking out a non-latest version. * * # Important Notes: * * - This mode uses a different PeerID for each checkout. * - Ensure no concurrent operations share the same PeerID if set manually. * - Importing does not affect the document's state or version; changes are * recorded in the [OpLog] only. Call `checkout` to apply changes. * @returns {boolean} */ isDetachedEditingEnabled(): boolean; /** * Set whether to record the timestamp of each change. Default is `false`. * * If enabled, the Unix timestamp will be recorded for each change automatically. * * You can also set each timestamp manually when you commit a change. * The timestamp manually set will override the automatic one. * * NOTE: Timestamps are forced to be in ascending order. * If you commit a new change with a timestamp that is less than the existing one, * the largest existing timestamp will be used instead. * @param {boolean} auto_record */ setRecordTimestamp(auto_record: boolean): void; /** * If two continuous local changes are within the interval, they will be merged into one change. * * The default value is 1_000_000, the default unit is milliseconds. * @param {number} interval */ setChangeMergeInterval(interval: number): void; /** * Set the rich text format configuration of the document. * * You need to config it if you use rich text `mark` method. * Specifically, you need to config the `expand` property of each style. * * Expand is used to specify the behavior of expanding when new text is inserted at the * beginning or end of the style. * * You can specify the `expand` option to set the behavior when inserting text at the boundary of the range. * * - `after`(default): when inserting text right after the given range, the mark will be expanded to include the inserted text * - `before`: when inserting text right before the given range, the mark will be expanded to include the inserted text * - `none`: the mark will not be expanded to include the inserted text at the boundaries * - `both`: when inserting text either right before or right after the given range, the mark will be expanded to include the inserted text * * @example * ```ts * const doc = new LoroDoc(); * doc.configTextStyle({ * bold: { expand: "after" }, * link: { expand: "before" } * }); * const text = doc.getText("text"); * text.insert(0, "Hello World!"); * text.mark({ start: 0, end: 5 }, "bold", true); * expect(text.toDelta()).toStrictEqual([ * { * insert: "Hello", * attributes: { * bold: true, * }, * }, * { * insert: " World!", * }, * ] as Delta<string>[]); * ``` * @param {{[key: string]: { expand: 'before'|'after'|'none'|'both' }}} styles */ configTextStyle(styles: {[key: string]: { expand: 'before'|'after'|'none'|'both' }}): void; /** * Get a loro document from the snapshot. * * @see You can check out what is the snapshot [here](#). * * @example * ```ts * import { LoroDoc } import "loro-crdt" * * const bytes = /* The bytes encoded from other loro document *\/; * const loro = LoroDoc.fromSnapshot(bytes); * ``` * @param {Uint8Array} snapshot * @returns {LoroDoc} */ static fromSnapshot(snapshot: Uint8Array): LoroDoc; /** * Attach the document state to the latest known version. * * > The document becomes detached during a `checkout` operation. * > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`. * > In a detached state, the document is not editable, and any `import` operations will be * > recorded in the `OpLog` without being applied to the `DocState`. * * This method has the same effect as invoking `checkout_to_latest`. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * const frontiers = doc.frontiers(); * text.insert(0, "Hello World!"); * loro.checkout(frontiers); * // you need call `attach()` or `checkoutToLatest()` before changing the doc. * loro.attach(); * text.insert(0, "Hi"); * ``` */ attach(): void; /** * `detached` indicates that the `DocState` is not synchronized with the latest version of `OpLog`. * * > The document becomes detached during a `checkout` operation. * > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`. * > In a detached state, the document is not editable, and any `import` operations will be * > recorded in the `OpLog` without being applied to the `DocState`. * * When `detached`, the document is not editable. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * const frontiers = doc.frontiers(); * text.insert(0, "Hello World!"); * console.log(doc.is_detached()); // false * loro.checkout(frontiers); * console.log(doc.is_detached()); // true * loro.attach(); * console.log(doc.is_detached()); // false * ``` * @returns {boolean} */ isDetached(): boolean; /** * Detach the document state from the latest known version. * * After detaching, all import operations will be recorded in the `OpLog` without being applied to the `DocState`. * When `detached`, the document is not editable. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * doc.detach(); * console.log(doc.is_detached()); // true * ``` */ detach(): void; /** * Duplicate the document with a different PeerID * * The time complexity and space complexity of this operation are both O(n), * @returns {LoroDoc} */ fork(): LoroDoc; /** * Creates a new LoroDoc at a specified version (Frontiers) * @param {({ peer: PeerID, counter: number })[]} frontiers * @returns {LoroDoc} */ forkAt(frontiers: ({ peer: PeerID, counter: number })[]): LoroDoc; /** * Checkout the `DocState` to the latest version of `OpLog`. * * > The document becomes detached during a `checkout` operation. * > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`. * > In a detached state, the document is not editable, and any `import` operations will be * > recorded in the `OpLog` without being applied to the `DocState`. * * This has the same effect as `attach`. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * const frontiers = doc.frontiers(); * text.insert(0, "Hello World!"); * loro.checkout(frontiers); * // you need call `checkoutToLatest()` or `attach()` before changing the doc. * loro.checkoutToLatest(); * text.insert(0, "Hi"); * ``` */ checkoutToLatest(): void; /** * @param {({ peer: PeerID, counter: number })[]} ids * @param {Function} f */ travelChangeAncestors(ids: ({ peer: PeerID, counter: number })[], f: Function): void; /** * Checkout the `DocState` to a specific version. * * > The document becomes detached during a `checkout` operation. * > Being `detached` implies that the `DocState` is not synchronized with the latest version of the `OpLog`. * > In a detached state, the document is not editable, and any `import` operations will be * > recorded in the `OpLog` without being applied to the `DocState`. * * You should call `attach` to attach the `DocState` to the latest version of `OpLog`. * * @param frontiers - the specific frontiers * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * const frontiers = doc.frontiers(); * text.insert(0, "Hello World!"); * loro.checkout(frontiers); * console.log(doc.toJSON()); // {"text": ""} * ``` * @param {({ peer: PeerID, counter: number })[]} frontiers */ checkout(frontiers: ({ peer: PeerID, counter: number })[]): void; /** * Set the peer ID of the current writer. * * It must be a number, a BigInt, or a decimal string that can be parsed to a unsigned 64-bit integer. * * Note: use it with caution. You need to make sure there is not chance that two peers * have the same peer ID. Otherwise, we cannot ensure the consistency of the document. * @param {number | bigint | `${number}`} peer_id */ setPeerId(peer_id: number | bigint | `${number}`): void; /** * Commit the cumulative auto committed transaction. * * You can specify the `origin`, `timestamp`, and `message` of the commit. * * The `origin` is used to mark the event, and the `message` works like a git commit message. * * The events will be emitted after a transaction is committed. A transaction is committed when: * * - `doc.commit()` is called. * - `doc.export(mode)` is called. * - `doc.import(data)` is called. * - `doc.checkout(version)` is called. * * NOTE: Timestamps are forced to be in ascending order. * If you commit a new change with a timestamp that is less than the existing one, * the largest existing timestamp will be used instead. * * NOTE: The `origin` will not be persisted, but the `message` will. * @param {{ origin?: string, timestamp?: number, message?: string } | undefined} [options] */ commit(options?: { origin?: string, timestamp?: number, message?: string }): void; /** * Get the number of operations in the pending transaction. * * The pending transaction is the one that is not committed yet. It will be committed * automatically after calling `doc.commit()`, `doc.export(mode)` or `doc.checkout(version)`. * @returns {number} */ getPendingTxnLength(): number; /** * Get a LoroText by container id. * * The object returned is a new js object each time because it need to cross * the WASM boundary. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * ``` * @param {ContainerID | string} cid * @returns {LoroText} */ getText(cid: ContainerID | string): LoroText; /** * Get a LoroCounter by container id * @param {ContainerID | string} cid * @returns {LoroCounter} */ getCounter(cid: ContainerID | string): LoroCounter; /** * Set the commit message of the next commit * @param {string} msg */ setNextCommitMessage(msg: string): void; /** * Get deep value of the document with container id * @returns {any} */ getDeepValueWithID(): any; /** * Get the path from the root to the container * @param {ContainerID} id * @returns {Array<any> | undefined} */ getPathToContainer(id: ContainerID): Array<any> | undefined; /** * Evaluate JSONPath against a LoroDoc * @param {string} jsonpath * @returns {Array<any>} */ JSONPath(jsonpath: string): Array<any>; /** * Get the encoded version vector of the current document. * * If you checkout to a specific version, the version vector will change. * @returns {VersionVector} */ version(): VersionVector; /** * The doc only contains the history since this version * * This is empty if the doc is not shallow. * * The ops included by the shallow history start version vector are not in the doc. * @returns {VersionVector} */ shallowSinceVV(): VersionVector; /** * Check if the doc contains the full history. * @returns {boolean} */ isShallow(): boolean; /** * The doc only contains the history since this version * * This is empty if the doc is not shallow. * * The ops included by the shallow history start frontiers are not in the doc. * @returns {{ peer: PeerID, counter: number }[]} */ shallowSinceFrontiers(): { peer: PeerID, counter: number }[]; /** * Get the encoded version vector of the latest version in OpLog. * * If you checkout to a specific version, the version vector will not change. * @returns {VersionVector} */ oplogVersion(): VersionVector; /** * Get the frontiers of the current document. * * If you checkout to a specific version, this value will change. * @returns {{ peer: PeerID, counter: number }[]} */ frontiers(): { peer: PeerID, counter: number }[]; /** * Get the frontiers of the latest version in OpLog. * * If you checkout to a specific version, this value will not change. * @returns {{ peer: PeerID, counter: number }[]} */ oplogFrontiers(): { peer: PeerID, counter: number }[]; /** * Compare the version of the OpLog with the specified frontiers. * * This method is useful to compare the version by only a small amount of data. * * This method returns an integer indicating the relationship between the version of the OpLog (referred to as 'self') * and the provided 'frontiers' parameter: * * - -1: The version of 'self' is either less than 'frontiers' or is non-comparable (parallel) to 'frontiers', * indicating that it is not definitively less than 'frontiers'. * - 0: The version of 'self' is equal to 'frontiers'. * - 1: The version of 'self' is greater than 'frontiers'. * * # Internal * * Frontiers cannot be compared without the history of the OpLog. * @param {({ peer: PeerID, counter: number })[]} frontiers * @returns {number} */ cmpWithFrontiers(frontiers: ({ peer: PeerID, counter: number })[]): number; /** * Compare the ordering of two Frontiers. * * It's assumed that both Frontiers are included by the doc. Otherwise, an error will be thrown. * * Return value: * * - -1: a < b * - 0: a == b * - 1: a > b * - undefined: a ∥ b: a and b are concurrent * @param {({ peer: PeerID, counter: number })[]} a * @param {({ peer: PeerID, counter: number })[]} b * @returns {-1 | 1 | 0 | undefined} */ cmpFrontiers(a: ({ peer: PeerID, counter: number })[], b: ({ peer: PeerID, counter: number })[]): -1 | 1 | 0 | undefined; /** * Export the snapshot of current version, it's include all content of * operations and states * * @deprecated Use `export({mode: "snapshot"})` instead * @returns {Uint8Array} */ exportSnapshot(): Uint8Array; /** * Export the document based on the specified ExportMode. * * @param mode - The export mode to use. Can be one of: * - `{ mode: "snapshot" }`: Export a full snapshot of the document. * - `{ mode: "update", from: VersionVector }`: Export updates from the given version vector. * - `{ mode: "updates-in-range", spans: { id: ID, len: number }[] }`: Export updates within the specified ID spans. * - `{ mode: "shallow-snapshot", frontiers: Frontiers }`: Export a garbage-collected snapshot up to the given frontiers. * * @returns A byte array containing the exported data. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * doc.setText("text", "Hello World"); * * // Export a full snapshot * const snapshotBytes = doc.export({ mode: "snapshot" }); * * // Export updates from a specific version * const vv = doc.oplogVersion(); * doc.setText("text", "Hello Loro"); * const updateBytes = doc.export({ mode: "update", from: vv }); * * // Export a garbage-collected snapshot * const gcBytes = doc.export({ mode: "shallow-snapshot", frontiers: doc.oplogFrontiers() }); * * // Export updates within specific ID spans * const spanBytes = doc.export({ * mode: "updates-in-range", * spans: [{ id: "1", len: 10 }, { id: "2", len: 5 }] * }); * ``` * @param {ExportMode} mode * @returns {Uint8Array} */ export(mode: ExportMode): Uint8Array; /** * Export updates from the specific version to the current version with JSON format. * @param {VersionVector | undefined} [start_vv] * @param {VersionVector | undefined} [end_vv] * @returns {JsonSchema} */ exportJsonUpdates(start_vv?: VersionVector, end_vv?: VersionVector): JsonSchema; /** * Import updates from the JSON format. * * only supports backward compatibility but not forward compatibility. * @param {string | JsonSchema} json * @returns {ImportStatus} */ importJsonUpdates(json: string | JsonSchema): ImportStatus; /** * Import a snapshot or a update to current doc. * * Note: * - Updates within the current version will be ignored * - Updates with missing dependencies will be pending until the dependencies are received * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "Hello"); * // get all updates of the doc * const updates = doc.exportFrom(); * const snapshot = doc.exportSnapshot(); * const doc2 = new LoroDoc(); * // import snapshot * doc2.import(snapshot); * // or import updates * doc2.import(updates); * ``` * @param {Uint8Array} update_or_snapshot * @returns {ImportStatus} */ import(update_or_snapshot: Uint8Array): ImportStatus; /** * Import a batch of updates. * * It's more efficient than importing updates one by one. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "Hello"); * const updates = doc.exportFrom(); * const snapshot = doc.exportSnapshot(); * const doc2 = new LoroDoc(); * doc2.importUpdateBatch([snapshot, updates]); * ``` * @param {Array<any>} data */ importUpdateBatch(data: Array<any>): void; /** * Get the shallow json format of the document state. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * const tree = doc.getTree("tree"); * const map = doc.getMap("map"); * const shallowValue = doc.toShallowJSON(); * /* * {"list": ..., "tree": ..., "map": ...} * *\/ * console.log(shallowValue); * ``` * @returns {any} */ getShallowValue(): any; /** * Get the json format of the document state. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, "Hello"); * const text = list.insertContainer(0, new LoroText()); * text.insert(0, "Hello"); * const map = list.insertContainer(1, new LoroMap()); * map.set("foo", "bar"); * /* * {"list": ["Hello", {"foo": "bar"}]} * *\/ * console.log(doc.toJSON()); * ``` * @returns {any} */ toJSON(): any; /** * Subscribe to the changes of the loro document. The function will be called when the * transaction is committed or updates from remote are imported. * * Returns a subscription ID, which can be used to unsubscribe. * * The events will be emitted after a transaction is committed. A transaction is committed when: * * - `doc.commit()` is called. * - `doc.exportFrom(version)` is called. * - `doc.import(data)` is called. * - `doc.checkout(version)` is called. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * doc.subscribe((event)=>{ * console.log(event); * }); * text.insert(0, "Hello"); * // the events will be emitted when `commit()` is called. * doc.commit(); * ``` * @param {Function} f * @returns {any} */ subscribe(f: Function): any; /** * Debug the size of the history */ debugHistory(): void; /** * Get all of changes in the oplog * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "Hello"); * const changes = doc.getAllChanges(); * * for (let [peer, changes] of changes.entries()){ * console.log("peer: ", peer); * for (let change in changes){ * console.log("change: ", change); * } * } * ``` * @returns {Map<PeerID, Change[]>} */ getAllChanges(): Map<PeerID, Change[]>; /** * Get the change of a specific ID * @param {{ peer: PeerID, counter: number }} id * @returns {Change} */ getChangeAt(id: { peer: PeerID, counter: number }): Change; /** * Get the change of with specific peer_id and lamport <= given lamport * @param {string} peer_id * @param {number} lamport * @returns {Change | undefined} */ getChangeAtLamport(peer_id: string, lamport: number): Change | undefined; /** * Get all ops of the change of a specific ID * @param {{ peer: PeerID, counter: number }} id * @returns {any[]} */ getOpsInChange(id: { peer: PeerID, counter: number }): any[]; /** * Convert frontiers to a readable version vector * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "Hello"); * const frontiers = doc.frontiers(); * const version = doc.frontiersToVV(frontiers); * ``` * @param {({ peer: PeerID, counter: number })[]} frontiers * @returns {VersionVector} */ frontiersToVV(frontiers: ({ peer: PeerID, counter: number })[]): VersionVector; /** * Convert a version vector to frontiers * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "Hello"); * const version = doc.version(); * const frontiers = doc.vvToFrontiers(version); * ``` * @param {VersionVector} vv * @returns {{ peer: PeerID, counter: number }[]} */ vvToFrontiers(vv: VersionVector): { peer: PeerID, counter: number }[]; /** * Get the value or container at the given path * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("key", 1); * console.log(doc.getByPath("map/key")); // 1 * console.log(doc.getByPath("map")); // LoroMap * ``` * @param {string} path * @returns {Value | Container | undefined} */ getByPath(path: string): Value | Container | undefined; /** * Get the absolute position of the given Cursor * * @example * ```ts * const doc = new LoroDoc(); * const text = doc.getText("text"); * text.insert(0, "123"); * const pos0 = text.getCursor(0, 0); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(0); * } * text.insert(0, "1"); * { * const ans = doc.getCursorPos(pos0!); * expect(ans.offset).toBe(1); * } * ``` * @param {Cursor} cursor * @returns {{ update?: Cursor, offset: number, side: Side }} */ getCursorPos(cursor: Cursor): { update?: Cursor, offset: number, side: Side }; /** * Peer ID of the current writer. */ readonly peerId: bigint; /** * Get peer id in decimal string. */ readonly peerIdStr: PeerID; } /** * The handler of a list container. * * Learn more at https://loro.dev/docs/tutorial/list */ export class LoroList { free(): void; /** * Create a new detached LoroList. * * The edits on a detached container will not be persisted. * To attach the container to the document, please insert it into an attached container. */ constructor(); /** * "List" * @returns {'List'} */ kind(): 'List'; /** * Delete elements from index to index + len. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * list.delete(0, 1); * console.log(list.value); // [] * ``` * @param {number} index * @param {number} len */ delete(index: number, len: number): void; /** * Get elements of the list. If the type of a element is a container, it will be * resolved recursively. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * const text = list.insertContainer(1, new LoroText()); * text.insert(0, "Hello"); * console.log(list.getDeepValue()); // [100, "Hello"]; * ``` * @returns {any} */ toJSON(): any; /** * Subscribe to the changes of the list. * * Returns a subscription id, which can be used to unsubscribe. * * The events will be emitted after a transaction is committed. A transaction is committed when: * * - `doc.commit()` is called. * - `doc.exportFrom(version)` is called. * - `doc.import(data)` is called. * - `doc.checkout(version)` is called. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.subscribe((event)=>{ * console.log(event); * }); * list.insert(0, 100); * doc.commit(); * ``` * @param {Function} f * @returns {any} */ subscribe(f: Function): any; /** * Get the parent container. * * - The parent container of the root tree is `undefined`. * - The object returned is a new js object each time because it need to cross * the WASM boundary. * @returns {Container | undefined} */ parent(): Container | undefined; /** * Whether the container is attached to a document. * * If it's detached, the operations on the container will not be persisted. * @returns {boolean} */ isAttached(): boolean; /** * Get the attached container associated with this. * * Returns an attached `Container` that equals to this or created by this, otherwise `undefined`. * @returns {LoroList | undefined} */ getAttached(): LoroList | undefined; /** * Pop a value from the end of the list. * @returns {Value | undefined} */ pop(): Value | undefined; /** * Delete all elements in the list. */ clear(): void; /** * Get the id of this container. */ readonly id: ContainerID; /** * Get the length of list. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const list = doc.getList("list"); * list.insert(0, 100); * list.insert(1, "foo"); * list.insert(2, true); * console.log(list.length); // 3 * ``` */ readonly length: number; } /** * The handler of a map container. * * Learn more at https://loro.dev/docs/tutorial/map */ export class LoroMap { free(): void; /** * Create a new detached LoroMap. * * The edits on a detached container will not be persisted. * To attach the container to the document, please insert it into an attached container. */ constructor(); /** * "Map" * @returns {'Map'} */ kind(): 'Map'; /** * Remove the key from the map. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * map.delete("foo"); * ``` * @param {string} key */ delete(key: string): void; /** * Get the keys of the map. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * map.set("baz", "bar"); * const keys = map.keys(); // ["foo", "baz"] * ``` * @returns {any[]} */ keys(): any[]; /** * Get the values of the map. If the value is a child container, the corresponding * `Container` will be returned. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * map.set("baz", "bar"); * const values = map.values(); // ["bar", "bar"] * ``` * @returns {any[]} */ values(): any[]; /** * Get the entries of the map. If the value is a child container, the corresponding * `Container` will be returned. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * map.set("baz", "bar"); * const entries = map.entries(); // [["foo", "bar"], ["baz", "bar"]] * ``` * @returns {([string, Value | Container])[]} */ entries(): ([string, Value | Container])[]; /** * Get the keys and the values. If the type of value is a child container, * it will be resolved recursively. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * const text = map.setContainer("text", new LoroText()); * text.insert(0, "Hello"); * console.log(map.getDeepValue()); // {"foo": "bar", "text": "Hello"} * ``` * @returns {any} */ toJSON(): any; /** * Subscribe to the changes of the map. * * Returns a subscription id, which can be used to unsubscribe. * * The events will be emitted after a transaction is committed. A transaction is committed when: * * - `doc.commit()` is called. * - `doc.exportFrom(version)` is called. * - `doc.import(data)` is called. * - `doc.checkout(version)` is called. * * @param {Listener} f - Event listener * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.subscribe((event)=>{ * console.log(event); * }); * map.set("foo", "bar"); * doc.commit(); * ``` * @param {Function} f * @returns {any} */ subscribe(f: Function): any; /** * Get the parent container. * * - The parent container of the root tree is `undefined`. * - The object returned is a new js object each time because it need to cross * the WASM boundary. * @returns {Container | undefined} */ parent(): Container | undefined; /** * Whether the container is attached to a document. * * If it's detached, the operations on the container will not be persisted. * @returns {boolean} */ isAttached(): boolean; /** * Get the attached container associated with this. * * Returns an attached `Container` that equals to this or created by this, otherwise `undefined`. * @returns {LoroMap | undefined} */ getAttached(): LoroMap | undefined; /** * Delete all key-value pairs in the map. */ clear(): void; /** * The container id of this handler. */ readonly id: ContainerID; /** * Get the size of the map. * * @example * ```ts * import { LoroDoc } from "loro-crdt"; * * const doc = new LoroDoc(); * const map = doc.getMap("map"); * map.set("foo", "bar"); * console.log(map.size); // 1 * ``` */ readonly size: number; } /** * The handler of a list container. * * Learn more at https://loro.dev/docs/tutorial/list */ export class LoroMovableList { free(): void; /** * Create a new detached LoroList. * * The edits on a detached container will not be persisted. * To attach the container to the document, please insert it into an attached container. */ constructor(); /** * "MovableList" * @returns {'MovableList'} */ kind(): 'MovableList'; /** * Delete elements from index to index + len. * * @example * ```ts * import { LoroDoc } from "lo