UNPKG

json-joy

Version:

Collection of libraries for building collaborative editing apps.

173 lines (172 loc) 5.88 kB
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(); } }