json-joy
Version:
Collection of libraries for building collaborative editing apps.
350 lines (349 loc) • 14.8 kB
TypeScript
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;
}