json-joy
Version:
Collection of libraries for building collaborative editing apps.
246 lines (245 loc) • 8.55 kB
JavaScript
import * as operations from '../../operations';
import { JsonCrdtPatchOpcodeOverlay } from '../../constants';
import { CrdtWriter } from '../../util/binary/CrdtWriter';
import { Timestamp } from '../../clock';
import { CborEncoder } from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder';
/**
* JSON CRDT Patch "binary" codec encoder.
*/
export class Encoder extends CborEncoder {
writer;
patchSid = 0;
/**
* Creates a new encoder instance.
*
* @param writer An optional custom implementation of CRDT writer.
*/
constructor(writer = new CrdtWriter()) {
super(writer);
this.writer = writer;
}
/**
* Encodes a JSON CRDT Patch into a {@link Uint8Array} blob.
*
* @param patch A JSON CRDT Patch to encode.
* @returns A {@link Uint8Array} blob containing the encoded JSON CRDT Patch.
*/
encode(patch) {
this.writer.reset();
const id = patch.getId();
const sid = (this.patchSid = id.sid);
const writer = this.writer;
writer.vu57(sid);
writer.vu57(id.time);
const meta = patch.meta;
if (meta === undefined)
this.writeUndef();
else
this.writeArr([meta]);
this.encodeOperations(patch);
return writer.flush();
}
encodeOperations(patch) {
const ops = patch.ops;
const length = ops.length;
this.writer.vu57(length);
for (let i = 0; i < length; i++)
this.encodeOperation(ops[i]);
}
encodeId(id) {
const sessionId = id.sid;
const time = id.time;
const writer = this.writer;
if (sessionId === this.patchSid) {
writer.b1vu56(0, time);
}
else {
writer.b1vu56(1, time);
writer.vu57(sessionId);
}
}
encodeTss(span) {
this.encodeId(span);
this.writer.vu57(span.span);
}
writeInsStr(length, obj, ref, str) {
const writer = this.writer;
if (length <= 0b111) {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_str + length);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_str);
writer.vu57(length);
}
this.encodeId(obj);
this.encodeId(ref);
return writer.utf8(str);
}
encodeOperation(op) {
const writer = this.writer;
const constr = op.constructor;
switch (constr) {
case operations.NewConOp: {
const operation = op;
const val = operation.val;
if (val instanceof Timestamp) {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_con + 1);
this.encodeId(val);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_con);
this.writeAny(val);
}
break;
}
case operations.NewValOp: {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_val);
break;
}
case operations.NewObjOp: {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_obj);
break;
}
case operations.NewVecOp: {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_vec);
break;
}
case operations.NewStrOp: {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_str);
break;
}
case operations.NewBinOp: {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_bin);
break;
}
case operations.NewArrOp: {
writer.u8(JsonCrdtPatchOpcodeOverlay.new_arr);
break;
}
case operations.InsValOp: {
const operation = op;
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_val);
this.encodeId(operation.obj);
this.encodeId(operation.val);
break;
}
case operations.InsObjOp: {
const operation = op;
const data = operation.data;
const length = data.length;
if (length <= 0b111) {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_obj + length);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_obj);
writer.vu57(length);
}
this.encodeId(operation.obj);
for (let i = 0; i < length; i++) {
const tuple = data[i];
this.writeStr(tuple[0]);
this.encodeId(tuple[1]);
}
break;
}
case operations.InsVecOp: {
const operation = op;
const data = operation.data;
const length = data.length;
if (length <= 0b111) {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_vec + length);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_vec);
writer.vu57(length);
}
this.encodeId(operation.obj);
for (let i = 0; i < length; i++) {
const tuple = data[i];
writer.u8(tuple[0]);
this.encodeId(tuple[1]);
}
break;
}
case operations.InsStrOp: {
const operation = op;
const obj = operation.obj;
const ref = operation.ref;
const str = operation.data;
const len1 = str.length;
writer.ensureCapacity(24 + len1 * 4);
const x = writer.x;
const len2 = this.writeInsStr(len1, obj, ref, str);
if (len1 !== len2) {
writer.x = x;
this.writeInsStr(len2, obj, ref, str);
}
break;
}
case operations.InsBinOp: {
const operation = op;
const buf = operation.data;
const length = buf.length;
if (length <= 0b111) {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_bin + length);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_bin);
writer.vu57(length);
}
this.encodeId(operation.obj);
this.encodeId(operation.ref);
writer.buf(buf, length);
break;
}
case operations.InsArrOp: {
const operation = op;
const elements = operation.data;
const length = elements.length;
if (length <= 0b111) {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_arr + length);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.ins_arr);
writer.vu57(length);
}
this.encodeId(operation.obj);
this.encodeId(operation.ref);
for (let i = 0; i < length; i++)
this.encodeId(elements[i]);
break;
}
case operations.DelOp: {
const operation = op;
const what = operation.what;
const length = what.length;
if (length <= 0b111) {
writer.u8(JsonCrdtPatchOpcodeOverlay.del + length);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.del);
writer.vu57(length);
}
this.encodeId(operation.obj);
for (let i = 0; i < length; i++)
this.encodeTss(what[i]);
break;
}
case operations.NopOp: {
const operation = op;
const length = operation.len;
if (length <= 0b111) {
writer.u8(JsonCrdtPatchOpcodeOverlay.nop + length);
}
else {
writer.u8(JsonCrdtPatchOpcodeOverlay.nop);
writer.vu57(length);
}
break;
}
default: {
throw new Error('UNKNOWN_OP');
}
}
}
}