@sanity/mutator
Version: 
A set of models to make it easier to utilize the powerful real time collaborative features of Sanity
1 lines • 149 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/document/debug.ts","../src/patch/ImmutableAccessor.ts","../src/util.ts","../src/jsonpath/arrayToJSONMatchPath.ts","../src/jsonpath/descend.ts","../src/jsonpath/tokenize.ts","../src/jsonpath/parse.ts","../src/jsonpath/toPath.ts","../src/jsonpath/Expression.ts","../src/jsonpath/Descender.ts","../src/jsonpath/Matcher.ts","../src/jsonpath/PlainProbe.ts","../src/jsonpath/extractAccessors.ts","../src/jsonpath/extract.ts","../src/jsonpath/extractWithPath.ts","../src/patch/DiffMatchPatch.ts","../src/patch/IncPatch.ts","../src/patch/util.ts","../src/patch/InsertPatch.ts","../src/patch/SetIfMissingPatch.ts","../src/patch/SetPatch.ts","../src/patch/UnsetPatch.ts","../src/patch/parse.ts","../src/patch/Patcher.ts","../src/document/luid.ts","../src/document/Mutation.ts","../src/document/Document.ts","../src/document/SquashingBuffer.ts","../src/document/BufferedDocument.ts"],"sourcesContent":["import debugIt from 'debug'\n\nexport const debug = debugIt('mutator-document')\n","import {type Probe} from '../jsonpath/Probe'\n\n/**\n * An immutable probe/writer for plain JS objects that will never mutate\n * the provided _value in place. Each setter returns a new (wrapped) version\n * of the value.\n */\nexport class ImmutableAccessor implements Probe {\n  _value: unknown\n  path: (string | number)[]\n\n  constructor(value: unknown, path?: (string | number)[]) {\n    this._value = value\n    this.path = path || []\n  }\n\n  containerType(): 'array' | 'object' | 'primitive' {\n    if (Array.isArray(this._value)) {\n      return 'array'\n    } else if (this._value !== null && typeof this._value === 'object') {\n      return 'object'\n    }\n    return 'primitive'\n  }\n\n  // Common reader, supported by all containers\n  get(): unknown {\n    return this._value\n  }\n\n  // Array reader\n  length(): number {\n    if (!Array.isArray(this._value)) {\n      throw new Error(\"Won't return length of non-indexable _value\")\n    }\n\n    return this._value.length\n  }\n\n  getIndex(i: number): ImmutableAccessor | false | null {\n    if (!Array.isArray(this._value)) {\n      return false\n    }\n\n    if (i >= this.length()) {\n      return null\n    }\n\n    return new ImmutableAccessor(this._value[i], this.path.concat(i))\n  }\n\n  // Object reader\n  hasAttribute(key: string): boolean {\n    return isRecord(this._value) ? this._value.hasOwnProperty(key) : false\n  }\n\n  attributeKeys(): string[] {\n    return isRecord(this._value) ? Object.keys(this._value) : []\n  }\n\n  getAttribute(key: string): ImmutableAccessor | null {\n    if (!isRecord(this._value)) {\n      throw new Error('getAttribute only applies to plain objects')\n    }\n\n    if (!this.hasAttribute(key)) {\n      return null\n    }\n\n    return new ImmutableAccessor(this._value[key], this.path.concat(key))\n  }\n\n  // Common writer, supported by all containers\n  set(value: unknown): ImmutableAccessor {\n    return value === this._value ? this : new ImmutableAccessor(value, this.path)\n  }\n\n  // array writer interface\n  setIndex(i: number, value: unknown): ImmutableAccessor {\n    if (!Array.isArray(this._value)) {\n      throw new Error('setIndex only applies to arrays')\n    }\n\n    if (Object.is(value, this._value[i])) {\n      return this\n    }\n\n    const nextValue = this._value.slice()\n    nextValue[i] = value\n    return new ImmutableAccessor(nextValue, this.path)\n  }\n\n  setIndexAccessor(i: number, accessor: ImmutableAccessor): ImmutableAccessor {\n    return this.setIndex(i, accessor.get())\n  }\n\n  unsetIndices(indices: number[]): ImmutableAccessor {\n    if (!Array.isArray(this._value)) {\n      throw new Error('unsetIndices only applies to arrays')\n    }\n\n    const length = this._value.length\n    const nextValue = []\n    // Copy every _value _not_ in the indices array over to the newValue\n    for (let i = 0; i < length; i++) {\n      if (indices.indexOf(i) === -1) {\n        nextValue.push(this._value[i])\n      }\n    }\n    return new ImmutableAccessor(nextValue, this.path)\n  }\n\n  insertItemsAt(pos: number, items: unknown[]): ImmutableAccessor {\n    if (!Array.isArray(this._value)) {\n      throw new Error('insertItemsAt only applies to arrays')\n    }\n\n    let nextValue\n    if (this._value.length === 0 && pos === 0) {\n      nextValue = items\n    } else {\n      nextValue = this._value.slice(0, pos).concat(items).concat(this._value.slice(pos))\n    }\n\n    return new ImmutableAccessor(nextValue, this.path)\n  }\n\n  // Object writer interface\n  setAttribute(key: string, value: unknown): ImmutableAccessor {\n    if (!isRecord(this._value)) {\n      throw new Error('Unable to set attribute of non-object container')\n    }\n\n    if (Object.is(value, this._value[key])) {\n      return this\n    }\n\n    const nextValue = Object.assign({}, this._value, {[key]: value})\n    return new ImmutableAccessor(nextValue, this.path)\n  }\n\n  setAttributeAccessor(key: string, accessor: ImmutableAccessor): ImmutableAccessor {\n    return this.setAttribute(key, accessor.get())\n  }\n\n  unsetAttribute(key: string): ImmutableAccessor {\n    if (!isRecord(this._value)) {\n      throw new Error('Unable to unset attribute of non-object container')\n    }\n\n    const nextValue = Object.assign({}, this._value)\n    delete nextValue[key]\n    return new ImmutableAccessor(nextValue, this.path)\n  }\n}\n\nfunction isRecord(value: unknown): value is {[key: string]: unknown} {\n  return value !== null && typeof value === 'object'\n}\n","export function isRecord(value: unknown): value is {[key: string]: unknown} {\n  return value !== null && typeof value === 'object'\n}\n","import {type Path, type PathSegment} from '@sanity/types'\n\nimport {isRecord} from '../util'\n\nconst IS_DOTTABLE = /^[a-z_$]+/\n\n/**\n * Converts a path in array form to a JSONPath string\n *\n * @param pathArray - Array of path segments\n * @returns String representation of the path\n * @internal\n */\nexport function arrayToJSONMatchPath(pathArray: Path): string {\n  let path = ''\n  pathArray.forEach((segment, index) => {\n    path += stringifySegment(segment, index === 0)\n  })\n  return path\n}\n\n// Converts an array of simple values (strings, numbers only) to a jsonmatch path string.\nfunction stringifySegment(\n  segment: PathSegment | Record<string, unknown>,\n  hasLeading: boolean,\n): string {\n  if (typeof segment === 'number') {\n    return `[${segment}]`\n  }\n\n  if (isRecord(segment)) {\n    const seg = segment as Record<string, unknown>\n    return Object.keys(segment)\n      .map((key) => (isPrimitiveValue(seg[key]) ? `[${key}==\"${seg[key]}\"]` : ''))\n      .join('')\n  }\n\n  if (typeof segment === 'string' && IS_DOTTABLE.test(segment)) {\n    return hasLeading ? segment : `.${segment}`\n  }\n\n  return `['${segment}']`\n}\n\nfunction isPrimitiveValue(val: unknown): val is string | number | boolean {\n  switch (typeof val) {\n    case 'number':\n    case 'string':\n    case 'boolean':\n      return true\n    default:\n      return false\n  }\n}\n","import {type Expr, type PathExpr} from './types'\n\n/**\n * Splits an expression into a set of heads, tails. A head is the next leaf node to\n * check for matches, and a tail is everything that follows it. Matching is done by\n * matching heads, then proceedint to the matching value, splitting the tail into\n * heads and tails and checking the heads against the new value, and so on.\n */\nexport function descend(tail: Expr): [Expr | null, PathExpr | null][] {\n  const [head, newTail] = splitIfPath(tail)\n  if (!head) {\n    throw new Error('Head cannot be null')\n  }\n\n  return spreadIfUnionHead(head, newTail)\n}\n\n// Split path in [head, tail]\nfunction splitIfPath(tail: Expr): [Expr | null, PathExpr | null] {\n  if (tail.type !== 'path') {\n    return [tail, null]\n  }\n\n  const nodes = tail.nodes\n  if (nodes.length === 0) {\n    return [null, null]\n  }\n\n  if (nodes.length === 1) {\n    return [nodes[0], null]\n  }\n\n  return [nodes[0], {type: 'path', nodes: nodes.slice(1)}]\n}\n\nfunction concatPaths(path1: PathExpr | null, path2: PathExpr | null): PathExpr | null {\n  if (!path1 && !path2) {\n    return null\n  }\n\n  const nodes1 = path1 ? path1.nodes : []\n  const nodes2 = path2 ? path2.nodes : []\n  return {\n    type: 'path',\n    nodes: nodes1.concat(nodes2),\n  }\n}\n\n// Spreads a union head into several heads/tails\nfunction spreadIfUnionHead(head: Expr, tail: PathExpr | null): [Expr | null, PathExpr | null][] {\n  if (head.type !== 'union') {\n    return [[head, tail]]\n  }\n\n  return head.nodes.map((node) => {\n    if (node.type === 'path') {\n      const [subHead, subTail] = splitIfPath(node)\n      return [subHead, concatPaths(subTail, tail)]\n    }\n\n    return [node, tail]\n  })\n}\n","import {\n  type IdentifierToken,\n  type NumberToken,\n  type QuotedToken,\n  type SymbolClass,\n  type SymbolToken,\n  type Token,\n} from './types'\n\n// TODO: Support '*'\n\nconst digitChar = /[0-9]/\nconst attributeCharMatcher = /^[a-zA-Z0-9_]$/\nconst attributeFirstCharMatcher = /^[a-zA-Z_]$/\n\nconst symbols: Record<SymbolClass, string[]> = {\n  // NOTE: These are compared against in order of definition,\n  // thus '==' must come before '=', '>=' before '>', etc.\n  operator: ['..', '.', ',', ':', '?'],\n  comparator: ['>=', '<=', '<', '>', '==', '!='],\n  keyword: ['$', '@'],\n  boolean: ['true', 'false'],\n  paren: ['[', ']'],\n}\n\nconst symbolClasses = Object.keys(symbols) as SymbolClass[]\n\ntype TokenizerFn = () => Token | null\n\n/**\n * Tokenizes a jsonpath2 expression\n */\nclass Tokenizer {\n  source: string\n  i: number\n  length: number\n  tokenizers: TokenizerFn[]\n\n  constructor(path: string) {\n    this.source = path\n    this.length = path.length\n    this.i = 0\n    this.tokenizers = [\n      this.tokenizeSymbol,\n      this.tokenizeIdentifier,\n      this.tokenizeNumber,\n      this.tokenizeQuoted,\n    ].map((fn) => fn.bind(this))\n  }\n\n  tokenize(): Token[] {\n    const result: Token[] = []\n    while (!this.EOF()) {\n      this.chompWhitespace()\n      let token: Token | null = null\n      // @todo refactor into a simpler `.find()`?\n      const found = this.tokenizers.some((tokenizer) => {\n        token = tokenizer()\n        return Boolean(token)\n      })\n      if (!found || !token) {\n        throw new Error(`Invalid tokens in jsonpath '${this.source}' @ ${this.i}`)\n      }\n      result.push(token)\n    }\n    return result\n  }\n\n  takeWhile(fn: (character: string) => string | null): string | null {\n    const start = this.i\n    let result = ''\n    while (!this.EOF()) {\n      const nextChar = fn(this.source[this.i])\n      if (nextChar === null) {\n        break\n      }\n      result += nextChar\n      this.i++\n    }\n    if (this.i === start) {\n      return null\n    }\n    return result\n  }\n\n  EOF(): boolean {\n    return this.i >= this.length\n  }\n\n  peek(): string | null {\n    if (this.EOF()) {\n      return null\n    }\n    return this.source[this.i]\n  }\n\n  consume(str: string) {\n    if (this.i + str.length > this.length) {\n      throw new Error(`Expected ${str} at end of jsonpath`)\n    }\n    if (str === this.source.slice(this.i, this.i + str.length)) {\n      this.i += str.length\n    } else {\n      throw new Error(`Expected \"${str}\", but source contained \"${this.source.slice()}`)\n    }\n  }\n\n  // Tries to match the upcoming bit of string with the provided string. If it matches, returns\n  // the string, then advances the read pointer to the next bit. If not, returns null and nothing\n  // happens.\n  tryConsume(str: string) {\n    if (this.i + str.length > this.length) {\n      return null\n    }\n    if (str === this.source.slice(this.i, this.i + str.length)) {\n      // When checking symbols that consist of valid attribute characters, we\n      // need to make sure we don't inadvertently treat an attribute as a\n      // symbol. For example, an attribute 'trueCustomerField' should not be\n      // scanned as the boolean symbol \"true\".\n      if (str[0].match(attributeCharMatcher)) {\n        // check that char following the symbol match is not also an attribute char\n        if (this.length > this.i + str.length) {\n          const nextChar = this.source[this.i + str.length]\n          if (nextChar && nextChar.match(attributeCharMatcher)) {\n            return null\n          }\n        }\n      }\n      this.i += str.length\n      return str\n    }\n    return null\n  }\n\n  chompWhitespace(): void {\n    this.takeWhile((char): string | null => {\n      return char === ' ' ? '' : null\n    })\n  }\n\n  tokenizeQuoted(): QuotedToken | null {\n    const quote = this.peek()\n    if (quote === \"'\" || quote === '\"') {\n      this.consume(quote)\n      let escape = false\n      const inner = this.takeWhile((char) => {\n        if (escape) {\n          escape = false\n          return char\n        }\n        if (char === '\\\\') {\n          escape = true\n          return ''\n        }\n        if (char != quote) {\n          return char\n        }\n        return null\n      })\n      this.consume(quote)\n      return {\n        type: 'quoted',\n        value: inner,\n        quote: quote === '\"' ? 'double' : 'single',\n      }\n    }\n    return null\n  }\n\n  tokenizeIdentifier(): IdentifierToken | null {\n    let first = true\n    const identifier = this.takeWhile((char) => {\n      if (first) {\n        first = false\n        return char.match(attributeFirstCharMatcher) ? char : null\n      }\n      return char.match(attributeCharMatcher) ? char : null\n    })\n    if (identifier !== null) {\n      return {\n        type: 'identifier',\n        name: identifier,\n      }\n    }\n    return null\n  }\n\n  tokenizeNumber(): NumberToken | null {\n    const start = this.i\n    let dotSeen = false\n    let digitSeen = false\n    let negative = false\n    if (this.peek() === '-') {\n      negative = true\n      this.consume('-')\n    }\n    const number = this.takeWhile((char) => {\n      if (char === '.' && !dotSeen && digitSeen) {\n        dotSeen = true\n        return char\n      }\n      digitSeen = true\n      return char.match(digitChar) ? char : null\n    })\n    if (number !== null) {\n      return {\n        type: 'number',\n        value: negative ? -number : +number,\n        raw: negative ? `-${number}` : number,\n      }\n    }\n    // No number, rewind\n    this.i = start\n    return null\n  }\n\n  tokenizeSymbol(): SymbolToken | null {\n    for (const symbolClass of symbolClasses) {\n      const patterns = symbols[symbolClass]\n      const symbol = patterns.find((pattern) => this.tryConsume(pattern))\n      if (symbol) {\n        return {\n          type: symbolClass,\n          symbol,\n        }\n      }\n    }\n\n    return null\n  }\n}\n\nexport function tokenize(jsonpath: string): Token[] {\n  return new Tokenizer(jsonpath).tokenize()\n}\n","// Converts a string into an abstract syntax tree representation\n\nimport {tokenize} from './tokenize'\nimport {\n  type AliasExpr,\n  type AttributeExpr,\n  type BooleanExpr,\n  type ConstraintExpr,\n  type IndexExpr,\n  type NumberExpr,\n  type PathExpr,\n  type RangeExpr,\n  type RecursiveExpr,\n  type StringExpr,\n  type Token,\n  type UnionExpr,\n} from './types'\n\n// TODO: Support '*'\n\nclass Parser {\n  tokens: Token[]\n  length: number\n  i: number\n\n  constructor(path: string) {\n    this.tokens = tokenize(path)\n    this.length = this.tokens.length\n    this.i = 0\n  }\n\n  parse() {\n    return this.parsePath()\n  }\n\n  EOF() {\n    return this.i >= this.length\n  }\n\n  // Look at upcoming token\n  peek() {\n    if (this.EOF()) {\n      return null\n    }\n    return this.tokens[this.i]\n  }\n\n  consume() {\n    const result = this.peek()\n    this.i += 1\n    return result\n  }\n\n  // Return next token if it matches the pattern\n  probe(pattern: Record<string, unknown>): Token | null {\n    const token = this.peek()\n    if (!token) {\n      return null\n    }\n\n    const record = token as unknown as Record<string, unknown>\n    const match = Object.keys(pattern).every((key) => {\n      return key in token && pattern[key] === record[key]\n    })\n\n    return match ? token : null\n  }\n\n  // Return and consume next token if it matches the pattern\n  match(pattern: Partial<Token>): Token | null {\n    return this.probe(pattern) ? this.consume() : null\n  }\n\n  parseAttribute(): AttributeExpr | null {\n    const token = this.match({type: 'identifier'})\n    if (token && token.type === 'identifier') {\n      return {\n        type: 'attribute',\n        name: token.name,\n      }\n    }\n    const quoted = this.match({type: 'quoted', quote: 'single'})\n    if (quoted && quoted.type === 'quoted') {\n      return {\n        type: 'attribute',\n        name: quoted.value || '',\n      }\n    }\n    return null\n  }\n\n  parseAlias(): AliasExpr | null {\n    if (this.match({type: 'keyword', symbol: '@'}) || this.match({type: 'keyword', symbol: '$'})) {\n      return {\n        type: 'alias',\n        target: 'self',\n      }\n    }\n    return null\n  }\n\n  parseNumber(): NumberExpr | null {\n    const token = this.match({type: 'number'})\n    if (token && token.type === 'number') {\n      return {\n        type: 'number',\n        value: token.value,\n      }\n    }\n    return null\n  }\n\n  parseNumberValue(): number | null {\n    const expr = this.parseNumber()\n    if (expr) {\n      return expr.value\n    }\n    return null\n  }\n\n  parseSliceSelector(): RangeExpr | IndexExpr | null {\n    const start = this.i\n    const rangeStart = this.parseNumberValue()\n\n    const colon1 = this.match({type: 'operator', symbol: ':'})\n    if (!colon1) {\n      if (rangeStart === null) {\n        // Rewind, this was actually nothing\n        this.i = start\n        return null\n      }\n\n      // Unwrap, this was just a single index not followed by colon\n      return {type: 'index', value: rangeStart}\n    }\n\n    const result: RangeExpr = {\n      type: 'range',\n      start: rangeStart,\n      end: this.parseNumberValue(),\n    }\n\n    const colon2 = this.match({type: 'operator', symbol: ':'})\n    if (colon2) {\n      result.step = this.parseNumberValue()\n    }\n\n    if (result.start === null && result.end === null) {\n      // rewind, this wasnt' a slice selector\n      this.i = start\n      return null\n    }\n\n    return result\n  }\n\n  parseValueReference(): AttributeExpr | RangeExpr | IndexExpr | null {\n    return this.parseAttribute() || this.parseSliceSelector()\n  }\n\n  parseLiteralValue(): StringExpr | BooleanExpr | NumberExpr | null {\n    const literalString = this.match({type: 'quoted', quote: 'double'})\n    if (literalString && literalString.type === 'quoted') {\n      return {\n        type: 'string',\n        value: literalString.value || '',\n      }\n    }\n    const literalBoolean = this.match({type: 'boolean'})\n    if (literalBoolean && literalBoolean.type === 'boolean') {\n      return {\n        type: 'boolean',\n        value: literalBoolean.symbol === 'true',\n      }\n    }\n    return this.parseNumber()\n  }\n\n  // TODO: Reorder constraints so that literal value is always on rhs, and variable is always\n  // on lhs.\n  parseFilterExpression(): ConstraintExpr | null {\n    const start = this.i\n    const expr = this.parseAttribute() || this.parseAlias()\n    if (!expr) {\n      return null\n    }\n\n    if (this.match({type: 'operator', symbol: '?'})) {\n      return {\n        type: 'constraint',\n        operator: '?',\n        lhs: expr,\n      }\n    }\n\n    const binOp = this.match({type: 'comparator'})\n    if (!binOp || binOp.type !== 'comparator') {\n      // No expression, rewind!\n      this.i = start\n      return null\n    }\n\n    const lhs = expr\n    const rhs = this.parseLiteralValue()\n    if (!rhs) {\n      throw new Error(`Operator ${binOp.symbol} needs a literal value at the right hand side`)\n    }\n\n    return {\n      type: 'constraint',\n      operator: binOp.symbol,\n      lhs: lhs,\n      rhs: rhs,\n    }\n  }\n\n  parseExpression(): ConstraintExpr | AttributeExpr | RangeExpr | IndexExpr | null {\n    return this.parseFilterExpression() || this.parseValueReference()\n  }\n\n  parseUnion(): UnionExpr | null {\n    if (!this.match({type: 'paren', symbol: '['})) {\n      return null\n    }\n\n    const terms = []\n    let expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference()\n    while (expr) {\n      terms.push(expr)\n      // End of union?\n      if (this.match({type: 'paren', symbol: ']'})) {\n        break\n      }\n\n      if (!this.match({type: 'operator', symbol: ','})) {\n        throw new Error('Expected ]')\n      }\n\n      expr = this.parseFilterExpression() || this.parsePath() || this.parseValueReference()\n      if (!expr) {\n        throw new Error(\"Expected expression following ','\")\n      }\n    }\n\n    return {\n      type: 'union',\n      nodes: terms,\n    }\n  }\n\n  parseRecursive(): RecursiveExpr | null {\n    if (!this.match({type: 'operator', symbol: '..'})) {\n      return null\n    }\n\n    const subpath = this.parsePath()\n    if (!subpath) {\n      throw new Error(\"Expected path following '..' operator\")\n    }\n\n    return {\n      type: 'recursive',\n      term: subpath,\n    }\n  }\n\n  parsePath(): PathExpr | AttributeExpr | UnionExpr | RecursiveExpr | null {\n    const nodes: (AttributeExpr | UnionExpr | RecursiveExpr)[] = []\n    const expr = this.parseAttribute() || this.parseUnion() || this.parseRecursive()\n    if (!expr) {\n      return null\n    }\n\n    nodes.push(expr)\n    while (!this.EOF()) {\n      if (this.match({type: 'operator', symbol: '.'})) {\n        const attr = this.parseAttribute()\n        if (!attr) {\n          throw new Error(\"Expected attribute name following '.\")\n        }\n        nodes.push(attr)\n        continue\n      } else if (this.probe({type: 'paren', symbol: '['})) {\n        const union = this.parseUnion()\n        if (!union) {\n          throw new Error(\"Expected union following '['\")\n        }\n        nodes.push(union)\n      } else {\n        const recursive = this.parseRecursive()\n        if (recursive) {\n          nodes.push(recursive)\n        }\n        break\n      }\n    }\n\n    if (nodes.length === 1) {\n      return nodes[0]\n    }\n\n    return {\n      type: 'path',\n      nodes: nodes,\n    }\n  }\n}\n\nexport function parseJsonPath(path: string): PathExpr | AttributeExpr | UnionExpr | RecursiveExpr {\n  const parsed = new Parser(path).parse()\n  if (!parsed) {\n    throw new Error(`Failed to parse JSON path \"${path}\"`)\n  }\n  return parsed\n}\n","import {type Expr} from './types'\n\n/**\n * Converts a parsed expression back into jsonpath2, roughly -\n * mostly for use with tests.\n *\n * @param expr - Expression to convert to path\n * @returns a string representation of the path\n * @internal\n */\nexport function toPath(expr: Expr): string {\n  return toPathInner(expr, false)\n}\n\nfunction toPathInner(expr: Expr, inUnion: boolean): string {\n  switch (expr.type) {\n    case 'attribute':\n      return expr.name\n    case 'alias':\n      return expr.target === 'self' ? '@' : '$'\n    case 'number':\n      return `${expr.value}`\n    case 'range': {\n      const result = []\n      if (!inUnion) {\n        result.push('[')\n      }\n      if (expr.start) {\n        result.push(`${expr.start}`)\n      }\n      result.push(':')\n      if (expr.end) {\n        result.push(`${expr.end}`)\n      }\n      if (expr.step) {\n        result.push(`:${expr.step}`)\n      }\n      if (!inUnion) {\n        result.push(']')\n      }\n      return result.join('')\n    }\n    case 'index':\n      if (inUnion) {\n        return `${expr.value}`\n      }\n\n      return `[${expr.value}]`\n    case 'constraint': {\n      const rhs = expr.rhs ? ` ${toPathInner(expr.rhs, false)}` : ''\n      const inner = `${toPathInner(expr.lhs, false)} ${expr.operator}${rhs}`\n\n      if (inUnion) {\n        return inner\n      }\n\n      return `[${inner}]`\n    }\n    case 'string':\n      return JSON.stringify(expr.value)\n    case 'path': {\n      const result = []\n      const nodes = expr.nodes.slice()\n      while (nodes.length > 0) {\n        const node = nodes.shift()\n        if (node) {\n          result.push(toPath(node))\n        }\n\n        const upcoming = nodes[0]\n        if (upcoming && toPathInner(upcoming, false)[0] !== '[') {\n          result.push('.')\n        }\n      }\n      return result.join('')\n    }\n    case 'union':\n      return `[${expr.nodes.map((e) => toPathInner(e, true)).join(',')}]`\n    default:\n      throw new Error(`Unknown node type ${expr.type}`)\n    case 'recursive':\n      return `..${toPathInner(expr.term, false)}`\n  }\n}\n","// A utility wrapper class to process parsed jsonpath expressions\n\nimport {descend} from './descend'\nimport {parseJsonPath} from './parse'\nimport {type Probe} from './Probe'\nimport {toPath} from './toPath'\nimport {type Expr, type HeadTail} from './types'\n\nexport interface Range {\n  start: number\n  end: number\n  step: number\n}\n\nexport class Expression {\n  expr: Expr\n\n  constructor(expr: Expr | Expression | null) {\n    if (!expr) {\n      throw new Error('Attempted to create Expression from null-value')\n    }\n\n    // This is a wrapped expr\n    if ('expr' in expr) {\n      this.expr = expr.expr\n    } else {\n      this.expr = expr\n    }\n\n    if (!('type' in this.expr)) {\n      throw new Error('Attempt to create Expression for expression with no type')\n    }\n  }\n\n  isPath(): boolean {\n    return this.expr.type === 'path'\n  }\n\n  isUnion(): boolean {\n    return this.expr.type === 'union'\n  }\n\n  isCollection(): boolean {\n    return this.isPath() || this.isUnion()\n  }\n\n  isConstraint(): boolean {\n    return this.expr.type === 'constraint'\n  }\n\n  isRecursive(): boolean {\n    return this.expr.type === 'recursive'\n  }\n\n  isExistenceConstraint(): boolean {\n    return this.expr.type === 'constraint' && this.expr.operator === '?'\n  }\n\n  isIndex(): boolean {\n    return this.expr.type === 'index'\n  }\n\n  isRange(): boolean {\n    return this.expr.type === 'range'\n  }\n\n  expandRange(probe?: Probe): Range {\n    const probeLength = () => {\n      if (!probe) {\n        throw new Error('expandRange() required a probe that was not passed')\n      }\n\n      return probe.length()\n    }\n\n    let start = 'start' in this.expr ? this.expr.start || 0 : 0\n    start = interpretNegativeIndex(start, probe)\n    let end = 'end' in this.expr ? this.expr.end || probeLength() : probeLength()\n    end = interpretNegativeIndex(end, probe)\n    const step = 'step' in this.expr ? this.expr.step || 1 : 1\n    return {start, end, step}\n  }\n\n  isAttributeReference(): boolean {\n    return this.expr.type === 'attribute'\n  }\n\n  // Is a range or index -> something referencing indexes\n  isIndexReference(): boolean {\n    return this.isIndex() || this.isRange()\n  }\n\n  name(): string {\n    return 'name' in this.expr ? this.expr.name : ''\n  }\n\n  isSelfReference(): boolean {\n    return this.expr.type === 'alias' && this.expr.target === 'self'\n  }\n\n  constraintTargetIsSelf(): boolean {\n    return (\n      this.expr.type === 'constraint' &&\n      this.expr.lhs.type === 'alias' &&\n      this.expr.lhs.target === 'self'\n    )\n  }\n\n  constraintTargetIsAttribute(): boolean {\n    return this.expr.type === 'constraint' && this.expr.lhs.type === 'attribute'\n  }\n\n  testConstraint(probe: Probe): boolean {\n    const expr = this.expr\n\n    if (expr.type === 'constraint' && expr.lhs.type === 'alias' && expr.lhs.target === 'self') {\n      if (probe.containerType() !== 'primitive') {\n        return false\n      }\n\n      if (expr.type === 'constraint' && expr.operator === '?') {\n        return true\n      }\n\n      const lhs = probe.get()\n      const rhs = expr.rhs && 'value' in expr.rhs ? expr.rhs.value : undefined\n      return testBinaryOperator(lhs, expr.operator, rhs)\n    }\n\n    if (expr.type !== 'constraint') {\n      return false\n    }\n\n    const lhs = expr.lhs\n    if (!lhs) {\n      throw new Error('No LHS of expression')\n    }\n\n    if (lhs.type !== 'attribute') {\n      throw new Error(`Constraint target ${lhs.type} not supported`)\n    }\n\n    if (probe.containerType() !== 'object') {\n      return false\n    }\n\n    const lhsValue = probe.getAttribute(lhs.name)\n    if (lhsValue === undefined || lhsValue === null || lhsValue.containerType() !== 'primitive') {\n      // LHS is void and empty, or it is a collection\n      return false\n    }\n\n    if (this.isExistenceConstraint()) {\n      // There is no rhs, and if we're here the key did exist\n      return true\n    }\n\n    const rhs = expr.rhs && 'value' in expr.rhs ? expr.rhs.value : undefined\n    return testBinaryOperator(lhsValue.get(), expr.operator, rhs)\n  }\n\n  pathNodes(): Expr[] {\n    return this.expr.type === 'path' ? this.expr.nodes : [this.expr]\n  }\n\n  prepend(node: Expression): Expression {\n    if (!node) {\n      return this\n    }\n\n    return new Expression({\n      type: 'path',\n      nodes: node.pathNodes().concat(this.pathNodes()),\n    })\n  }\n\n  concat(other: Expression | null): Expression {\n    return other ? other.prepend(this) : this\n  }\n\n  descend(): HeadTail[] {\n    return descend(this.expr).map((headTail) => {\n      const [head, tail] = headTail\n      return {\n        head: head ? new Expression(head) : null,\n        tail: tail ? new Expression(tail) : null,\n      }\n    })\n  }\n\n  unwrapRecursive(): Expression {\n    if (this.expr.type !== 'recursive') {\n      throw new Error(`Attempt to unwrap recursive on type ${this.expr.type}`)\n    }\n\n    return new Expression(this.expr.term)\n  }\n\n  toIndicies(probe?: Probe): number[] {\n    if (this.expr.type !== 'index' && this.expr.type !== 'range') {\n      throw new Error('Node cannot be converted to indexes')\n    }\n\n    if (this.expr.type === 'index') {\n      return [interpretNegativeIndex(this.expr.value, probe)]\n    }\n\n    const result: number[] = []\n    const range = this.expandRange(probe)\n    let {start, end} = range\n    if (range.step < 0) {\n      ;[start, end] = [end, start]\n    }\n\n    for (let i = start; i < end; i++) {\n      result.push(i)\n    }\n\n    return result\n  }\n\n  toFieldReferences(): number[] | string[] {\n    if (this.isIndexReference()) {\n      return this.toIndicies()\n    }\n    if (this.expr.type === 'attribute') {\n      return [this.expr.name]\n    }\n    throw new Error(`Can't convert ${this.expr.type} to field references`)\n  }\n\n  toString(): string {\n    return toPath(this.expr)\n  }\n\n  static fromPath(path: string): Expression {\n    const parsed = parseJsonPath(path)\n    if (!parsed) {\n      throw new Error(`Failed to parse path \"${path}\"`)\n    }\n\n    return new Expression(parsed)\n  }\n\n  static attributeReference(name: string): Expression {\n    return new Expression({\n      type: 'attribute',\n      name: name,\n    })\n  }\n\n  static indexReference(i: number): Expression {\n    return new Expression({\n      type: 'index',\n      value: i,\n    })\n  }\n}\n\n// Tests an operator on two given primitive values\nfunction testBinaryOperator(lhsValue: any, operator: string, rhsValue: any) {\n  switch (operator) {\n    case '>':\n      return lhsValue > rhsValue\n    case '>=':\n      return lhsValue >= rhsValue\n    case '<':\n      return lhsValue < rhsValue\n    case '<=':\n      return lhsValue <= rhsValue\n    case '==':\n      return lhsValue === rhsValue\n    case '!=':\n      return lhsValue !== rhsValue\n    default:\n      throw new Error(`Unsupported binary operator ${operator}`)\n  }\n}\n\nfunction interpretNegativeIndex(index: number, probe?: Probe): number {\n  if (index >= 0) {\n    return index\n  }\n\n  if (!probe) {\n    throw new Error('interpretNegativeIndex() must have a probe when < 0')\n  }\n\n  return index + probe.length()\n}\n","import {flatten} from 'lodash'\n\nimport {Expression} from './Expression'\nimport {type Probe} from './Probe'\n\n/**\n * Descender models the state of one partial jsonpath evaluation. Head is the\n * next thing to match, tail is the upcoming things once the head is matched.\n */\nexport class Descender {\n  head: Expression | null\n  tail: Expression | null\n\n  constructor(head: Expression | null, tail: Expression | null) {\n    this.head = head\n    this.tail = tail\n  }\n\n  // Iterate this descender once processing any constraints that are\n  // resolvable on the current value. Returns an array of new descenders\n  // that are guaranteed to be without constraints in the head\n  iterate(probe: Probe): Descender[] {\n    let result: Descender[] = [this]\n    if (this.head && this.head.isConstraint()) {\n      let anyConstraints = true\n      // Keep rewriting constraints until there are none left\n      while (anyConstraints) {\n        result = flatten(\n          result.map((descender) => {\n            return descender.iterateConstraints(probe)\n          }),\n        )\n        anyConstraints = result.some((descender) => {\n          return descender.head && descender.head.isConstraint()\n        })\n      }\n    }\n    return result\n  }\n\n  isRecursive(): boolean {\n    return Boolean(this.head && this.head.isRecursive())\n  }\n\n  hasArrived(): boolean {\n    return this.head === null && this.tail === null\n  }\n\n  extractRecursives(): Descender[] {\n    if (this.head && this.head.isRecursive()) {\n      const term = this.head.unwrapRecursive()\n      return new Descender(null, term.concat(this.tail)).descend()\n    }\n    return []\n  }\n\n  iterateConstraints(probe: Probe): Descender[] {\n    const head = this.head\n    if (head === null || !head.isConstraint()) {\n      // Not a constraint, no rewrite\n      return [this]\n    }\n\n    const result: Descender[] = []\n\n    if (probe.containerType() === 'primitive' && head.constraintTargetIsSelf()) {\n      if (head.testConstraint(probe)) {\n        result.push(...this.descend())\n      }\n      return result\n    }\n\n    // The value is an array\n    if (probe.containerType() === 'array') {\n      const length = probe.length()\n      for (let i = 0; i < length; i++) {\n        // Push new descenders with constraint translated to literal indices\n        // where they match\n        const constraint = probe.getIndex(i)\n        if (constraint && head.testConstraint(constraint)) {\n          result.push(new Descender(new Expression({type: 'index', value: i}), this.tail))\n        }\n      }\n      return result\n    }\n\n    // The value is an object\n    if (probe.containerType() === 'object') {\n      if (head.constraintTargetIsSelf()) {\n        // There are no matches for target self ('@') on a plain object\n        return []\n      }\n\n      if (head.testConstraint(probe)) {\n        return this.descend()\n      }\n\n      return result\n    }\n\n    return result\n  }\n\n  descend(): Descender[] {\n    if (!this.tail) {\n      return [new Descender(null, null)]\n    }\n\n    return this.tail.descend().map((ht) => {\n      return new Descender(ht.head, ht.tail)\n    })\n  }\n\n  toString(): string {\n    const result = ['<']\n    if (this.head) {\n      result.push(this.head.toString())\n    }\n    result.push('|')\n    if (this.tail) {\n      result.push(this.tail.toString())\n    }\n    result.push('>')\n    return result.join('')\n  }\n}\n","import {Descender} from './Descender'\nimport {Expression} from './Expression'\nimport {parseJsonPath} from './parse'\nimport {type Probe} from './Probe'\n\ninterface Result<P = unknown> {\n  leads: {\n    target: Expression\n    matcher: Matcher\n  }[]\n\n  delivery?: {\n    targets: Expression[]\n    payload: P\n  }\n}\n\n/**\n * @internal\n */\nexport class Matcher {\n  active: Descender[]\n  recursives: Descender[]\n  payload: unknown\n\n  constructor(active: Descender[], parent?: Matcher) {\n    this.active = active || []\n    if (parent) {\n      this.recursives = parent.recursives\n      this.payload = parent.payload\n    } else {\n      this.recursives = []\n    }\n    this.extractRecursives()\n  }\n\n  setPayload(payload: unknown): this {\n    this.payload = payload\n    return this\n  }\n\n  // Moves any recursive descenders onto the recursive track, removing them from\n  // the active set\n  extractRecursives(): void {\n    this.active = this.active.filter((descender) => {\n      if (descender.isRecursive()) {\n        this.recursives.push(...descender.extractRecursives())\n        return false\n      }\n      return true\n    })\n  }\n\n  // Find recursives that are relevant now and should be considered part of the active set\n  activeRecursives(probe: Probe): Descender[] {\n    return this.recursives.filter((descender) => {\n      const head = descender.head\n      if (!head) {\n        return false\n      }\n\n      // Constraints are always relevant\n      if (head.isConstraint()) {\n        return true\n      }\n\n      // Index references are only relevant for indexable values\n      if (probe.containerType() === 'array' && head.isIndexReference()) {\n        return true\n      }\n\n      // Attribute references are relevant for plain objects\n      if (probe.containerType() === 'object') {\n        return head.isAttributeReference() && probe.hasAttribute(head.name())\n      }\n\n      return false\n    })\n  }\n\n  match(probe: Probe): Result {\n    return this.iterate(probe).extractMatches(probe)\n  }\n\n  iterate(probe: Probe): Matcher {\n    const newActiveSet: Descender[] = []\n    this.active.concat(this.activeRecursives(probe)).forEach((descender) => {\n      newActiveSet.push(...descender.iterate(probe))\n    })\n    return new Matcher(newActiveSet, this)\n  }\n\n  // Returns true if any of the descenders in the active or recursive set\n  // consider the current state a final destination\n  isDestination(): boolean {\n    return this.active.some((descender) => descender.hasArrived())\n  }\n\n  hasRecursives(): boolean {\n    return this.recursives.length > 0\n  }\n\n  // Returns any payload delivieries and leads that needs to be followed to complete\n  // the process.\n  extractMatches(probe: Probe): Result {\n    const leads: {target: Expression; matcher: Matcher}[] = []\n    const targets: Expression[] = []\n    this.active.forEach((descender) => {\n      if (descender.hasArrived()) {\n        // This was already arrived, so matches this value, not descenders\n        targets.push(\n          new Expression({\n            type: 'alias',\n            target: 'self',\n          }),\n        )\n        return\n      }\n\n      const descenderHead = descender.head\n      if (!descenderHead) {\n        return\n      }\n\n      if (probe.containerType() === 'array' && !descenderHead.isIndexReference()) {\n        // This descender does not match an indexable value\n        return\n      }\n\n      if (probe.containerType() === 'object' && !descenderHead.isAttributeReference()) {\n        // This descender never match a plain object\n        return\n      }\n\n      if (descender.tail) {\n        // Not arrived yet\n        const matcher = new Matcher(descender.descend(), this)\n        descenderHead.toFieldReferences().forEach(() => {\n          leads.push({\n            target: descenderHead,\n            matcher: matcher,\n          })\n        })\n      } else {\n        // arrived\n        targets.push(descenderHead)\n      }\n    })\n\n    // If there are recursive terms, we need to add a lead for every descendant ...\n    if (this.hasRecursives()) {\n      // The recustives matcher will have no active set, only inherit recursives from this\n      const recursivesMatcher = new Matcher([], this)\n      if (probe.containerType() === 'array') {\n        const length = probe.length()\n        for (let i = 0; i < length; i++) {\n          leads.push({\n            target: Expression.indexReference(i),\n            matcher: recursivesMatcher,\n          })\n        }\n      } else if (probe.containerType() === 'object') {\n        probe.attributeKeys().forEach((name) => {\n          leads.push({\n            target: Expression.attributeReference(name),\n            matcher: recursivesMatcher,\n          })\n        })\n      }\n    }\n\n    return targets.length > 0\n      ? {leads: leads, delivery: {targets, payload: this.payload}}\n      : {leads: leads}\n  }\n\n  static fromPath(jsonpath: string): Matcher {\n    const path = parseJsonPath(jsonpath)\n    if (!path) {\n      throw new Error(`Failed to parse path from \"${jsonpath}\"`)\n    }\n\n    const descender = new Descender(null, new Expression(path))\n    return new Matcher(descender.descend())\n  }\n}\n","import {isRecord} from '../util'\nimport {type Probe} from './Probe'\n\n// A default implementation of a probe for vanilla JS _values\nexport class PlainProbe implements Probe {\n  _value: unknown\n  path: (string | number)[]\n\n  constructor(value: unknown, path?: (string | number)[]) {\n    this._value = value\n    this.path = path || []\n  }\n\n  containerType(): 'array' | 'object' | 'primitive' {\n    if (Array.isArray(this._value)) {\n      return 'array'\n    } else if (this._value !== null && typeof this._value === 'object') {\n      return 'object'\n    }\n    return 'primitive'\n  }\n\n  length(): number {\n    if (!Array.isArray(this._value)) {\n      throw new Error(\"Won't return length of non-indexable _value\")\n    }\n\n    return this._value.length\n  }\n\n  getIndex(i: number): false | null | PlainProbe {\n    if (!Array.isArray(this._value)) {\n      return false\n    }\n\n    if (i >= this.length()) {\n      return null\n    }\n\n    return new PlainProbe(this._value[i], this.path.concat(i))\n  }\n\n  hasAttribute(key: string): boolean {\n    if (!isRecord(this._value)) {\n      return false\n    }\n\n    return this._value.hasOwnProperty(key)\n  }\n\n  attributeKeys(): string[] {\n    return isRecord(this._value) ? Object.keys(this._value) : []\n  }\n\n  getAttribute(key: string): null | PlainProbe {\n    if (!isRecord(this._value)) {\n      throw new Error('getAttribute only applies to plain objects')\n    }\n\n    if (!this.hasAttribute(key)) {\n      return null\n    }\n\n    return new PlainProbe(this._value[key], this.path.concat(key))\n  }\n\n  get(): unknown {\n    return this._value\n  }\n}\n","import {compact} from 'lodash'\n\nimport {type Expression} from './Expression'\nimport {Matcher} from './Matcher'\nimport {PlainProbe} from './PlainProbe'\nimport {type Probe} from './Probe'\n\nexport function extractAccessors(path: string, value: unknown): Probe[] {\n  const result: Probe[] = []\n  const matcher = Matcher.fromPath(path).setPayload(function appendResult(values: Probe[]) {\n    result.push(...values)\n  })\n  const accessor = new PlainProbe(value)\n  descend(matcher, accessor)\n  return result\n}\n\nfunction descend(matcher: Matcher, accessor: Probe) {\n  const {leads, delivery} = matcher.match(accessor)\n\n  leads.forEach((lead) => {\n    accessorsFromTarget(lead.target, accessor).forEach((childAccessor) => {\n      descend(lead.matcher, childAccessor)\n    })\n  })\n\n  if (delivery) {\n    delivery.targets.forEach((target) => {\n      if (typeof delivery.payload === 'function') {\n        delivery.payload(accessorsFromTarget(target, accessor))\n      }\n    })\n  }\n}\n\nfunction accessorsFromTarget(target: Expression, accessor: Probe) {\n  const result = []\n  if (target.isIndexReference()) {\n    target.toIndicies(accessor).forEach((i) => {\n      result.push(accessor.getIndex(i))\n    })\n  } else if (target.isAttributeReference()) {\n    result.push(accessor.getAttribute(target.name()))\n  } else if (target.isSelfReference()) {\n    result.push(accessor)\n  } else {\n    throw new Error(`Unable to derive accessor for target ${target.toString()}`)\n  }\n  return compact(result)\n}\n","import {extractAccessors} from './extractAccessors'\n\n/**\n * Extracts values matching the given JsonPath\n *\n * @param path - Path to extract\n * @param value - Value to extract from\n * @returns An array of values matching the given path\n * @public\n */\nexport function extract(path: string, value: unknown): unknown[] {\n  const accessors = extractAccessors(path, value)\n  return accessors.map((acc) => acc.get())\n}\n","import {extractAccessors} from './extractAccessors'\n\n/**\n * Extracts a value for the given JsonPath, and includes the specific path of where it was found\n *\n * @param path - Path to extract\n * @param value - Value to extract from\n * @returns An array of objects with `path` and `value` keys\n * @internal\n */\nexport function extractWithPath(\n  path: string,\n  value: unknown,\n): {path: (string | number)[]; value: unknown}[] {\n  const accessors = extractAccessors(path, value)\n  return accessors.map((acc) => ({path: acc.path, value: acc.get()}))\n}\n","import {applyPatches, parsePatch, type Patch} from '@sanity/diff-match-patch'\n\nimport {type Expression} from '../jsonpath'\nimport {type ImmutableAccessor} from './ImmutableAccessor'\n\nfunction applyPatch(patch: Patch[], oldValue: unknown) {\n  // Silently avoid patching if the value type is not string\n  if (typeof oldValue !== 'string') return oldValue\n  const [result] = applyPatches(patch, oldValue, {allowExceedingIndices: true})\n  return result\n}\n\nexport class DiffMatchPatch {\n  path: string\n  dmpPatch: Patch[]\n  id: string\n\n  constructor(id: string, path: string, dmpPatchSrc: string) {\n    this.id = id\n    this.path = path\n    this.dmpPatch = parsePatch(dmpPatchSrc)\n  }\n\n  apply(targets: Expression[], accessor: ImmutableAccessor): ImmutableAccessor {\n    let result = accessor\n\n    // The target must be a container type\n    if (result.containerType() === 'primitive') {\n      return result\n    }\n\n    for (const target of targets) {\n      if (target.isIndexReference()) {\n        for (const index of target.toIndicies(accessor)) {\n          // Skip patching unless the index actually currently exists\n          const item = result.getIndex(index)\n          if (!item) {\n            continue\n          }\n\n          const oldValue = item.get()\n          const nextValue = applyPatch(this.dmpPatch, oldValue)\n          result = result.setIndex(index, nextValue)\n        }\n\n        continue\n      }\n\n      if (target.isAttributeReference() && result.hasAttribute(target.name())) {\n        const attribute = result.getAttribute(target.name())\n        if (!attribute) {\n          continue\n        }\n\n        const oldValue = attribute.get()\n        const nextValue = applyPatch(this.dmpPatch, oldValue)\n        result = result.setAttribute(target.name(), nextValue)\n        continue\n      }\n\n      throw new Error(`Unable to apply diffMatchPatch to target ${target.toString()}`)\n    }\n\n    return result\n  }\n}\n","import {type Expression} from '../jsonpath'\nimport {type ImmutableAccessor} from './ImmutableAccessor'\n\nfunction performIncrement(previousValue: unknown, delta: number): number {\n  if (typeof previousValue !== 'number' || !Number.isFinite(previousValue)) {\n    return previousValue as number\n  }\n\n  return previousValue + delta\n}\n\nexport class IncPatch {\n  path: string\n  value: number\n  id: string\n\n  constructor(id: string, path: string, value: number) {\n    this.path = path\n    this.value = value\n    this.id = id\n  }\n\n  apply(targets: Expression[], accessor: ImmutableAccessor): ImmutableAccessor {\n    let result = accessor\n\n    // The target must be a container type\n    if (result.containerType() === 'primitive') {\n      return result\n    }\n\n    for (const target of targets) {\n      if (target.isIndexReference()) {\n        for (const index of target.toIndicies(accessor)) {\n          // Skip patching unless the index actually currently exists\n          const item = result.getIndex(index)\n          if (!item) {\n            continue\n          }\n\n          const previousValue = item.get()\n          result = result.setIndex(index, performIncrement(previousValue, this.value))\n        }\n\n        continue\n      }\n\n      if (target.isAttributeReference()) {\n        const attribute = result.getAttribute(target.name())\n        if (!attribute) {\n          continue\n        }\n\n        const previousValue = attribute.get()\n        result = result.setAttribute(target.name(), performIncrement(previousValue, this.value))\n        continue\n      }\n\n      throw new Error(`Unable to apply to target ${target.toString()}`)\n    }\n\n    return result\n  }\n}\n","import {type Expression} from '../jsonpath'\nimport {type ImmutableAccessor} from './ImmutableAccessor'\n\nexport function targetsToIndicies(targets: Expression[], accessor: ImmutableAccessor): number[] {\n  const result: number[] = []\n  targets.forEach((target) => {\n    if (target.isIndexReference()) {\n      result.push(...target.toIndicies(accessor))\n    }\n  })\n  return result.sort()\n}\n","import {max, min} from 'lodash'\n\nimport {type Expression} from '../jsonpath'\nimport {type ImmutableAccessor} from './ImmutableAccessor'\nimport {targetsToIndicies} from './util'\n\nexport class InsertPatch {\n  location: string\n  path: string\n  items: unknown[]\n  id: string\n\n  constructor(id: string, location: string, path: string, items: unknown[]) {\n    this.id = id\n    this.location = location\n    this.path = path\n    this.items = items\n  }\n\n  apply(targets: Expression[], accessor: ImmutableAccessor): ImmutableAccessor {\n    let result = accessor\n    if (accessor.containerType() !== 'array') {\n      throw new Error('Attempt to apply insert patch to non-array value')\n    }\n\n    switch (this.location) {\n      case 'before': {\n