All files / json-crdt/json-patch JsonPatchStore.ts

92.1% Statements 35/38
100% Branches 6/6
90.9% Functions 10/11
91.42% Lines 32/35

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    2x 2x           2x         30x 30x 30x   30x 30x 30x 30x     30x 30x 30x     30x 5x 5x 5x     30x 3x 3x 2x     30x 13x 13x 8x     30x 6x 6x   3x       48x     4x                           30x    
import type {SyncStore} from '../../util/events/sync-store';
import type {JsonNodeApi} from '../model/api/types';
import {JsonPatch} from './JsonPatch';
import {toPath} from '@jsonjoy.com/json-pointer/lib/util';
import type {Path} from '@jsonjoy.com/json-pointer/lib/types';
import type {Model, NodeApi} from '../model';
import type {Operation, OperationAdd, OperationRemove, OperationReplace} from '../../json-patch';
import type {JsonNode, JsonNodeView} from '../nodes';
 
export class JsonPatchStore<N extends JsonNode = JsonNode<any>> implements SyncStore<Readonly<JsonNodeView<N>>> {
  public readonly patcher: JsonPatch<N>;
  public readonly pfx: string;
 
  constructor(
    public readonly model: Model<N>,
    public readonly path: Path = [],
    public readonly base?: NodeApi<any>,
  ) {
    this.pfx = path.length ? path.join() : '';
    const api = model.api;
    this.patcher = new JsonPatch(model, path, base);
    this.subscribe = (listener) => api.onChange.listen(listener);
  }
 
  public readonly update = (change: Operation | Operation[]): void => {
    const ops = Array.isArray(change) ? change : [change];
    this.patcher.apply(ops);
  };
 
  public readonly add = (path: string | Path, value: unknown): Operation => {
    const op: OperationAdd = {op: 'add', path, value};
    this.update([op]);
    return op;
  };
 
  public readonly replace = (path: string | Path, value: unknown): Operation => {
    const op: OperationReplace = {op: 'replace', path, value};
    this.update([op]);
    return op;
  };
 
  public readonly remove = (path: string | Path): Operation => {
    const op: OperationRemove = {op: 'remove', path};
    this.update([op]);
    return op;
  };
 
  public readonly del = (path: string | Path): Operation | undefined => {
    try {
      return this.remove(path);
    } catch {
      return;
    }
  };
 
  public readonly get = (path: string | Path = ''): unknown => this.patcher.get(path);
 
  public bind(path: string | Path): JsonPatchStore<N> {
    return new JsonPatchStore(this.model, this.path.concat(toPath(path)), this.base);
  }
 
  public api(): JsonNodeApi<N> | undefined {
    try {
      return this.model.api.find(this.path) as unknown as JsonNodeApi<N>;
    } catch {
      return;
    }
  }
 
  // ---------------------------------------------------------------- SyncStore
 
  public readonly subscribe: SyncStore<any>['subscribe'];
  public readonly getSnapshot = () => this.get() as Readonly<JsonNodeView<N>>;
}