Skip to content

Commit

Permalink
feat(json-crdt): 馃幐 improve model initialization APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed May 2, 2024
1 parent 89daf03 commit e0474d1
Showing 1 changed file with 60 additions and 17 deletions.
77 changes: 60 additions & 17 deletions src/json-crdt/model/Model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ export const UNDEFINED = new ConNode(ORIGIN, undefined);
* i.e. model, of the JSON CRDT document.
*/
export 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.
*/
public static readonly sid = randomSessionId;

/**
* Create a CRDT model which uses logical clock. Logical clock assigns a
* logical timestamp to every node and operation. Logical timestamp consists
Expand All @@ -34,9 +45,9 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
*
* @deprecated Use `Model.create()` instead.
*/
public static withLogicalClock(clockOrSessionId?: clock.ClockVector | number): Model {
public static readonly withLogicalClock = (clockOrSessionId?: clock.ClockVector | number): Model => {
return Model.create(undefined, clockOrSessionId);
}
};

/**
* Create a CRDT model which uses server clock. In this model a central server
Expand All @@ -50,9 +61,9 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
*
* @deprecated Use `Model.create()` instead: `Model.create(undefined, SESSION.SERVER)`.
*/
public static withServerClock(time: number = 1): Model {
public static readonly withServerClock = (time: number = 1): Model => {
return Model.create(undefined, new clock.ServerClockVector(SESSION.SERVER, time));
}
};

/**
* Create a new JSON CRDT model. If a schema is provided, the model is
Expand Down Expand Up @@ -115,10 +126,10 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
*
* @param schema The schema (typing and default value) to set for this model.
* @param sidOrClock Session ID to use for local operations. Defaults to a random
* session ID.
* session ID generated by {@link Model.sid}.
* @returns A strictly typed model.
*/
public static create<S extends NodeBuilder>(schema?: S, sidOrClock: clock.ClockVector | number = randomSessionId()): Model<SchemaToJsonNode<S>> {
public static readonly create = <S extends NodeBuilder>(schema?: S, sidOrClock: clock.ClockVector | number = Model.sid()): Model<SchemaToJsonNode<S>> => {
const cl = typeof sidOrClock === 'number'
? sidOrClock === SESSION.SERVER
? new clock.ServerClockVector(SESSION.SERVER, 1)
Expand All @@ -127,23 +138,38 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
const model = new Model<SchemaToJsonNode<S>>(cl);
if (schema) model.setSchema(schema, true);
return model;
}
};

/**
* Un-serializes a model from "binary" structural encoding.
* 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.
*/
public static fromBinary(data: Uint8Array): Model {
public static readonly fromBinary = (data: Uint8Array): Model => {
return decoder.decode(data);
}
};

public static load<S extends NodeBuilder>(data: Uint8Array, sid?: number, schema?: S): Model<SchemaToJsonNode<S>> {
/**
* 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.
*/
public static readonly load = <S extends NodeBuilder>(data: Uint8Array, sid?: number, schema?: S): Model<SchemaToJsonNode<S>> => {
const model = decoder.decode(data) as unknown as Model<SchemaToJsonNode<S>>;
if (schema) model.setSchema(schema, true);
if (typeof sid === 'number') model.setSid(sid);
return model;
}
};

/**
* Instantiates a model from a collection of patches. The patches are applied
Expand Down Expand Up @@ -399,7 +425,7 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
* @param sessionId Session ID to use for the new model.
* @returns A copy of this model with a new session ID.
*/
public fork(sessionId: number = randomSessionId()): Model<N> {
public fork(sessionId: number = Model.sid()): Model<N> {
const copy = Model.fromBinary(this.toBinary()) as unknown as Model<N>;
if (copy.clock.sid !== sessionId && copy.clock instanceof clock.ClockVector)
copy.clock = copy.clock.fork(sessionId);
Expand Down Expand Up @@ -489,14 +515,31 @@ export class Model<N extends JsonNode = JsonNode<any>> implements Printable {
const oldSid = c.sid;
if (useGlobalSession) c.sid = SESSION.GLOBAL;
this.api.root(schema);
if (useGlobalSession) {
c.sid = oldSid;
c.observe(new clock.Timestamp(SESSION.GLOBAL, c.time - 1), 1);
}
if (useGlobalSession) this.setSid(oldSid);
}
return <any>this;
}

/**
* 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.
*/
public setSid(sid: number): void {
const cl = this.clock;
const oldSid = cl.sid;
if (oldSid !== sid) {
cl.sid = sid;
cl.observe(new clock.Timestamp(oldSid, cl.time - 1), 1);
}
}

// ---------------------------------------------------------------- Printable

public toString(tab: string = ''): string {
Expand Down

0 comments on commit e0474d1

Please sign in to comment.