All files / json-crdt/codec/structural/compact Encoder.ts

97.87% Statements 92/94
96.96% Branches 32/33
100% Functions 11/11
97.46% Lines 77/79

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 13819x 19x 19x           19x           2568x 2568x 2568x 2568x 315x   2253x 2253x   2568x 2568x 2568x 2568x       294955x       22547x   272408x 272408x             112083x 77297x 63834x 38181x 36152x 36146x 3776x         34786x 92121x 34786x 34786x       6x 6x 6x 6x 6x 18x 18x   16x 16x     6x 6x       13463x 13463x 13463x 11533x 11533x 11533x 11533x 807x   10726x 10726x 15365x 10726x     13463x 13463x       25653x 25653x 113915x     25653x 25653x       3776x 3776x 57422x     3776x 3776x       2029x 2029x       32370x 32370x 32370x 2x 2x 2x 32368x 8316x 8316x   32370x      
import * as nodes from '../../../nodes';
import {ClockEncoder} from '../../../../json-crdt-patch/codec/clock/ClockEncoder';
import {type ITimestampStruct, Timestamp} from '../../../../json-crdt-patch/clock';
import {JsonCrdtDataType} from '../../../../json-crdt-patch/constants';
import {SESSION} from '../../../../json-crdt-patch/constants';
import type * as t from './types';
import type {Model} from '../../../model';
 
export class Encoder {
  protected time?: number;
  protected clock?: ClockEncoder;
  protected model!: Model;
 
  public encode(model: Model<any>): t.JsonCrdtCompactDocument {
    this.model = model;
    const isServerTime = model.clock.sid === SESSION.SERVER;
    const clock = model.clock;
    if (isServerTime) {
      this.time = clock.time;
    } else {
      this.clock = new ClockEncoder();
      this.clock.reset(model.clock);
    }
    const root = model.root;
    const doc: t.JsonCrdtCompactDocument = [0, !root.val.time ? 0 : this.cNode(root.node())];
    doc[0] = isServerTime ? this.time! : this.clock!.toJson();
    return doc;
  }
 
  protected ts(ts: ITimestampStruct): t.JsonCrdtCompactTimestamp {
    switch (ts.sid) {
      case SESSION.SYSTEM:
        return [ts.sid, ts.time];
      case SESSION.SERVER:
        return this.time! - ts.time;
      default: {
        const relativeId = this.clock!.append(ts);
        return [-relativeId.sessionIndex, relativeId.timeDiff];
      }
    }
  }
 
  protected cNode(node: nodes.JsonNode): t.JsonCrdtCompactNode {
    // TODO: PERF: use switch with `node.constructor`.
    if (node instanceof nodes.ObjNode) return this.cObj(node);
    else if (node instanceof nodes.ArrNode) return this.cArr(node);
    else if (node instanceof nodes.StrNode) return this.cStr(node);
    else if (node instanceof nodes.ValNode) return this.cVal(node);
    else if (node instanceof nodes.VecNode) return this.cVec(node);
    else if (node instanceof nodes.ConNode) return this.cCon(node);
    else if (node instanceof nodes.BinNode) return this.cBin(node);
    throw new Error('UNKNOWN_NODE');
  }
 
  protected cObj(obj: nodes.ObjNode): t.JsonCrdtCompactObj {
    const map: t.JsonCrdtCompactObj[2] = {};
    obj.nodes((child, key) => (map[key] = this.cNode(child)));
    const res: t.JsonCrdtCompactObj = [JsonCrdtDataType.obj, this.ts(obj.id), map];
    return res;
  }
 
  protected cVec(vec: nodes.VecNode): t.JsonCrdtCompactVec {
    const elements = vec.elements;
    const length = elements.length;
    const index = this.model.index;
    const map: t.JsonCrdtCompactVec[2] = [];
    for (let i = 0; i < length; i++) {
      const elementId = elements[i];
      if (!elementId) map.push(0);
      else {
        const node = index.get(elementId)!;
        map.push(this.cNode(node));
      }
    }
    const res: t.JsonCrdtCompactVec = [JsonCrdtDataType.vec, this.ts(vec.id), map];
    return res;
  }
 
  protected cArr(node: nodes.ArrNode): t.JsonCrdtCompactArr {
    const chunks: t.JsonCrdtCompactArr[2] = [];
    const index = this.model.index;
    for (let chunk = node.first(); chunk; chunk = node.next(chunk)) {
      const deleted = chunk.del;
      const span = chunk.span;
      const chunkIdEncoded = this.ts(chunk.id);
      if (deleted) {
        chunks.push([chunkIdEncoded, span]);
      } else {
        const nodeIds = chunk.data!;
        const nodes: t.JsonCrdtCompactArrChunk[1] = [];
        for (let i = 0; i < span; i++) nodes.push(this.cNode(index.get(nodeIds[i])!));
        chunks.push([chunkIdEncoded, nodes]);
      }
    }
    const res: t.JsonCrdtCompactArr = [JsonCrdtDataType.arr, this.ts(node.id), chunks];
    return res;
  }
 
  protected cStr(node: nodes.StrNode): t.JsonCrdtCompactStr {
    const chunks: t.JsonCrdtCompactStr[2] = [];
    for (let chunk = node.first(); chunk; chunk = node.next(chunk))
      chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data!] as
        | t.JsonCrdtCompactStrChunk
        | t.JsonCrdtCompactTombstone);
    const res: t.JsonCrdtCompactStr = [JsonCrdtDataType.str, this.ts(node.id), chunks];
    return res;
  }
 
  protected cBin(node: nodes.BinNode): t.JsonCrdtCompactBin {
    const chunks: t.JsonCrdtCompactBin[2] = [];
    for (let chunk = node.first(); chunk; chunk = node.next(chunk))
      chunks.push([this.ts(chunk.id), chunk.del ? chunk.span : chunk.data!] as
        | t.JsonCrdtCompactBinChunk
        | t.JsonCrdtCompactTombstone);
    const res: t.JsonCrdtCompactBin = [JsonCrdtDataType.bin, this.ts(node.id), chunks];
    return res;
  }
 
  protected cVal(node: nodes.ValNode): t.JsonCrdtCompactVal {
    const res: t.JsonCrdtCompactVal = [JsonCrdtDataType.val, this.ts(node.id), this.cNode(node.node())];
    return res;
  }
 
  protected cCon(node: nodes.ConNode): t.JsonCrdtCompactCon {
    const val = node.val;
    const res: t.JsonCrdtCompactCon = [JsonCrdtDataType.con, this.ts(node.id), val];
    if (val instanceof Timestamp) {
      res[2] = 0;
      const specialData = this.ts(val);
      res.push(specialData);
    } else if (val === undefined) {
      res[2] = 0;
      res.push(0);
    }
    return res;
  }
}