UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

350 lines (349 loc) 14.8 kB
import * as clock from '../../json-crdt-patch/clock'; import { ConNode } from '../nodes/const/ConNode'; import { ModelApi } from './api/ModelApi'; import { RootNode } from '../nodes'; import type { SchemaToJsonNode } from '../schema/types'; import { Extensions } from '../extensions/Extensions'; import type { JsonCrdtPatchOperation, Patch } from '../../json-crdt-patch/Patch'; import type { JsonNode, JsonNodeView } from '../nodes/types'; import type { Printable } from 'tree-dump/lib/types'; import type { NodeBuilder } from '../../json-crdt-patch'; export declare const UNDEFINED: ConNode<undefined>; /** * In instance of Model class represents the underlying data structure, * i.e. model, of the JSON CRDT document. */ export declare class Model<N extends JsonNode = JsonNode<any>> implements Printable { /** * Generates a random session ID. Use this method to generate a session ID * for a new user. Store the session ID in the user's browser or device once * and reuse it for all editing sessions of that user. * * Generating a new session ID for each editing session will work, however, * that is not recommended. If a user generates a new session ID for each * editing session, the session clock table will grow indefinitely. */ static readonly sid: () => number; /** * Use this method to generate a random session ID for an existing document. * It checks for the uniqueness of the session ID given the current peers in * the document. This reduces the chance of collision substantially. * * @returns A random session ID that is not used by any peer in the current * document. */ rndSid(): number; /** * Create a CRDT model which uses logical clock. Logical clock assigns a * logical timestamp to every node and operation. Logical timestamp consists * of a session ID and sequence number 2-tuple. Logical clocks allow to * sync peer-to-peer. * * @param clockOrSessionId Logical clock to use. * @returns CRDT model. * * @deprecated Use `Model.create()` instead. */ static readonly withLogicalClock: (clockOrSessionId?: clock.ClockVector | number) => Model; /** * Create a CRDT model which uses server clock. In this model a central server * timestamps each operation with a sequence number. Each timestamp consists * simply of a sequence number, which was assigned by a server. In this model * all operations are approved, persisted and re-distributed to all clients by * a central server. * * @param time Latest known server sequence number. * @returns CRDT model. * * @deprecated Use `Model.create()` instead: `Model.create(undefined, SESSION.SERVER)`. */ static readonly withServerClock: (time?: number) => Model; /** * Create a new JSON CRDT model. If a schema is provided, the model is * strictly typed and the default value of the model is set to the default * value of the schema. * * By default, the model is created with a random session ID and is using * a logical clock. It is also possible to create a model which uses a server * clock by providing the session ID `SESSION.SERVER` (1). * * ### Examples * * Create a basic model, without schema and default value: * * ```ts * const model = Model.create(); * ``` * * Create a strictly typed model with a schema and default value: * * ```ts * const schema = s.obj({ * ticker: s.con<string>('BODEN'), * name: s.str('Jeo Boden'), * tags: s.arr( * s.str('token'), * ), * }); * const model = Model.create(schema); * const patch = model.api.flush(); * ``` * * Create a model with a custom session ID for your logical clock: * * ```ts * const schema = s.str(''); * const sid = 123456789; * const model = Model.create(schema, sid); * const patch = model.api.flush(); * ``` * * The session ID must be at least 65,536 or higher, [see JSON CRDT Patch * specification][json-crdt-patch]. * * [json-crdt-patch]: https://jsonjoy.com/specs/json-crdt-patch/patch-document/logical-clock * * To create a model with a server clock, use the `SESSION.SERVER`, which is * equal to 1: * * ```ts * const model = Model.create(undefined, SESSION.SERVER); * // or * const model = Model.create(undefined, 1); * ``` * * Finally, you can create a model with your clock vector: * * ```ts * const clock = new ClockVector(123456789, 1); * const model = Model.create(undefined, clock); * ``` * * @param schema The schema (typing and default value) to set for this model. * When a schema is provided, the model is strictly typed and the default * value of the model is set to the value of the schema. Also, you MUST * call `model.api.flush()` immediately after creating the model to clear * the change buffer of the patch that was created during the initialization * of the model. * @param sidOrClock Session ID to use for local operations. Defaults to a random * session ID generated by {@link Model.sid}. * @returns A strictly typed model. */ static readonly create: <S extends NodeBuilder>(schema?: S, sidOrClock?: clock.ClockVector | number) => Model<SchemaToJsonNode<S>>; /** * Decodes a model from a "binary" structural encoding. * * Use {@link Model.load} instead, if you want to set the session ID of the * model and the right schema for the model, during the de-serialization. * * @param data Binary blob of a model encoded using "binary" structural * encoding. * @returns An instance of a model. */ static readonly fromBinary: <N_1 extends JsonNode = JsonNode<any>>(data: Uint8Array) => Model<N_1>; /** * Un-serializes a model from "binary" structural encoding. The session ID of * the model is set to the provided session ID `sid`, or the default session * ID of the un-serialized model is used. * * @param data Binary blob of a model encoded using "binary" structural * encoding. * @param sid Session ID to set for the model. * @returns An instance of a model. */ static readonly load: <S extends NodeBuilder>(data: Uint8Array, sid?: number, schema?: S) => Model<SchemaToJsonNode<S>>; /** * Instantiates a model from a collection of patches. The patches are applied * to the model in the order they are provided. The session ID of the model is * set to the session ID of the first patch. * * @param patches A collection of initial patches to apply to the model. * @returns A model with the patches applied. */ static fromPatches(patches: Patch[]): Model; /** * Root of the JSON document is implemented as Last Write Wins Register, * so that the JSON document does not necessarily need to be an object. The * JSON document can be any JSON value. */ root: RootNode<N>; /** * Clock that keeps track of logical timestamps of the current editing session * and logical clocks of all known peers. */ clock: clock.IClockVector; /** * Index of all known node objects (objects, array, strings, values) * in this document. * * @ignore */ index: { min: import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>> | undefined; root: import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>> | undefined; max: import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>> | undefined; readonly comparator: import("sonic-forest/lib/types").Comparator<clock.ITimestampStruct>; set(k: clock.ITimestampStruct, v: JsonNode<unknown>): import("sonic-forest/lib/types").SonicNodePublicReference<import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>>>; find(k: clock.ITimestampStruct): import("sonic-forest/lib/types").SonicNodePublicReference<import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>>> | undefined; get(k: clock.ITimestampStruct): JsonNode<unknown> | undefined; del(k: clock.ITimestampStruct): boolean; clear(): void; has(k: clock.ITimestampStruct): boolean; _size: number; size(): number; isEmpty(): boolean; getOrNextLower(k: clock.ITimestampStruct): import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>> | undefined; forEach(fn: (node: import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>>) => void): void; first(): import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>> | undefined; last(): import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>> | undefined; readonly next: <N_1 extends import("sonic-forest/lib/types").HeadlessNode>(curr: N_1) => N_1 | undefined; iterator0(): () => import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>> | undefined; iterator(): Iterator<import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>>, any, any>; entries(): IterableIterator<import("sonic-forest/lib/types").ITreeNode<clock.ITimestampStruct, JsonNode<unknown>>>; toString(tab: string): string; }; /** * Extensions to the JSON CRDT protocol. Extensions are used to implement * custom data types on top of the JSON CRDT protocol. * * @ignore * @todo Allow this to be `undefined`. */ ext: Extensions; constructor(clockVector: clock.IClockVector); /** @ignore */ private _api?; /** * API for applying local changes to the current document. */ get api(): ModelApi<N>; /** * Experimental node retrieval API using proxy objects. */ get find(): import("./api/proxy").ProxyNodeVal<RootNode<N>>; /** * Experimental node retrieval API using proxy objects. Returns a strictly * typed proxy wrapper around the value of the root node. * * @todo consider renaming this to `_`. */ get s(): import("./api/proxy").JsonNodeToProxyNode<N>; /** * Tracks number of times the `applyPatch` was called. * * @ignore */ tick: number; /** * Applies a batch of patches to the document. * * @param patches A batch, i.e. an array of patches. */ applyBatch(patches: Patch[]): void; /** * Callback called before every `applyPatch` call. */ onbeforepatch?: (patch: Patch) => void; /** * Callback called after every `applyPatch` call. */ onpatch?: (patch: Patch) => void; /** * Works like `applyPatch`, but is intended to be used by the local client * for locally generated patches. It checks if the model clock is ahead of * the patch clock and rebases the patch if necessary. * * @param patch A patch to apply to the document. */ applyLocalPatch(patch: Patch): void; /** * Applies a single patch to the document. All mutations to the model must go * through this method. (With the only exception of local changes through API, * which have an alternative path.) * * @param patch A patch to apply to the document. */ applyPatch(patch: Patch): void; /** * Applies a single operation to the model. All mutations to the model must go * through this method. * * For advanced use only, better use `applyPatch` instead. You MUST increment * the `tick` property and call the necessary event emitters manually. * * @param op Any JSON CRDT Patch operation * @ignore * @internal */ applyOperation(op: JsonCrdtPatchOperation): void; /** * Recursively deletes a tree of nodes. Used when root node is overwritten or * when object contents of container node (object or array) is removed. * * @ignore */ protected deleteNodeTree(value: clock.ITimestampStruct): void; /** * Creates a copy of this model with a new session ID. If the session ID is * not provided, a random session ID is generated. * * @param sessionId Session ID to use for the new model. * @returns A copy of this model with a new session ID. */ fork(sessionId?: number): Model<N>; /** * Creates a copy of this model with the same session ID. * * @returns A copy of this model with the same session ID. */ clone(): Model<N>; /** * Callback called before model isi reset using the `.reset()` method. */ onbeforereset?: () => void; /** * Callback called after model has been reset using the `.reset()` method. */ onreset?: () => void; /** * Resets the model to equivalent state of another model. */ reset(to: Model<N>): void; /** * Returns the view of the model. * * @returns JSON/CBOR of the model. */ view(): Readonly<JsonNodeView<N>>; /** * Serialize this model using "binary" structural encoding. * * @returns This model encoded in octets. */ toBinary(): Uint8Array; /** * Strictly types the model and sets the default value of the model, if * the document is empty. * * @param schema The schema to set for this model. * @param sid Session ID to use for setting the default value of the document. * Defaults to `SESSION.GLOBAL` (2), which is the default session ID * for all operations operations that are not attributed to a specific * session. * @returns Strictly typed model. */ setSchema<S extends NodeBuilder>(schema: S, useGlobalSession?: boolean): Model<SchemaToJsonNode<S>>; /** * Changes the session ID of the model. By modifying the attached clock vector * of the model. Be careful when changing the session ID of the model, as this * is an advanced operation. * * Use the {@link Model.load} method to load a model with the the right session * ID, instead of changing the session ID of the model. When in doubt, use the * {@link Model.fork} method to create a new model with the right session ID. * * @param sid The new session ID to set for the model. */ setSid(sid: number): void; toString(tab?: string): string; }