All files / json-crdt-patch/codec/compact encode.ts

98.78% Statements 81/82
97.56% Branches 40/41
100% Functions 4/4
100% Lines 75/75

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 12414x 14x   14x 14x     14x 82324x 82324x     14x 13375x 13375x 9877x 9877x   3498x                 14x 8584x 8584x   8584x 8584x 8584x 8584x 8584x 8584x   8584x 48853x 3677x 3677x 3x 3674x 365x   3309x   45176x 1370x 43806x 5820x 37986x 4x 37982x 4888x 33094x 1394x 31700x 3868x 27832x 2865x 24967x 6484x 17834x 6484x         6484x 18483x 2x 2x 2x         2x 18481x 7136x           7136x 11345x 2926x           2926x 8419x 2914x 8553x 2914x           2914x 5505x 4389x     13375x   4389x 1116x 1116x 1116x 1116x 1116x       8584x    
import * as operations from '../../operations';
import {type ITimespanStruct, type ITimestampStruct, Timestamp} from '../../clock';
import type {Patch} from '../../Patch';
import {JsonCrdtPatchOpcode, SESSION} from '../../constants';
import {toBase64} from '@jsonjoy.com/base64/lib/toBase64';
import type * as types from './types';
 
const timestamp = (sid: number, ts: ITimestampStruct): types.CompactCodecTimestamp => {
  const tsSessionId = ts.sid;
  return tsSessionId === sid ? ts.time : [tsSessionId, ts.time];
};
 
const timespan = (sid: number, span: ITimespanStruct): types.CompactCodecTimespan => {
  const ts = timestamp(sid, span);
  if (ts instanceof Array) {
    ts.push(span.span);
    return ts;
  }
  return [ts, span.span];
};
 
/**
 * Encodes a patch into a compact binary format into a JavaScript array.
 *
 * @param patch The patch to encode.
 * @returns The encoded patch as a JavaScript POJO.
 */
export const encode = (patch: Patch): types.CompactCodecPatch => {
  const id = patch.getId();
  Iif (!id) throw new Error('PATCH_EMPTY');
 
  const sid = id.sid;
  const time = id.time;
  const header: types.CompactCodecPatch[0] = sid === SESSION.SERVER ? [time] : [[sid, time]];
  const meta = patch.meta;
  if (meta !== undefined) header.push(meta);
  const res: types.CompactCodecPatch = [header];
 
  for (const op of patch.ops) {
    if (op instanceof operations.NewConOp) {
      const val = op.val;
      if (val instanceof Timestamp) {
        res.push([JsonCrdtPatchOpcode.new_con, timestamp(sid, val), true]);
      } else if (val === undefined) {
        res.push([JsonCrdtPatchOpcode.new_con]);
      } else {
        res.push([JsonCrdtPatchOpcode.new_con, val]);
      }
    } else if (op instanceof operations.NewValOp) {
      res.push([JsonCrdtPatchOpcode.new_val]);
    } else if (op instanceof operations.NewObjOp) {
      res.push([JsonCrdtPatchOpcode.new_obj]);
    } else if (op instanceof operations.NewVecOp) {
      res.push([JsonCrdtPatchOpcode.new_vec]);
    } else if (op instanceof operations.NewStrOp) {
      res.push([JsonCrdtPatchOpcode.new_str]);
    } else if (op instanceof operations.NewBinOp) {
      res.push([JsonCrdtPatchOpcode.new_bin]);
    } else if (op instanceof operations.NewArrOp) {
      res.push([JsonCrdtPatchOpcode.new_arr]);
    } else if (op instanceof operations.InsValOp) {
      res.push([JsonCrdtPatchOpcode.ins_val, timestamp(sid, op.obj), timestamp(sid, op.val)]);
    } else if (op instanceof operations.InsObjOp) {
      const tuples: types.CompactCodecInsObjOperation[2] = [];
      for (const [key, value] of op.data) tuples.push([key, timestamp(sid, value)]);
      const operation: types.CompactCodecInsObjOperation = [
        JsonCrdtPatchOpcode.ins_obj,
        timestamp(sid, op.obj),
        tuples,
      ];
      res.push(operation);
    } else if (op instanceof operations.InsVecOp) {
      const tuples: types.CompactCodecInsVecOperation[2] = [];
      for (const [key, value] of op.data) tuples.push([key, timestamp(sid, value)]);
      const operation: types.CompactCodecInsVecOperation = [
        JsonCrdtPatchOpcode.ins_vec,
        timestamp(sid, op.obj),
        tuples,
      ];
      res.push(operation);
    } else if (op instanceof operations.InsStrOp) {
      const operation: types.CompactCodecInsStrOperation = [
        JsonCrdtPatchOpcode.ins_str,
        timestamp(sid, op.obj),
        timestamp(sid, op.ref),
        op.data,
      ];
      res.push(operation);
    } else if (op instanceof operations.InsBinOp) {
      const operation: types.CompactCodecInsBinOperation = [
        JsonCrdtPatchOpcode.ins_bin,
        timestamp(sid, op.obj),
        timestamp(sid, op.ref),
        toBase64(op.data),
      ];
      res.push(operation);
    } else if (op instanceof operations.InsArrOp) {
      const elements: types.CompactCodecInsArrOperation[3] = [];
      for (const element of op.data) elements.push(timestamp(sid, element));
      const operation: types.CompactCodecInsArrOperation = [
        JsonCrdtPatchOpcode.ins_arr,
        timestamp(sid, op.obj),
        timestamp(sid, op.ref),
        elements,
      ];
      res.push(operation);
    } else if (op instanceof operations.DelOp) {
      const operation: types.CompactCodecDelOperation = [
        JsonCrdtPatchOpcode.del,
        timestamp(sid, op.obj),
        op.what.map((span) => timespan(sid, span)),
      ];
      res.push(operation);
    } else if (op instanceof operations.NopOp) {
      const operation: types.CompactCodecNopOperation = [JsonCrdtPatchOpcode.nop];
      const len = op.len;
      if (len > 1) operation.push(len);
      res.push(operation);
    }
  }
 
  return res;
};