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

97.91% Statements 94/96
96.96% Branches 32/33
100% Functions 11/11
97.53% Lines 79/81

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 19x       19x           2482x 2482x 2482x 2482x 315x   2167x 2167x   2482x 2482x 2482x 2482x       300107x       22660x   277447x 277447x             120851x 83041x 69743x 43476x 40189x 40183x 4644x         37810x 98883x 37810x 37810x       6x 6x 6x 6x 6x 18x 18x   16x 16x     6x 6x       13298x 13298x 13298x 12090x 12090x 12090x 12090x 1088x   11002x 11002x 16199x 11002x     13298x 13298x       26267x 26267x 109402x     26267x 26267x       4644x 4644x 57762x     4644x 4644x       3287x 3287x       35539x 35539x 35539x 2x 2x 2x 35537x 8816x 8816x   35539x      
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;
  }
}