All files / json-patch/op OpSplit.ts

96.49% Statements 55/57
95.45% Branches 21/22
87.5% Functions 7/8
98.07% Lines 51/52

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  15x   15x 15x 15x               15x     165x 165x   165x       27x               135x 135x 135x 135x 110x 30x 30x 80x 135x       135x 50x 50x 50x 50x 5x                   5x 85x 35x 35x 35x 35x                       35x 50x 40x 40x 40x 40x                       40x 10x 5x 5x 5x       15x         15x 15x       12x 12x       6x 6x 6x 6x 6x      
import type {CompactSplitOp, OPCODE_SPLIT} from '../codec/compact/types';
import {AbstractOp} from './AbstractOp';
import type {OperationSplit, SlateNode, SlateTextNode, SlateElementNode} from '../types';
import {find, isObjectReference, 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';
 
type Composable = string | number | SlateNode;
 
/**
 * @category JSON Patch Extended
 */
export class OpSplit extends AbstractOp<'split'> {
  constructor(
    path: Path,
    public readonly pos: number,
    public readonly props: object | null,
  ) {
    super(path);
  }
 
  public op() {
    return 'split' as const;
  }
 
  public code() {
    return OPCODE.split;
  }
 
  public apply(doc: unknown) {
    const ref = find(doc, this.path);
    Iif (ref.val === undefined) throw new Error('NOT_FOUND');
    const tuple = this.split(ref.val);
    if (isObjectReference(ref)) ref.obj[ref.key] = tuple;
    else if (isArrayReference(ref)) {
      ref.obj[ref.key] = tuple[0];
      ref.obj.splice(ref.key + 1, 0, tuple[1]);
    } else doc = tuple;
    return {doc, old: ref.val};
  }
 
  private split<T>(node: T): [T | Composable, T | Composable] {
    if (typeof node === 'string') {
      const {pos, props} = this;
      const before = node.slice(0, pos);
      const after = node.slice(pos);
      if (!props) return [before, after];
      const textNodes: [SlateTextNode, SlateTextNode] = [
        {
          ...props,
          text: before,
        },
        {
          ...props,
          text: after,
        },
      ];
      return textNodes;
    } else if (isTextNode(node)) {
      const {pos, props} = this;
      const before = node.text.slice(0, pos);
      const after = node.text.slice(pos);
      const textNodes: [SlateTextNode, SlateTextNode] = [
        {
          ...node,
          ...props,
          text: before,
        },
        {
          ...node,
          ...props,
          text: after,
        },
      ];
      return textNodes;
    } else if (isElementNode(node)) {
      const {pos, props} = this;
      const before = node.children.slice(0, pos);
      const after = node.children.slice(pos);
      const elementNodes: [SlateElementNode, SlateElementNode] = [
        {
          ...node,
          ...props,
          children: before,
        },
        {
          ...node,
          ...props,
          children: after,
        },
      ];
      return elementNodes;
    } else if (typeof node === 'number') {
      const {pos} = this;
      return [pos, node - pos];
    } else return [node, node];
  }
 
  public toJson(parent?: AbstractOp): OperationSplit {
    const op: OperationSplit = {
      op: 'split',
      path: formatJsonPointer(this.path),
      pos: this.pos,
    };
    if (this.props) op.props = this.props;
    return op;
  }
 
  public toCompact(parent: undefined | AbstractOp, verbose: boolean): CompactSplitOp {
    const opcode: OPCODE_SPLIT = verbose ? 'split' : OPCODE.split;
    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.split);
    encoder.encodeArray(this.path as unknown[]);
    encoder.encodeNumber(this.pos);
    if (this.props) encoder.encodeObject(this.props as Record<string, unknown>);
  }
}