@sanity/diff-patch 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,575 @@
1
+ import {cleanupEfficiency, makeDiff, makePatches, stringifyPatches} from '@sanity/diff-match-patch'
2
+ import {DiffError} from './diffError.js'
3
+ import {type Path, pathToString} from './paths.js'
4
+ import {validateProperty} from './validate.js'
5
+ import {
6
+ type Patch,
7
+ type SetPatch,
8
+ type UnsetPatch,
9
+ type InsertAfterPatch,
10
+ type DiffMatchPatch,
11
+ type SanityInsertPatch,
12
+ type SanityPatch,
13
+ type SanitySetPatch,
14
+ type SanityUnsetPatch,
15
+ type SanityDiffMatchPatch,
16
+ type SanityPatchMutation,
17
+ } from './patches.js'
18
+
19
+ const ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']
20
+
21
+ type PrimitiveValue = string | number | boolean | null | undefined
22
+
23
+ /**
24
+ * An object (record) that has a `_key` property
25
+ *
26
+ * @internal
27
+ */
28
+ export interface KeyedSanityObject {
29
+ [key: string]: unknown
30
+ _key: string
31
+ }
32
+
33
+ /**
34
+ * An object (record) that _may_ have a `_key` property
35
+ *
36
+ * @internal
37
+ */
38
+ export type SanityObject = KeyedSanityObject | Partial<KeyedSanityObject>
39
+
40
+ /**
41
+ * Represents a partial Sanity document (eg a "stub").
42
+ *
43
+ * @public
44
+ */
45
+ export interface DocumentStub {
46
+ _id?: string
47
+ _type?: string
48
+ _rev?: string
49
+ _createdAt?: string
50
+ _updatedAt?: string
51
+ [key: string]: unknown
52
+ }
53
+
54
+ /**
55
+ * Options for the diff-match-patch algorithm.
56
+ *
57
+ * @public
58
+ */
59
+ export interface DiffMatchPatchOptions {
60
+ /**
61
+ * Whether or not to use diff-match-patch at all
62
+ *
63
+ * @defaultValue `true`
64
+ */
65
+ enabled: boolean
66
+
67
+ /**
68
+ * Threshold at which to start using diff-match-patch instead of a regular `set` patch.
69
+ *
70
+ * @defaultValue `30`
71
+ */
72
+ lengthThresholdAbsolute: number
73
+
74
+ /**
75
+ * Only use generated diff-match-patch if the patch length is less than or equal to
76
+ * (targetString * relative). Example: A 100 character target with a relative factor
77
+ * of 1.2 will allow a 120 character diff-match-patch. If larger than this number,
78
+ * it will fall back to a regular `set` patch.
79
+ *
80
+ * @defaultValue `1.2`
81
+ */
82
+ lengthThresholdRelative: number
83
+ }
84
+
85
+ /**
86
+ * Options for the patch generator
87
+ *
88
+ * @public
89
+ */
90
+ export interface PatchOptions {
91
+ /**
92
+ * Document ID to apply the patch to.
93
+ *
94
+ * @defaultValue `undefined` - tries to extract `_id` from passed document
95
+ */
96
+ id?: string
97
+
98
+ /**
99
+ * Base path to apply the patch to - useful if diffing sub-branches of a document.
100
+ *
101
+ * @defaultValue `[]` - eg root of the document
102
+ */
103
+ basePath?: Path
104
+
105
+ /**
106
+ * Only apply the patch if the document revision matches this value.
107
+ * If the property is the boolean value `true`, it will attempt to extract
108
+ * the revision from the document `_rev` property.
109
+ *
110
+ * @defaultValue `undefined` (do not apply revision check)
111
+ */
112
+ ifRevisionID?: string | true
113
+
114
+ /**
115
+ * Whether or not to hide warnings during the diff process.
116
+ *
117
+ * @defaultValue `false`
118
+ */
119
+ hideWarnings?: boolean
120
+
121
+ /**
122
+ * Options for the diff-match-patch algorithm.
123
+ */
124
+ diffMatchPatch?: Partial<DiffMatchPatchOptions>
125
+ }
126
+
127
+ /**
128
+ * Options for diff generation, where all DMP properties are required
129
+ *
130
+ * @public
131
+ */
132
+ export type DiffOptions = PatchOptions & {diffMatchPatch: Required<DiffMatchPatchOptions>}
133
+
134
+ const defaultOptions = {
135
+ hideWarnings: false,
136
+ diffMatchPatch: {
137
+ enabled: true,
138
+
139
+ // Only use diff-match-patch if target string is longer than this threshold
140
+ lengthThresholdAbsolute: 30,
141
+
142
+ // Only use generated diff-match-patch if the patch length is less than or equal to
143
+ // (targetString * relative). Example: A 100 character target with a relative factor
144
+ // of 1.2 will allow a 120 character diff-match-patch. If larger than this number,
145
+ // it will fall back to a regular `set` patch.
146
+ lengthThresholdRelative: 1.2,
147
+ },
148
+ } satisfies DiffOptions
149
+
150
+ /**
151
+ * Merges the default options with the passed in options.
152
+ *
153
+ * @param options - Options to merge with the defaults
154
+ * @returns Merged options
155
+ */
156
+ function mergeOptions(options: PatchOptions): DiffOptions {
157
+ return {
158
+ ...defaultOptions,
159
+ ...options,
160
+ diffMatchPatch: {...defaultOptions.diffMatchPatch, ...(options.diffMatchPatch || {})},
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Generates an array of mutations for Sanity, based on the differences between
166
+ * the two passed documents/trees.
167
+ *
168
+ * @param itemA - The first document/tree to compare
169
+ * @param itemB - The second document/tree to compare
170
+ * @param opts - Options for the diff generation
171
+ * @returns Array of mutations
172
+ * @public
173
+ */
174
+ export function diffPatch(
175
+ itemA: DocumentStub,
176
+ itemB: DocumentStub,
177
+ opts?: PatchOptions,
178
+ ): SanityPatchMutation[] {
179
+ const options = mergeOptions(opts || {})
180
+ const id = options.id || (itemA._id === itemB._id && itemA._id)
181
+ const revisionLocked = options.ifRevisionID
182
+ const ifRevisionID = typeof revisionLocked === 'boolean' ? itemA._rev : revisionLocked
183
+ const basePath = options.basePath || []
184
+ if (!id) {
185
+ throw new Error(
186
+ '_id on itemA and itemB not present or differs, specify document id the mutations should be applied to',
187
+ )
188
+ }
189
+
190
+ if (revisionLocked === true && !ifRevisionID) {
191
+ throw new Error(
192
+ '`ifRevisionID` is set to `true`, but no `_rev` was passed in item A. Either explicitly set `ifRevisionID` to a revision, or pass `_rev` as part of item A.',
193
+ )
194
+ }
195
+
196
+ if (basePath.length === 0 && itemA._type !== itemB._type) {
197
+ throw new Error(`_type is immutable and cannot be changed (${itemA._type} => ${itemB._type})`)
198
+ }
199
+
200
+ const operations = diffItem(itemA, itemB, options, basePath, [])
201
+ return serializePatches(operations, {id, ifRevisionID: revisionLocked ? ifRevisionID : undefined})
202
+ }
203
+
204
+ /**
205
+ * Diffs two items and returns an array of patches.
206
+ * Note that this is different from `diffPatch`, which generates _mutations_.
207
+ *
208
+ * @param itemA - The first item to compare
209
+ * @param itemB - The second item to compare
210
+ * @param opts - Options for the diff generation
211
+ * @param path - Path to the current item
212
+ * @param patches - Array of patches to append the results to. Note that this is MUTATED.
213
+ * @returns Array of patches
214
+ * @public
215
+ */
216
+ export function diffItem(
217
+ itemA: unknown,
218
+ itemB: unknown,
219
+ opts: DiffOptions = defaultOptions,
220
+ path: Path = [],
221
+ patches: Patch[] = [],
222
+ ): Patch[] {
223
+ if (itemA === itemB) {
224
+ return patches
225
+ }
226
+
227
+ const aType = Array.isArray(itemA) ? 'array' : typeof itemA
228
+ const bType = Array.isArray(itemB) ? 'array' : typeof itemB
229
+
230
+ const aIsUndefined = aType === 'undefined'
231
+ const bIsUndefined = bType === 'undefined'
232
+
233
+ if (aIsUndefined && !bIsUndefined) {
234
+ patches.push({op: 'set', path, value: itemB})
235
+ return patches
236
+ }
237
+
238
+ if (!aIsUndefined && bIsUndefined) {
239
+ patches.push({op: 'unset', path})
240
+ return patches
241
+ }
242
+
243
+ const options = mergeOptions(opts)
244
+ const dataType = aIsUndefined ? bType : aType
245
+ const isContainer = dataType === 'object' || dataType === 'array'
246
+ if (!isContainer) {
247
+ return diffPrimitive(itemA as PrimitiveValue, itemB as PrimitiveValue, options, path, patches)
248
+ }
249
+
250
+ if (aType !== bType) {
251
+ // Array => Object / Object => Array
252
+ patches.push({op: 'set', path, value: itemB})
253
+ return patches
254
+ }
255
+
256
+ return dataType === 'array'
257
+ ? diffArray(itemA as unknown[], itemB as unknown[], options, path, patches)
258
+ : diffObject(itemA as object, itemB as object, options, path, patches)
259
+ }
260
+
261
+ function diffObject(
262
+ itemA: SanityObject,
263
+ itemB: SanityObject,
264
+ options: DiffOptions,
265
+ path: Path,
266
+ patches: Patch[],
267
+ ) {
268
+ const atRoot = path.length === 0
269
+ const aKeys = Object.keys(itemA)
270
+ .filter(atRoot ? isNotIgnoredKey : yes)
271
+ .map((key) => validateProperty(key, itemA[key], path))
272
+
273
+ const aKeysLength = aKeys.length
274
+ const bKeys = Object.keys(itemB)
275
+ .filter(atRoot ? isNotIgnoredKey : yes)
276
+ .map((key) => validateProperty(key, itemB[key], path))
277
+
278
+ const bKeysLength = bKeys.length
279
+
280
+ // Check for deleted items
281
+ for (let i = 0; i < aKeysLength; i++) {
282
+ const key = aKeys[i]
283
+ if (!(key in itemB)) {
284
+ patches.push({op: 'unset', path: path.concat(key)})
285
+ }
286
+ }
287
+
288
+ // Check for changed items
289
+ for (let i = 0; i < bKeysLength; i++) {
290
+ const key = bKeys[i]
291
+ diffItem(itemA[key], itemB[key], options, path.concat([key]), patches)
292
+ }
293
+
294
+ return patches
295
+ }
296
+
297
+ function diffArray(
298
+ itemA: unknown[],
299
+ itemB: unknown[],
300
+ options: DiffOptions,
301
+ path: Path,
302
+ patches: Patch[],
303
+ ) {
304
+ // Check for new items
305
+ if (itemB.length > itemA.length) {
306
+ patches.push({
307
+ op: 'insert',
308
+ after: path.concat([-1]),
309
+ items: itemB.slice(itemA.length).map((item, i) => nullifyUndefined(item, path, i, options)),
310
+ })
311
+ }
312
+
313
+ // Check for deleted items
314
+ if (itemB.length < itemA.length) {
315
+ const isSingle = itemA.length - itemB.length === 1
316
+ const unsetItems = itemA.slice(itemB.length)
317
+
318
+ // If we're revision locked, we can safely unset ranges (eg 5:<end-of-array>).
319
+ // Also, if we don't have unique array keys, we can't use any better approach
320
+ // than array indexes. If we _do_ have unique array keys, we'll want to unset
321
+ // by key, as this is safer in a realtime, collaborative setting
322
+ if (isRevisionLocked(options) || !isUniquelyKeyed(unsetItems)) {
323
+ patches.push({
324
+ op: 'unset',
325
+ path: path.concat([isSingle ? itemB.length : [itemB.length, '']]),
326
+ })
327
+ } else {
328
+ patches.push(
329
+ ...unsetItems.map(
330
+ (item): UnsetPatch => ({op: 'unset', path: path.concat({_key: item._key})}),
331
+ ),
332
+ )
333
+ }
334
+ }
335
+
336
+ // Check for illegal array contents
337
+ for (let i = 0; i < itemB.length; i++) {
338
+ if (Array.isArray(itemB[i])) {
339
+ throw new DiffError('Multi-dimensional arrays not supported', path.concat(i), itemB[i])
340
+ }
341
+ }
342
+
343
+ const overlapping = Math.min(itemA.length, itemB.length)
344
+ const segmentA = itemA.slice(0, overlapping)
345
+ const segmentB = itemB.slice(0, overlapping)
346
+
347
+ return isUniquelyKeyed(segmentA) && isUniquelyKeyed(segmentB)
348
+ ? diffArrayByKey(segmentA, segmentB, options, path, patches)
349
+ : diffArrayByIndex(segmentA, segmentB, options, path, patches)
350
+ }
351
+
352
+ function diffArrayByIndex(
353
+ itemA: unknown[],
354
+ itemB: unknown[],
355
+ options: DiffOptions,
356
+ path: Path,
357
+ patches: Patch[],
358
+ ) {
359
+ for (let i = 0; i < itemA.length; i++) {
360
+ diffItem(
361
+ itemA[i],
362
+ nullifyUndefined(itemB[i], path, i, options),
363
+ options,
364
+ path.concat(i),
365
+ patches,
366
+ )
367
+ }
368
+
369
+ return patches
370
+ }
371
+
372
+ function diffArrayByKey(
373
+ itemA: KeyedSanityObject[],
374
+ itemB: KeyedSanityObject[],
375
+ options: DiffOptions,
376
+ path: Path,
377
+ patches: Patch[],
378
+ ) {
379
+ const keyedA = indexByKey(itemA)
380
+ const keyedB = indexByKey(itemB)
381
+
382
+ // There's a bunch of hard/semi-hard problems related to using keys
383
+ // Unless we have the exact same order, just use indexes for now
384
+ if (!arrayIsEqual(keyedA.keys, keyedB.keys)) {
385
+ return diffArrayByIndex(itemA, itemB, options, path, patches)
386
+ }
387
+
388
+ for (let i = 0; i < keyedB.keys.length; i++) {
389
+ const key = keyedB.keys[i]
390
+ const valueA = keyedA.index[key]
391
+ const valueB = nullifyUndefined(keyedB.index[key], path, i, options)
392
+ diffItem(valueA, valueB, options, path.concat({_key: key}), patches)
393
+ }
394
+
395
+ return patches
396
+ }
397
+
398
+ function getDiffMatchPatch(
399
+ itemA: PrimitiveValue,
400
+ itemB: PrimitiveValue,
401
+ options: DiffOptions,
402
+ path: Path,
403
+ ): DiffMatchPatch | undefined {
404
+ const {enabled, lengthThresholdRelative, lengthThresholdAbsolute} = options.diffMatchPatch
405
+ const segment = path[path.length - 1]
406
+ if (
407
+ !enabled ||
408
+ // Don't use for anything but strings
409
+ typeof itemA !== 'string' ||
410
+ typeof itemB !== 'string' ||
411
+ // Don't use for `_key`, `_ref` etc
412
+ (typeof segment === 'string' && segment[0] === '_') ||
413
+ // Don't use on short strings
414
+ itemB.length < lengthThresholdAbsolute
415
+ ) {
416
+ return undefined
417
+ }
418
+
419
+ let strPatch = ''
420
+ try {
421
+ const patch = makeDiff(itemA, itemB)
422
+ const diff = cleanupEfficiency(patch)
423
+ strPatch = stringifyPatches(makePatches(diff))
424
+ } catch (err) {
425
+ // Fall back to using regular set patch
426
+ return undefined
427
+ }
428
+
429
+ // Don't use patch if it's longer than allowed relative threshold.
430
+ // Allow a 120 character patch for a 100 character string,
431
+ // but don't allow a 800 character patch for a 500 character value.
432
+ return strPatch.length > itemB.length * lengthThresholdRelative
433
+ ? undefined
434
+ : {op: 'diffMatchPatch', path, value: strPatch}
435
+ }
436
+
437
+ function diffPrimitive(
438
+ itemA: PrimitiveValue,
439
+ itemB: PrimitiveValue,
440
+ options: DiffOptions,
441
+ path: Path,
442
+ patches: Patch[],
443
+ ): Patch[] {
444
+ const dmp = getDiffMatchPatch(itemA, itemB, options, path)
445
+
446
+ patches.push(
447
+ dmp || {
448
+ op: 'set',
449
+ path,
450
+ value: itemB,
451
+ },
452
+ )
453
+
454
+ return patches
455
+ }
456
+
457
+ function isNotIgnoredKey(key: string) {
458
+ return ignoredKeys.indexOf(key) === -1
459
+ }
460
+
461
+ function serializePatches(
462
+ patches: Patch[],
463
+ options: {id: string; ifRevisionID?: string},
464
+ ): SanityPatchMutation[] {
465
+ if (patches.length === 0) {
466
+ return []
467
+ }
468
+
469
+ const {id, ifRevisionID} = options
470
+ const set = patches.filter((patch): patch is SetPatch => patch.op === 'set')
471
+ const unset = patches.filter((patch): patch is UnsetPatch => patch.op === 'unset')
472
+ const insert = patches.filter((patch): patch is InsertAfterPatch => patch.op === 'insert')
473
+ const dmp = patches.filter((patch): patch is DiffMatchPatch => patch.op === 'diffMatchPatch')
474
+
475
+ const withSet =
476
+ set.length > 0 &&
477
+ set.reduce(
478
+ (patch: SanitySetPatch, item: SetPatch) => {
479
+ const path = pathToString(item.path)
480
+ patch.set[path] = item.value
481
+ return patch
482
+ },
483
+ {id, set: {}},
484
+ )
485
+
486
+ const withUnset =
487
+ unset.length > 0 &&
488
+ unset.reduce(
489
+ (patch: SanityUnsetPatch, item: UnsetPatch) => {
490
+ const path = pathToString(item.path)
491
+ patch.unset.push(path)
492
+ return patch
493
+ },
494
+ {id, unset: []},
495
+ )
496
+
497
+ const withInsert = insert.reduce((acc: SanityInsertPatch[], item: InsertAfterPatch) => {
498
+ const after = pathToString(item.after)
499
+ return acc.concat({id, insert: {after, items: item.items}})
500
+ }, [])
501
+
502
+ const withDmp =
503
+ dmp.length > 0 &&
504
+ dmp.reduce(
505
+ (patch: SanityDiffMatchPatch, item: DiffMatchPatch) => {
506
+ const path = pathToString(item.path)
507
+ patch.diffMatchPatch[path] = item.value
508
+ return patch
509
+ },
510
+ {id, diffMatchPatch: {}},
511
+ )
512
+
513
+ const patchSet: SanityPatch[] = [withUnset, withSet, withDmp, ...withInsert].filter(
514
+ (item): item is SanityPatch => item !== false,
515
+ )
516
+
517
+ return patchSet.map((patch, i) => ({
518
+ patch: ifRevisionID && i === 0 ? {...patch, ifRevisionID} : patch,
519
+ }))
520
+ }
521
+
522
+ function isUniquelyKeyed(arr: unknown[]): arr is KeyedSanityObject[] {
523
+ const keys = []
524
+
525
+ for (let i = 0; i < arr.length; i++) {
526
+ const key = getKey(arr[i])
527
+ if (!key || keys.indexOf(key) !== -1) {
528
+ return false
529
+ }
530
+
531
+ keys.push(key)
532
+ }
533
+
534
+ return true
535
+ }
536
+
537
+ function getKey(obj: unknown) {
538
+ return typeof obj === 'object' && obj !== null && (obj as KeyedSanityObject)._key
539
+ }
540
+
541
+ function indexByKey(arr: KeyedSanityObject[]) {
542
+ return arr.reduce(
543
+ (acc, item) => {
544
+ acc.keys.push(item._key)
545
+ acc.index[item._key] = item
546
+ return acc
547
+ },
548
+ {keys: [] as string[], index: {} as {[key: string]: KeyedSanityObject}},
549
+ )
550
+ }
551
+
552
+ function arrayIsEqual(itemA: unknown[], itemB: unknown[]) {
553
+ return itemA.length === itemB.length && itemA.every((item, i) => itemB[i] === item)
554
+ }
555
+
556
+ function nullifyUndefined(item: unknown, path: Path, index: number, options: PatchOptions) {
557
+ if (typeof item !== 'undefined') {
558
+ return item
559
+ }
560
+
561
+ if (!options.hideWarnings) {
562
+ const serializedPath = pathToString(path.concat(index))
563
+ console.warn(`undefined value in array converted to null (at '${serializedPath}')`)
564
+ }
565
+
566
+ return null
567
+ }
568
+
569
+ function isRevisionLocked(options: PatchOptions): boolean {
570
+ return Boolean(options.ifRevisionID)
571
+ }
572
+
573
+ function yes(_: unknown) {
574
+ return true
575
+ }
package/src/index.ts ADDED
@@ -0,0 +1,20 @@
1
+ export {diffPatch, diffItem} from './diffPatch.js'
2
+ export {DiffError} from './diffError.js'
3
+
4
+ export type {
5
+ DiffMatchPatch,
6
+ InsertAfterPatch,
7
+ Patch,
8
+ SanityDiffMatchPatch,
9
+ SanityInsertPatch,
10
+ SanityPatch,
11
+ SanityPatchMutation,
12
+ SanitySetPatch,
13
+ SanityUnsetPatch,
14
+ SetPatch,
15
+ UnsetPatch,
16
+ } from './patches.js'
17
+
18
+ export type {DiffMatchPatchOptions, DiffOptions, DocumentStub, PatchOptions} from './diffPatch.js'
19
+
20
+ export type {Path, PathSegment} from './paths.js'
package/src/patches.ts ADDED
@@ -0,0 +1,126 @@
1
+ import type {Path} from './paths.js'
2
+
3
+ /**
4
+ * A `set` operation
5
+ * Replaces the current path, does not merge
6
+ * Note: NOT a serializable mutation, see {@link SanitySetPatch} for that
7
+ *
8
+ * @public
9
+ */
10
+ export interface SetPatch {
11
+ op: 'set'
12
+ path: Path
13
+ value: unknown
14
+ }
15
+
16
+ /**
17
+ * A `unset` operation
18
+ * Unsets the entire value of the given path
19
+ * Note: NOT a serializable mutation, see {@link SanityUnsetPatch} for that
20
+ *
21
+ * @public
22
+ */
23
+ export interface UnsetPatch {
24
+ op: 'unset'
25
+ path: Path
26
+ }
27
+
28
+ /**
29
+ * A `insert` operation
30
+ * Inserts the given items _after_ the given path
31
+ * Note: NOT a serializable mutation, see {@link SanityInsertPatch} for that
32
+ *
33
+ * @public
34
+ */
35
+ export interface InsertAfterPatch {
36
+ op: 'insert'
37
+ after: Path
38
+ items: any[]
39
+ }
40
+
41
+ /**
42
+ * A `diffMatchPatch` operation
43
+ * Applies the given `value` (unidiff format) to the given path. Must be a string.
44
+ * Note: NOT a serializable mutation, see {@link SanityDiffMatchPatch} for that
45
+ *
46
+ * @public
47
+ */
48
+ export interface DiffMatchPatch {
49
+ op: 'diffMatchPatch'
50
+ path: Path
51
+ value: string
52
+ }
53
+
54
+ /**
55
+ * A patch containing either a Sanity set, unset, insert or diffMatchPatch operation
56
+ *
57
+ * @public
58
+ */
59
+ export type Patch = SetPatch | UnsetPatch | InsertAfterPatch | DiffMatchPatch
60
+
61
+ /**
62
+ * A Sanity `set` patch mutation operation
63
+ * Replaces the current path, does not merge
64
+ *
65
+ * @public
66
+ */
67
+ export interface SanitySetPatch {
68
+ id: string
69
+ set: {[key: string]: any}
70
+ }
71
+
72
+ /**
73
+ * A Sanity `unset` patch mutation operation
74
+ * Unsets the entire value of the given path
75
+ *
76
+ * @public
77
+ */
78
+ export interface SanityUnsetPatch {
79
+ id: string
80
+ unset: string[]
81
+ }
82
+
83
+ /**
84
+ * A Sanity `insert` patch mutation operation
85
+ * Inserts the given items at the given path (before/after)
86
+ *
87
+ * @public
88
+ */
89
+ export interface SanityInsertPatch {
90
+ id: string
91
+ insert:
92
+ | {before: string; items: any[]}
93
+ | {after: string; items: any[]}
94
+ | {replace: string; items: any[]}
95
+ }
96
+
97
+ /**
98
+ * A Sanity `diffMatchPatch` patch mutation operation
99
+ * Patches the given path with the given unidiff string.
100
+ *
101
+ * @public
102
+ */
103
+ export interface SanityDiffMatchPatch {
104
+ id: string
105
+ diffMatchPatch: {[key: string]: string}
106
+ }
107
+
108
+ /**
109
+ * A patch containing either a set, unset, insert or diffMatchPatch operation
110
+ *
111
+ * @public
112
+ */
113
+ export type SanityPatch =
114
+ | SanitySetPatch
115
+ | SanityUnsetPatch
116
+ | SanityInsertPatch
117
+ | SanityDiffMatchPatch
118
+
119
+ /**
120
+ * A mutation containing a single patch
121
+ *
122
+ * @public
123
+ */
124
+ export interface SanityPatchMutation {
125
+ patch: SanityPatch
126
+ }