All files / json-crdt/nodes/obj ObjNode.ts

98.14% Statements 53/54
100% Branches 13/13
94.11% Functions 16/17
97.82% Lines 45/46

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 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156156x 156x                         156x           203049x           203049x 203049x                   17671x 17671x 17656x                       293890x 293890x 286301x 286301x                 59299x 157897x                 11011x 33089x                           6972x           203049x         203049x           121627x 121627x 121627x 121627x 121462x 121462x 121462x 121462x 307952x 307952x 4x 4x   307948x 307948x 285401x 285401x 22547x   121462x           203049x     10785x           5393x 5393x         16230x     16228x 16228x            
import {printTree} from 'tree-dump/lib/printTree';
import {compare, type ITimestampStruct, printTs} from '../../../json-crdt-patch/clock';
import type {Model} from '../../model';
import type {Printable} from 'tree-dump/lib/types';
import type {JsonNode, JsonNodeView} from '..';
 
/**
 * Represents a `obj` JSON CRDT node, which is a Last-write-wins (LWW) object.
 * It is a map of string keys to LWW registers. The value of each register is
 * a reference to another JSON CRDT node.
 *
 * @category CRDT Node
 */
 
export class ObjNode<Value extends Record<string, JsonNode> = Record<string, JsonNode>>
  implements JsonNode<JsonNodeView<Value>>, Printable
{
  /**
   * @ignore
   */
  public readonly keys: Map<string, ITimestampStruct> = new Map();
 
  constructor(
    /**
     * @ignore
     */
    protected readonly doc: Model<any>,
    public readonly id: ITimestampStruct,
  ) {}
 
  /**
   * Retrieves a JSON CRDT node at the given key.
   *
   * @param key A key of the object.
   * @returns JSON CRDT node at the given key, if any.
   */
  public get<K extends keyof Value>(key: K): undefined | Value[K] {
    const id = this.keys.get(key as string);
    if (!id) return undefined;
    return this.doc.index.get(id) as Value[K];
  }
 
  /**
   * Rewrites object key.
   *
   * @param key Object key to set.
   * @param id ID of the contents of the key.
   * @returns Returns old entry ID, if any.
   * @ignore
   */
  public put(key: string, id: ITimestampStruct): undefined | ITimestampStruct {
    const currentId = this.keys.get(key);
    if (currentId && compare(currentId, id) >= 0) return;
    this.keys.set(key, id);
    return currentId;
  }
 
  /**
   * Iterate over all key-value pairs in the object.
   *
   * @param callback Callback to call for each key-value pair.
   */
  public nodes(callback: (node: JsonNode, key: string) => void) {
    const index = this.doc.index;
    this.keys.forEach((id, key) => callback(index.get(id)!, key));
  }
 
  // ----------------------------------------------------------------- JsonNode
 
  /**
   * @ignore
   */
  public children(callback: (node: JsonNode) => void) {
    const index = this.doc.index;
    this.keys.forEach((id, key) => callback(index.get(id)!));
  }
 
  /**
   * @ignore
   */
  public child() {
    return undefined;
  }
 
  /**
   * @ignore
   */
  public container(): JsonNode | undefined {
    return this;
  }
 
  /**
   * @ignore
   */
  private _tick: number = 0;
 
  /**
   * @ignore
   */
  private _view = {} as JsonNodeView<Value>;
 
  /**
   * @ignore
   */
  public view(): JsonNodeView<Value> {
    const doc = this.doc;
    const tick = doc.clock.time + doc.tick;
    const _view = this._view;
    if (this._tick === tick) return _view;
    const view = {} as JsonNodeView<Value>;
    const index = doc.index;
    let useCache = true;
    this.keys.forEach((id, key) => {
      const valueNode = index.get(id);
      if (!valueNode) {
        useCache = false;
        return;
      }
      const value = valueNode.view();
      if (value !== undefined) {
        if (_view[key] !== value) useCache = false;
        (<any>view)[key] = value;
      } else if (_view[key] !== undefined) useCache = false;
    });
    return useCache ? _view : ((this._tick = tick), (this._view = view));
  }
 
  /**
   * @ignore
   */
  public api: undefined | unknown = undefined;
 
  public name(): string {
    return 'obj';
  }
 
  // ---------------------------------------------------------------- Printable
 
  public toString(tab: string = ''): string {
    const header = this.name() + ' ' + printTs(this.id);
    return (
      header +
      printTree(
        tab,
        [...this.keys.entries()]
          .filter(([, id]) => !!this.doc.index.get(id))
          .map(
            ([key, id]) =>
              (tab) =>
                JSON.stringify(key) + printTree(tab + ' ', [(tab) => this.doc.index.get(id)!.toString(tab)]),
          ),
      )
    );
  }
}