json-joy
Version:
Collection of libraries for building collaborative editing apps.
173 lines (172 loc) • 5.88 kB
JavaScript
import { Timestamp } from '../../../../json-crdt-patch/clock';
import { ClockTable } from '../../../../json-crdt-patch/codec/clock/ClockTable';
import { CrdtWriter } from '../../../../json-crdt-patch/util/binary/CrdtWriter';
import { CborEncoder } from '@jsonjoy.com/json-pack/lib/cbor/CborEncoder';
import * as nodes from '../../../nodes';
import { CRDT_MAJOR_OVERLAY } from '../../structural/binary/constants';
export class Encoder {
enc;
clockTable;
constructor(writer) {
this.enc = new CborEncoder(writer || new CrdtWriter());
}
encode(doc, clockTable = ClockTable.from(doc.clock)) {
this.clockTable = clockTable;
const writer = this.enc.writer;
writer.reset();
clockTable.write(writer);
const encodedClock = writer.flush();
const rootValueId = doc.root.val;
const result = {
c: encodedClock,
};
if (rootValueId.sid !== 0) {
writer.reset();
this.ts(rootValueId);
result.r = writer.flush();
}
// biome-ignore lint: index is not iterable
doc.index.forEach(({ v: node }) => this.onNode(result, node));
return result;
}
onNode = (result, node) => {
const id = node.id;
const sid = id.sid;
const time = id.time;
const sidIndex = this.clockTable.getBySid(sid).index;
const field = (sidIndex.toString(36) + '_' + time.toString(36));
result[field] = this.encodeNode(node);
};
encodeNode(node) {
if (node instanceof nodes.ConNode)
return this.encodeCon(node);
else if (node instanceof nodes.ValNode)
return this.encodeVal(node);
else if (node instanceof nodes.ObjNode)
return this.encodeObj(node);
else if (node instanceof nodes.VecNode)
return this.encodeVec(node);
else if (node instanceof nodes.StrNode)
return this.encodeStr(node);
else if (node instanceof nodes.BinNode)
return this.encodeBin(node);
else if (node instanceof nodes.ArrNode)
return this.encodeArr(node);
throw new Error('UNKNOWN_NODE');
}
ts(id) {
const index = this.clockTable.getBySid(id.sid).index;
this.enc.writer.id(index, id.time);
}
writeTL(majorOverlay, length) {
const writer = this.enc.writer;
if (length < 24)
writer.u8(majorOverlay + length);
else if (length <= 0xff)
writer.u16(((majorOverlay + 24) << 8) + length);
else if (length <= 0xffff)
writer.u8u16(majorOverlay + 25, length);
else
writer.u8u32(majorOverlay + 26, length);
}
encodeCon(node) {
const encoder = this.enc;
const writer = encoder.writer;
const val = node.val;
writer.reset();
if (val instanceof Timestamp) {
this.writeTL(CRDT_MAJOR_OVERLAY.CON, 1);
this.ts(val);
}
else {
this.writeTL(CRDT_MAJOR_OVERLAY.CON, 0);
encoder.writeAny(val);
}
return writer.flush();
}
encodeVal(node) {
const writer = this.enc.writer;
const child = node.node();
writer.reset();
this.writeTL(CRDT_MAJOR_OVERLAY.VAL, 0);
this.ts(child.id);
return writer.flush();
}
encodeObj(node) {
const encoder = this.enc;
const writer = encoder.writer;
writer.reset();
const keys = node.keys;
this.writeTL(CRDT_MAJOR_OVERLAY.OBJ, keys.size);
keys.forEach(this.onObjKey);
return writer.flush();
}
onObjKey = (value, key) => {
this.enc.writeStr(key);
this.ts(value);
};
encodeVec(node) {
const writer = this.enc.writer;
writer.reset();
const length = node.elements.length;
this.writeTL(CRDT_MAJOR_OVERLAY.VEC, length);
for (let i = 0; i < length; i++) {
const childId = node.val(i);
if (!childId)
writer.u8(0);
else {
writer.u8(1);
this.ts(childId);
}
}
return writer.flush();
}
encodeStr(node) {
const encoder = this.enc;
const writer = encoder.writer;
writer.reset();
this.writeTL(CRDT_MAJOR_OVERLAY.STR, node.count);
for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
this.ts(chunk.id);
if (chunk.del)
encoder.writeUInteger(chunk.span);
else
encoder.writeStr(chunk.data);
}
return writer.flush();
}
encodeBin(node) {
const encoder = this.enc;
const writer = encoder.writer;
writer.reset();
this.writeTL(CRDT_MAJOR_OVERLAY.BIN, node.count);
for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
const length = chunk.span;
const deleted = chunk.del;
this.ts(chunk.id);
writer.b1vu56(~~deleted, length);
if (deleted)
continue;
writer.buf(chunk.data, length);
}
return writer.flush();
}
encodeArr(node) {
const encoder = this.enc;
const writer = encoder.writer;
writer.reset();
this.writeTL(CRDT_MAJOR_OVERLAY.ARR, node.count);
for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
const length = chunk.span;
const deleted = chunk.del;
this.ts(chunk.id);
writer.b1vu56(~~deleted, length);
if (deleted)
continue;
const data = chunk.data;
for (let i = 0; i < length; i++)
this.ts(data[i]);
}
return writer.flush();
}
}