All files / json-patch/op OpMerge.ts

90.47% Statements 38/42
81.81% Branches 18/22
87.5% Functions 7/8
94.11% Lines 32/34

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  15x   15x 15x 15x           15x     55x 55x   55x       7x               35x 35x 25x   20x 20x 20x   20x 20x   20x       20x 20x 20x 10x         10x         10x 10x       8x 8x       4x 4x 4x 4x 4x      
import type {CompactMergeOp, OPCODE_MERGE} from '../codec/compact/types';
import {AbstractOp} from './AbstractOp';
import type {OperationMerge} from '../types';
import {find, isArrayReference, type Path, formatJsonPointer} from '@jsonjoy.com/json-pointer';
import {isTextNode, isElementNode} from '../util';
import {OPCODE} from '../constants';
import type {IMessagePackEncoder} from '@jsonjoy.com/json-pack/lib/msgpack';
 
/**
 * @category JSON Patch Extended
 */
export class OpMerge extends AbstractOp<'merge'> {
  constructor(
    path: Path,
    public readonly pos: number,
    public readonly props: object | null,
  ) {
    super(path);
  }
 
  public op() {
    return 'merge' as const;
  }
 
  public code() {
    return OPCODE.merge;
  }
 
  public apply(doc: unknown) {
    const ref = find(doc, this.path);
    if (!isArrayReference(ref)) throw new Error('INVALID_TARGET');
    if (ref.key <= 0) throw new Error('INVALID_KEY');
 
    const one = ref.obj[ref.key - 1];
    const two = ref.obj[ref.key];
    const merged = this.merge(one, two);
 
    ref.obj[ref.key - 1] = merged;
    ref.obj.splice(ref.key, 1);
 
    return {doc, old: [one, two]};
  }
 
  private merge<T>(one: T, two: T) {
    Iif (typeof one === 'string' && typeof two === 'string') return one + two;
    Iif (typeof one === 'number' && typeof two === 'number') return one + two;
    if (isTextNode(one) && isTextNode(two)) return {...one, ...two, text: one.text + two.text};
    if (isElementNode(one) && isElementNode(two)) return {...one, ...two, children: [...one.children, ...two.children]};
    return [one, two];
  }
 
  public toJson(parent?: AbstractOp): OperationMerge {
    const op: OperationMerge = {
      op: 'merge',
      path: formatJsonPointer(this.path),
      pos: this.pos,
    };
    if (this.props) op.props = this.props;
    return op;
  }
 
  public toCompact(parent: undefined | AbstractOp, verbose: boolean): CompactMergeOp {
    const opcode: OPCODE_MERGE = verbose ? 'merge' : OPCODE.merge;
    return this.props ? [opcode, this.path, this.pos, this.props] : [opcode, this.path, this.pos];
  }
 
  public encode(encoder: IMessagePackEncoder, parent?: AbstractOp) {
    encoder.encodeArrayHeader(this.props ? 4 : 3);
    encoder.writer.u8(OPCODE.merge);
    encoder.encodeArray(this.path as unknown[]);
    encoder.encodeNumber(this.pos);
    if (this.props) encoder.encodeAny(this.props);
  }
}