@sanity/diff-patch 5.0.0 → 6.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.
package/dist/index.cjs CHANGED
@@ -18,7 +18,7 @@ function pathToString(path) {
18
18
  }, "");
19
19
  }
20
20
  function isKeyedObject(obj) {
21
- return typeof obj == "object" && typeof obj._key == "string";
21
+ return typeof obj == "object" && !!obj && "_key" in obj && typeof obj._key == "string";
22
22
  }
23
23
  class DiffError extends Error {
24
24
  path;
@@ -47,205 +47,225 @@ function validateProperty(property, value, path) {
47
47
  }
48
48
  return property;
49
49
  }
50
- const ignoredKeys = ["_id", "_type", "_createdAt", "_updatedAt", "_rev"], defaultOptions = {
51
- hideWarnings: !1,
52
- diffMatchPatch: {
53
- enabled: !0,
54
- // Only use diff-match-patch if target string is longer than this threshold
55
- lengthThresholdAbsolute: 30,
56
- // Only use generated diff-match-patch if the patch length is less than or equal to
57
- // (targetString * relative). Example: A 100 character target with a relative factor
58
- // of 1.2 will allow a 120 character diff-match-patch. If larger than this number,
59
- // it will fall back to a regular `set` patch.
60
- lengthThresholdRelative: 1.2
61
- }
62
- };
63
- function mergeOptions(options) {
64
- return {
65
- ...defaultOptions,
66
- ...options,
67
- diffMatchPatch: { ...defaultOptions.diffMatchPatch, ...options.diffMatchPatch || {} }
68
- };
69
- }
70
- function diffPatch(itemA, itemB, opts) {
71
- const options = mergeOptions(opts || {}), id = options.id || itemA._id === itemB._id && itemA._id, revisionLocked = options.ifRevisionID, ifRevisionID = typeof revisionLocked == "boolean" ? itemA._rev : revisionLocked, basePath = options.basePath || [];
50
+ function difference(source, target) {
51
+ if ("difference" in Set.prototype)
52
+ return source.difference(target);
53
+ const result = /* @__PURE__ */ new Set();
54
+ for (const item of source)
55
+ target.has(item) || result.add(item);
56
+ return result;
57
+ }
58
+ function intersection(source, target) {
59
+ if ("intersection" in Set.prototype)
60
+ return source.intersection(target);
61
+ const result = /* @__PURE__ */ new Set();
62
+ for (const item of source)
63
+ target.has(item) && result.add(item);
64
+ return result;
65
+ }
66
+ const SYSTEM_KEYS = ["_id", "_type", "_createdAt", "_updatedAt", "_rev"], DMP_MAX_STRING_SIZE = 1e6, DMP_MAX_STRING_LENGTH_CHANGE_RATIO = 0.4, DMP_MIN_SIZE_FOR_RATIO_CHECK = 1e4;
67
+ function diffPatch(source, target, options = {}) {
68
+ const id = options.id || source._id === target._id && source._id, revisionLocked = options.ifRevisionID, ifRevisionID = typeof revisionLocked == "boolean" ? source._rev : revisionLocked, basePath = options.basePath || [];
72
69
  if (!id)
73
70
  throw new Error(
74
- "_id on itemA and itemB not present or differs, specify document id the mutations should be applied to"
71
+ "_id on source and target not present or differs, specify document id the mutations should be applied to"
75
72
  );
76
73
  if (revisionLocked === !0 && !ifRevisionID)
77
74
  throw new Error(
78
75
  "`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."
79
76
  );
80
- if (basePath.length === 0 && itemA._type !== itemB._type)
81
- throw new Error(`_type is immutable and cannot be changed (${itemA._type} => ${itemB._type})`);
82
- const operations = diffItem(itemA, itemB, options, basePath, []);
83
- return serializePatches(operations, { id, ifRevisionID: revisionLocked ? ifRevisionID : void 0 });
84
- }
85
- function diffItem(itemA, itemB, opts = defaultOptions, path = [], patches = []) {
86
- if (itemA === itemB)
87
- return patches;
88
- const aType = Array.isArray(itemA) ? "array" : typeof itemA, bType = Array.isArray(itemB) ? "array" : typeof itemB, aIsUndefined = aType === "undefined", bIsUndefined = bType === "undefined";
89
- if (aIsUndefined && !bIsUndefined)
90
- return patches.push({ op: "set", path, value: itemB }), patches;
91
- if (!aIsUndefined && bIsUndefined)
92
- return patches.push({ op: "unset", path }), patches;
93
- const options = mergeOptions(opts), dataType = aIsUndefined ? bType : aType;
94
- return dataType === "object" || dataType === "array" ? aType !== bType ? (patches.push({ op: "set", path, value: itemB }), patches) : dataType === "array" ? diffArray(itemA, itemB, options, path, patches) : diffObject(itemA, itemB, options, path, patches) : diffPrimitive(itemA, itemB, options, path, patches);
95
- }
96
- function diffObject(itemA, itemB, options, path, patches) {
97
- const atRoot = path.length === 0, aKeys = Object.keys(itemA).filter(atRoot ? isNotIgnoredKey : yes).map((key) => validateProperty(key, itemA[key], path)), aKeysLength = aKeys.length, bKeys = Object.keys(itemB).filter(atRoot ? isNotIgnoredKey : yes).map((key) => validateProperty(key, itemB[key], path)), bKeysLength = bKeys.length;
77
+ if (basePath.length === 0 && source._type !== target._type)
78
+ throw new Error(`_type is immutable and cannot be changed (${source._type} => ${target._type})`);
79
+ const operations = diffItem(source, target, basePath, []);
80
+ return serializePatches(operations).map((patchOperations, i) => ({
81
+ patch: {
82
+ id,
83
+ // only add `ifRevisionID` to the first patch
84
+ ...i === 0 && ifRevisionID && { ifRevisionID },
85
+ ...patchOperations
86
+ }
87
+ }));
88
+ }
89
+ function diffValue(source, target, basePath = []) {
90
+ return serializePatches(diffItem(source, target, basePath));
91
+ }
92
+ function diffItem(source, target, path = [], patches = []) {
93
+ return source === target ? patches : typeof source == "string" && typeof target == "string" ? (diffString(source, target, path, patches), patches) : Array.isArray(source) && Array.isArray(target) ? (diffArray(source, target, path, patches), patches) : isRecord(source) && isRecord(target) ? (diffObject(source, target, path, patches), patches) : target === void 0 ? (patches.push({ op: "unset", path }), patches) : (patches.push({ op: "set", path, value: target }), patches);
94
+ }
95
+ function diffObject(source, target, path, patches) {
96
+ const atRoot = path.length === 0, aKeys = Object.keys(source).filter(atRoot ? isNotIgnoredKey : yes).map((key) => validateProperty(key, source[key], path)), aKeysLength = aKeys.length, bKeys = Object.keys(target).filter(atRoot ? isNotIgnoredKey : yes).map((key) => validateProperty(key, target[key], path)), bKeysLength = bKeys.length;
98
97
  for (let i = 0; i < aKeysLength; i++) {
99
98
  const key = aKeys[i];
100
- key in itemB || patches.push({ op: "unset", path: path.concat(key) });
99
+ key in target || patches.push({ op: "unset", path: path.concat(key) });
101
100
  }
102
101
  for (let i = 0; i < bKeysLength; i++) {
103
102
  const key = bKeys[i];
104
- diffItem(itemA[key], itemB[key], options, path.concat([key]), patches);
103
+ diffItem(source[key], target[key], path.concat([key]), patches);
105
104
  }
106
105
  return patches;
107
106
  }
108
- function diffArray(itemA, itemB, options, path, patches) {
109
- if (itemB.length > itemA.length && patches.push({
107
+ function diffArray(source, target, path, patches) {
108
+ return isUniquelyKeyed(source) && isUniquelyKeyed(target) ? diffArrayByKey(source, target, path, patches) : diffArrayByIndex(source, target, path, patches);
109
+ }
110
+ function diffArrayByIndex(source, target, path, patches) {
111
+ if (target.length > source.length && patches.push({
110
112
  op: "insert",
111
- after: path.concat([-1]),
112
- items: itemB.slice(itemA.length).map((item, i) => nullifyUndefined(item, path, i, options))
113
- }), itemB.length < itemA.length) {
114
- const isSingle = itemA.length - itemB.length === 1, unsetItems = itemA.slice(itemB.length);
115
- isRevisionLocked(options) || !isUniquelyKeyed(unsetItems) ? patches.push({
116
- op: "unset",
117
- path: path.concat([isSingle ? itemB.length : [itemB.length, ""]])
118
- }) : patches.push(
113
+ position: "after",
114
+ path: path.concat([-1]),
115
+ items: target.slice(source.length).map(nullifyUndefined)
116
+ }), target.length < source.length) {
117
+ const isSingle = source.length - target.length === 1, unsetItems = source.slice(target.length);
118
+ isUniquelyKeyed(unsetItems) ? patches.push(
119
119
  ...unsetItems.map(
120
120
  (item) => ({ op: "unset", path: path.concat({ _key: item._key }) })
121
121
  )
122
- );
122
+ ) : patches.push({
123
+ op: "unset",
124
+ path: path.concat([isSingle ? target.length : [target.length, ""]])
125
+ });
123
126
  }
124
- for (let i = 0; i < itemB.length; i++)
125
- if (Array.isArray(itemB[i]))
126
- throw new DiffError("Multi-dimensional arrays not supported", path.concat(i), itemB[i]);
127
- const overlapping = Math.min(itemA.length, itemB.length), segmentA = itemA.slice(0, overlapping), segmentB = itemB.slice(0, overlapping);
128
- return isUniquelyKeyed(segmentA) && isUniquelyKeyed(segmentB) ? diffArrayByKey(segmentA, segmentB, options, path, patches) : diffArrayByIndex(segmentA, segmentB, options, path, patches);
129
- }
130
- function diffArrayByIndex(itemA, itemB, options, path, patches) {
131
- for (let i = 0; i < itemA.length; i++)
132
- diffItem(
133
- itemA[i],
134
- nullifyUndefined(itemB[i], path, i, options),
135
- options,
136
- path.concat(i),
137
- patches
138
- );
127
+ for (let i = 0; i < target.length; i++)
128
+ if (Array.isArray(target[i]))
129
+ throw new DiffError("Multi-dimensional arrays not supported", path.concat(i), target[i]);
130
+ const overlapping = Math.min(source.length, target.length), segmentA = source.slice(0, overlapping), segmentB = target.slice(0, overlapping);
131
+ for (let i = 0; i < segmentA.length; i++)
132
+ diffItem(segmentA[i], nullifyUndefined(segmentB[i]), path.concat(i), patches);
139
133
  return patches;
140
134
  }
141
- function diffArrayByKey(itemA, itemB, options, path, patches) {
142
- const keyedA = indexByKey(itemA), keyedB = indexByKey(itemB);
143
- if (!arrayIsEqual(keyedA.keys, keyedB.keys))
144
- return diffArrayByIndex(itemA, itemB, options, path, patches);
145
- for (let i = 0; i < keyedB.keys.length; i++) {
146
- const key = keyedB.keys[i], valueA = keyedA.index[key], valueB = nullifyUndefined(keyedB.index[key], path, i, options);
147
- diffItem(valueA, valueB, options, path.concat({ _key: key }), patches);
135
+ function diffArrayByKey(source, target, path, patches) {
136
+ const sourceItemsByKey = new Map(source.map((item) => [item._key, item])), targetItemsByKey = new Map(target.map((item) => [item._key, item])), sourceKeys = new Set(sourceItemsByKey.keys()), targetKeys = new Set(targetItemsByKey.keys()), keysRemovedFromSource = difference(sourceKeys, targetKeys), keysAddedToTarget = difference(targetKeys, sourceKeys), keysInBothArrays = intersection(sourceKeys, targetKeys), sourceKeysStillPresent = Array.from(difference(sourceKeys, keysRemovedFromSource)), targetKeysAlreadyPresent = Array.from(difference(targetKeys, keysAddedToTarget)), keyReorderOperations = [];
137
+ for (let i = 0; i < keysInBothArrays.size; i++) {
138
+ const keyAtPositionInSource = sourceKeysStillPresent[i], keyAtPositionInTarget = targetKeysAlreadyPresent[i];
139
+ keyAtPositionInSource !== keyAtPositionInTarget && keyReorderOperations.push({
140
+ sourceKey: keyAtPositionInSource,
141
+ targetKey: keyAtPositionInTarget
142
+ });
143
+ }
144
+ keyReorderOperations.length && patches.push({
145
+ op: "reorder",
146
+ path,
147
+ snapshot: source,
148
+ reorders: keyReorderOperations
149
+ });
150
+ for (const key of keysInBothArrays)
151
+ diffItem(sourceItemsByKey.get(key), targetItemsByKey.get(key), [...path, { _key: key }], patches);
152
+ for (const keyToRemove of keysRemovedFromSource)
153
+ patches.push({ op: "unset", path: [...path, { _key: keyToRemove }] });
154
+ if (keysAddedToTarget.size) {
155
+ let insertionAnchorKey, itemsPendingInsertion = [];
156
+ const flushPendingInsertions = () => {
157
+ itemsPendingInsertion.length && patches.push({
158
+ op: "insert",
159
+ // Insert after the anchor key if we have one, otherwise insert at the beginning
160
+ ...insertionAnchorKey ? { position: "after", path: [...path, { _key: insertionAnchorKey }] } : { position: "before", path: [...path, 0] },
161
+ items: itemsPendingInsertion
162
+ });
163
+ };
164
+ for (const key of targetKeys)
165
+ keysAddedToTarget.has(key) ? itemsPendingInsertion.push(targetItemsByKey.get(key)) : keysInBothArrays.has(key) && (flushPendingInsertions(), insertionAnchorKey = key, itemsPendingInsertion = []);
166
+ flushPendingInsertions();
148
167
  }
149
168
  return patches;
150
169
  }
151
- function getDiffMatchPatch(itemA, itemB, options, path) {
152
- const { enabled, lengthThresholdRelative, lengthThresholdAbsolute } = options.diffMatchPatch, segment = path[path.length - 1];
153
- if (!enabled || // Don't use for anything but strings
154
- typeof itemA != "string" || typeof itemB != "string" || // Don't use for `_key`, `_ref` etc
155
- typeof segment == "string" && segment[0] === "_" || // Don't use on short strings
156
- itemB.length < lengthThresholdAbsolute)
157
- return;
158
- let strPatch = "";
159
- try {
160
- const patch = diffMatchPatch.makeDiff(itemA, itemB), diff = diffMatchPatch.cleanupEfficiency(patch);
161
- strPatch = diffMatchPatch.stringifyPatches(diffMatchPatch.makePatches(diff));
162
- } catch {
163
- return;
164
- }
165
- return strPatch.length > itemB.length * lengthThresholdRelative ? void 0 : { op: "diffMatchPatch", path, value: strPatch };
166
- }
167
- function diffPrimitive(itemA, itemB, options, path, patches) {
168
- const dmp = getDiffMatchPatch(itemA, itemB, options, path);
169
- return patches.push(
170
- dmp || {
171
- op: "set",
172
- path,
173
- value: itemB
170
+ function shouldUseDiffMatchPatch(source, target) {
171
+ const maxLength = Math.max(source.length, target.length);
172
+ return maxLength > DMP_MAX_STRING_SIZE ? !1 : maxLength < DMP_MIN_SIZE_FOR_RATIO_CHECK ? !0 : !(Math.abs(target.length - source.length) / maxLength > DMP_MAX_STRING_LENGTH_CHANGE_RATIO);
173
+ }
174
+ function getDiffMatchPatch(source, target, path) {
175
+ const last = path.at(-1);
176
+ if (!(typeof last == "string" && last.startsWith("_")) && shouldUseDiffMatchPatch(source, target))
177
+ try {
178
+ const strPatch = diffMatchPatch.stringifyPatches(diffMatchPatch.makePatches(source, target));
179
+ return { op: "diffMatchPatch", path, value: strPatch };
180
+ } catch {
181
+ return;
174
182
  }
175
- ), patches;
183
+ }
184
+ function diffString(source, target, path, patches) {
185
+ const dmp = getDiffMatchPatch(source, target, path);
186
+ return patches.push(dmp ?? { op: "set", path, value: target }), patches;
176
187
  }
177
188
  function isNotIgnoredKey(key) {
178
- return ignoredKeys.indexOf(key) === -1;
179
- }
180
- function serializePatches(patches, options) {
181
- if (patches.length === 0)
182
- return [];
183
- const { id, ifRevisionID } = options, set = patches.filter((patch) => patch.op === "set"), unset = patches.filter((patch) => patch.op === "unset"), insert = patches.filter((patch) => patch.op === "insert"), dmp = patches.filter((patch) => patch.op === "diffMatchPatch"), withSet = set.length > 0 && set.reduce(
184
- (patch, item) => {
185
- const path = pathToString(item.path);
186
- return patch.set[path] = item.value, patch;
187
- },
188
- { id, set: {} }
189
- ), withUnset = unset.length > 0 && unset.reduce(
190
- (patch, item) => {
191
- const path = pathToString(item.path);
192
- return patch.unset.push(path), patch;
193
- },
194
- { id, unset: [] }
195
- ), withInsert = insert.reduce((acc, item) => {
196
- const after = pathToString(item.after);
197
- return acc.concat({ id, insert: { after, items: item.items } });
198
- }, []), withDmp = dmp.length > 0 && dmp.reduce(
199
- (patch, item) => {
200
- const path = pathToString(item.path);
201
- return patch.diffMatchPatch[path] = item.value, patch;
202
- },
203
- { id, diffMatchPatch: {} }
204
- );
205
- return [withUnset, withSet, withDmp, ...withInsert].filter(
206
- (item) => item !== !1
207
- ).map((patch, i) => ({
208
- patch: ifRevisionID && i === 0 ? { ...patch, ifRevisionID } : patch
209
- }));
189
+ return SYSTEM_KEYS.indexOf(key) === -1;
190
+ }
191
+ function serializePatches(patches, curr) {
192
+ const [patch, ...rest] = patches;
193
+ if (!patch) return curr ? [curr] : [];
194
+ switch (patch.op) {
195
+ case "set":
196
+ case "diffMatchPatch": {
197
+ const emptyOp = { [patch.op]: {} };
198
+ return curr ? patch.op in curr ? (Object.assign(curr[patch.op], { [pathToString(patch.path)]: patch.value }), serializePatches(rest, curr)) : [curr, ...serializePatches(patches, emptyOp)] : serializePatches(patches, emptyOp);
199
+ }
200
+ case "unset": {
201
+ const emptyOp = { unset: [] };
202
+ return curr ? "unset" in curr ? (curr.unset.push(pathToString(patch.path)), serializePatches(rest, curr)) : [curr, ...serializePatches(patches, emptyOp)] : serializePatches(patches, emptyOp);
203
+ }
204
+ case "insert":
205
+ return curr ? [curr, ...serializePatches(patches)] : [
206
+ {
207
+ insert: {
208
+ [patch.position]: pathToString(patch.path),
209
+ items: patch.items
210
+ }
211
+ },
212
+ ...serializePatches(rest)
213
+ ];
214
+ case "reorder": {
215
+ if (curr) return [curr, ...serializePatches(patches)];
216
+ const tempKeyOperations = {};
217
+ tempKeyOperations.set = {};
218
+ for (const { sourceKey, targetKey } of patch.reorders) {
219
+ const temporaryKey = `__temp_reorder_${sourceKey}__`, finalContentForThisPosition = patch.snapshot[getIndexForKey(patch.snapshot, targetKey)];
220
+ Object.assign(tempKeyOperations.set, {
221
+ [pathToString([...patch.path, { _key: sourceKey }])]: {
222
+ ...finalContentForThisPosition,
223
+ _key: temporaryKey
224
+ }
225
+ });
226
+ }
227
+ const finalKeyOperations = {};
228
+ finalKeyOperations.set = {};
229
+ for (const { sourceKey, targetKey } of patch.reorders) {
230
+ const temporaryKey = `__temp_reorder_${sourceKey}__`;
231
+ Object.assign(finalKeyOperations.set, {
232
+ [pathToString([...patch.path, { _key: temporaryKey }, "_key"])]: targetKey
233
+ });
234
+ }
235
+ return [tempKeyOperations, finalKeyOperations, ...serializePatches(rest)];
236
+ }
237
+ default:
238
+ return [];
239
+ }
210
240
  }
211
241
  function isUniquelyKeyed(arr) {
212
- const keys = [];
213
- for (let i = 0; i < arr.length; i++) {
214
- const key = getKey(arr[i]);
215
- if (!key || keys.indexOf(key) !== -1)
216
- return !1;
217
- keys.push(key);
242
+ const seenKeys = /* @__PURE__ */ new Set();
243
+ for (const item of arr) {
244
+ if (!isKeyedObject(item) || seenKeys.has(item._key)) return !1;
245
+ seenKeys.add(item._key);
218
246
  }
219
247
  return !0;
220
248
  }
221
- function getKey(obj) {
222
- return typeof obj == "object" && obj !== null && obj._key;
223
- }
224
- function indexByKey(arr) {
225
- return arr.reduce(
226
- (acc, item) => (acc.keys.push(item._key), acc.index[item._key] = item, acc),
227
- { keys: [], index: {} }
249
+ const keyToIndexCache = /* @__PURE__ */ new WeakMap();
250
+ function getIndexForKey(keyedArray, targetKey) {
251
+ const cachedMapping = keyToIndexCache.get(keyedArray);
252
+ if (cachedMapping) return cachedMapping[targetKey];
253
+ const keyToIndexMapping = keyedArray.reduce(
254
+ (mapping, { _key }, arrayIndex) => (mapping[_key] = arrayIndex, mapping),
255
+ {}
228
256
  );
257
+ return keyToIndexCache.set(keyedArray, keyToIndexMapping), keyToIndexMapping[targetKey];
229
258
  }
230
- function arrayIsEqual(itemA, itemB) {
231
- return itemA.length === itemB.length && itemA.every((item, i) => itemB[i] === item);
232
- }
233
- function nullifyUndefined(item, path, index, options) {
234
- if (typeof item < "u")
235
- return item;
236
- if (!options.hideWarnings) {
237
- const serializedPath = pathToString(path.concat(index));
238
- console.warn(`undefined value in array converted to null (at '${serializedPath}')`);
239
- }
240
- return null;
259
+ function isRecord(value) {
260
+ return typeof value == "object" && !!value && !Array.isArray(value);
241
261
  }
242
- function isRevisionLocked(options) {
243
- return !!options.ifRevisionID;
262
+ function nullifyUndefined(item) {
263
+ return item === void 0 ? null : item;
244
264
  }
245
265
  function yes(_) {
246
266
  return !0;
247
267
  }
248
268
  exports.DiffError = DiffError;
249
- exports.diffItem = diffItem;
250
269
  exports.diffPatch = diffPatch;
270
+ exports.diffValue = diffValue;
251
271
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../src/paths.ts","../src/diffError.ts","../src/validate.ts","../src/diffPatch.ts"],"sourcesContent":["import type {KeyedSanityObject} from './diffPatch.js'\n\nconst IS_DOTTABLE_RE = /^[A-Za-z_][A-Za-z0-9_]*$/\n\n/**\n * A segment of a path\n *\n * @public\n */\nexport type PathSegment =\n | string // Property\n | number // Array index\n | {_key: string} // Array `_key` lookup\n | [number | '', number | ''] // From/to array index\n\n/**\n * An array of path segments representing a path in a document\n *\n * @public\n */\nexport type Path = PathSegment[]\n\n/**\n * Converts an array path to a string path\n *\n * @param path - The array path to convert\n * @returns A stringified path\n * @internal\n */\nexport function pathToString(path: Path): string {\n return path.reduce((target: string, segment: PathSegment, i: number) => {\n if (Array.isArray(segment)) {\n return `${target}[${segment.join(':')}]`\n }\n\n if (isKeyedObject(segment)) {\n return `${target}[_key==\"${segment._key}\"]`\n }\n\n if (typeof segment === 'number') {\n return `${target}[${segment}]`\n }\n\n if (typeof segment === 'string' && !IS_DOTTABLE_RE.test(segment)) {\n return `${target}['${segment}']`\n }\n\n if (typeof segment === 'string') {\n const separator = i === 0 ? '' : '.'\n return `${target}${separator}${segment}`\n }\n\n throw new Error(`Unsupported path segment \"${segment}\"`)\n }, '')\n}\n\nfunction isKeyedObject(obj: any): obj is KeyedSanityObject {\n return typeof obj === 'object' && typeof obj._key === 'string'\n}\n","import {type Path, pathToString} from './paths.js'\n\n/**\n * Represents an error that occurred during a diff process.\n * Contains `path`, `value` and `serializedPath` properties,\n * which is helpful for debugging and showing friendly messages.\n *\n * @public\n */\nexport class DiffError extends Error {\n public path: Path\n public value: unknown\n public serializedPath: string\n\n constructor(message: string, path: Path, value?: unknown) {\n const serializedPath = pathToString(path)\n super(`${message} (at '${serializedPath}')`)\n\n this.path = path\n this.serializedPath = serializedPath\n this.value = value\n }\n}\n","import {DiffError} from './diffError.js'\nimport type {Path} from './paths.js'\n\nconst idPattern = /^[a-z0-9][a-z0-9_.-]+$/i\nconst propPattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/\nconst propStartPattern = /^[a-z_]/i\n\n/**\n * Validate the given document/subtree for Sanity compatibility\n *\n * @param item - The document or subtree to validate\n * @param path - The path to the current item, for error reporting\n * @returns True if valid, throws otherwise\n * @internal\n */\nexport function validateDocument(item: unknown, path: Path = []): boolean {\n if (Array.isArray(item)) {\n return item.every((child, i) => {\n if (Array.isArray(child)) {\n throw new DiffError('Multi-dimensional arrays not supported', path.concat(i))\n }\n\n return validateDocument(child, path.concat(i))\n })\n }\n\n if (typeof item === 'object' && item !== null) {\n const obj = item as {[key: string]: any}\n return Object.keys(obj).every(\n (key) =>\n validateProperty(key, obj[key], path) && validateDocument(obj[key], path.concat(key)),\n )\n }\n\n return true\n}\n\n/**\n * Validate a property for Sanity compatibility\n *\n * @param property - The property to valide\n * @param value - The value of the property\n * @param path - The path of the property, for error reporting\n * @returns The property name, if valid\n * @internal\n */\nexport function validateProperty(property: string, value: unknown, path: Path): string {\n if (!propStartPattern.test(property)) {\n throw new DiffError('Keys must start with a letter (a-z)', path.concat(property), value)\n }\n\n if (!propPattern.test(property)) {\n throw new DiffError(\n 'Keys can only contain letters, numbers and underscores',\n path.concat(property),\n value,\n )\n }\n\n if (property === '_key' || property === '_ref' || property === '_type') {\n if (typeof value !== 'string') {\n throw new DiffError('Keys must be strings', path.concat(property), value)\n }\n\n if (!idPattern.test(value)) {\n throw new DiffError('Invalid key - use less exotic characters', path.concat(property), value)\n }\n }\n\n return property\n}\n","import {cleanupEfficiency, makeDiff, makePatches, stringifyPatches} from '@sanity/diff-match-patch'\nimport {DiffError} from './diffError.js'\nimport {type Path, pathToString} from './paths.js'\nimport {validateProperty} from './validate.js'\nimport {\n type Patch,\n type SetPatch,\n type UnsetPatch,\n type InsertAfterPatch,\n type DiffMatchPatch,\n type SanityInsertPatch,\n type SanityPatch,\n type SanitySetPatch,\n type SanityUnsetPatch,\n type SanityDiffMatchPatch,\n type SanityPatchMutation,\n} from './patches.js'\n\nconst ignoredKeys = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']\n\ntype PrimitiveValue = string | number | boolean | null | undefined\n\n/**\n * An object (record) that has a `_key` property\n *\n * @internal\n */\nexport interface KeyedSanityObject {\n [key: string]: unknown\n _key: string\n}\n\n/**\n * An object (record) that _may_ have a `_key` property\n *\n * @internal\n */\nexport type SanityObject = KeyedSanityObject | Partial<KeyedSanityObject>\n\n/**\n * Represents a partial Sanity document (eg a \"stub\").\n *\n * @public\n */\nexport interface DocumentStub {\n _id?: string\n _type?: string\n _rev?: string\n _createdAt?: string\n _updatedAt?: string\n [key: string]: unknown\n}\n\n/**\n * Options for the diff-match-patch algorithm.\n *\n * @public\n */\nexport interface DiffMatchPatchOptions {\n /**\n * Whether or not to use diff-match-patch at all\n *\n * @defaultValue `true`\n */\n enabled: boolean\n\n /**\n * Threshold at which to start using diff-match-patch instead of a regular `set` patch.\n *\n * @defaultValue `30`\n */\n lengthThresholdAbsolute: number\n\n /**\n * Only use generated diff-match-patch if the patch length is less than or equal to\n * (targetString * relative). Example: A 100 character target with a relative factor\n * of 1.2 will allow a 120 character diff-match-patch. If larger than this number,\n * it will fall back to a regular `set` patch.\n *\n * @defaultValue `1.2`\n */\n lengthThresholdRelative: number\n}\n\n/**\n * Options for the patch generator\n *\n * @public\n */\nexport interface PatchOptions {\n /**\n * Document ID to apply the patch to.\n *\n * @defaultValue `undefined` - tries to extract `_id` from passed document\n */\n id?: string\n\n /**\n * Base path to apply the patch to - useful if diffing sub-branches of a document.\n *\n * @defaultValue `[]` - eg root of the document\n */\n basePath?: Path\n\n /**\n * Only apply the patch if the document revision matches this value.\n * If the property is the boolean value `true`, it will attempt to extract\n * the revision from the document `_rev` property.\n *\n * @defaultValue `undefined` (do not apply revision check)\n */\n ifRevisionID?: string | true\n\n /**\n * Whether or not to hide warnings during the diff process.\n *\n * @defaultValue `false`\n */\n hideWarnings?: boolean\n\n /**\n * Options for the diff-match-patch algorithm.\n */\n diffMatchPatch?: Partial<DiffMatchPatchOptions>\n}\n\n/**\n * Options for diff generation, where all DMP properties are required\n *\n * @public\n */\nexport type DiffOptions = PatchOptions & {diffMatchPatch: Required<DiffMatchPatchOptions>}\n\nconst defaultOptions = {\n hideWarnings: false,\n diffMatchPatch: {\n enabled: true,\n\n // Only use diff-match-patch if target string is longer than this threshold\n lengthThresholdAbsolute: 30,\n\n // Only use generated diff-match-patch if the patch length is less than or equal to\n // (targetString * relative). Example: A 100 character target with a relative factor\n // of 1.2 will allow a 120 character diff-match-patch. If larger than this number,\n // it will fall back to a regular `set` patch.\n lengthThresholdRelative: 1.2,\n },\n} satisfies DiffOptions\n\n/**\n * Merges the default options with the passed in options.\n *\n * @param options - Options to merge with the defaults\n * @returns Merged options\n */\nfunction mergeOptions(options: PatchOptions): DiffOptions {\n return {\n ...defaultOptions,\n ...options,\n diffMatchPatch: {...defaultOptions.diffMatchPatch, ...(options.diffMatchPatch || {})},\n }\n}\n\n/**\n * Generates an array of mutations for Sanity, based on the differences between\n * the two passed documents/trees.\n *\n * @param itemA - The first document/tree to compare\n * @param itemB - The second document/tree to compare\n * @param opts - Options for the diff generation\n * @returns Array of mutations\n * @public\n */\nexport function diffPatch(\n itemA: DocumentStub,\n itemB: DocumentStub,\n opts?: PatchOptions,\n): SanityPatchMutation[] {\n const options = mergeOptions(opts || {})\n const id = options.id || (itemA._id === itemB._id && itemA._id)\n const revisionLocked = options.ifRevisionID\n const ifRevisionID = typeof revisionLocked === 'boolean' ? itemA._rev : revisionLocked\n const basePath = options.basePath || []\n if (!id) {\n throw new Error(\n '_id on itemA and itemB not present or differs, specify document id the mutations should be applied to',\n )\n }\n\n if (revisionLocked === true && !ifRevisionID) {\n throw new Error(\n '`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.',\n )\n }\n\n if (basePath.length === 0 && itemA._type !== itemB._type) {\n throw new Error(`_type is immutable and cannot be changed (${itemA._type} => ${itemB._type})`)\n }\n\n const operations = diffItem(itemA, itemB, options, basePath, [])\n return serializePatches(operations, {id, ifRevisionID: revisionLocked ? ifRevisionID : undefined})\n}\n\n/**\n * Diffs two items and returns an array of patches.\n * Note that this is different from `diffPatch`, which generates _mutations_.\n *\n * @param itemA - The first item to compare\n * @param itemB - The second item to compare\n * @param opts - Options for the diff generation\n * @param path - Path to the current item\n * @param patches - Array of patches to append the results to. Note that this is MUTATED.\n * @returns Array of patches\n * @public\n */\nexport function diffItem(\n itemA: unknown,\n itemB: unknown,\n opts: DiffOptions = defaultOptions,\n path: Path = [],\n patches: Patch[] = [],\n): Patch[] {\n if (itemA === itemB) {\n return patches\n }\n\n const aType = Array.isArray(itemA) ? 'array' : typeof itemA\n const bType = Array.isArray(itemB) ? 'array' : typeof itemB\n\n const aIsUndefined = aType === 'undefined'\n const bIsUndefined = bType === 'undefined'\n\n if (aIsUndefined && !bIsUndefined) {\n patches.push({op: 'set', path, value: itemB})\n return patches\n }\n\n if (!aIsUndefined && bIsUndefined) {\n patches.push({op: 'unset', path})\n return patches\n }\n\n const options = mergeOptions(opts)\n const dataType = aIsUndefined ? bType : aType\n const isContainer = dataType === 'object' || dataType === 'array'\n if (!isContainer) {\n return diffPrimitive(itemA as PrimitiveValue, itemB as PrimitiveValue, options, path, patches)\n }\n\n if (aType !== bType) {\n // Array => Object / Object => Array\n patches.push({op: 'set', path, value: itemB})\n return patches\n }\n\n return dataType === 'array'\n ? diffArray(itemA as unknown[], itemB as unknown[], options, path, patches)\n : diffObject(itemA as object, itemB as object, options, path, patches)\n}\n\nfunction diffObject(\n itemA: SanityObject,\n itemB: SanityObject,\n options: DiffOptions,\n path: Path,\n patches: Patch[],\n) {\n const atRoot = path.length === 0\n const aKeys = Object.keys(itemA)\n .filter(atRoot ? isNotIgnoredKey : yes)\n .map((key) => validateProperty(key, itemA[key], path))\n\n const aKeysLength = aKeys.length\n const bKeys = Object.keys(itemB)\n .filter(atRoot ? isNotIgnoredKey : yes)\n .map((key) => validateProperty(key, itemB[key], path))\n\n const bKeysLength = bKeys.length\n\n // Check for deleted items\n for (let i = 0; i < aKeysLength; i++) {\n const key = aKeys[i]\n if (!(key in itemB)) {\n patches.push({op: 'unset', path: path.concat(key)})\n }\n }\n\n // Check for changed items\n for (let i = 0; i < bKeysLength; i++) {\n const key = bKeys[i]\n diffItem(itemA[key], itemB[key], options, path.concat([key]), patches)\n }\n\n return patches\n}\n\nfunction diffArray(\n itemA: unknown[],\n itemB: unknown[],\n options: DiffOptions,\n path: Path,\n patches: Patch[],\n) {\n // Check for new items\n if (itemB.length > itemA.length) {\n patches.push({\n op: 'insert',\n after: path.concat([-1]),\n items: itemB.slice(itemA.length).map((item, i) => nullifyUndefined(item, path, i, options)),\n })\n }\n\n // Check for deleted items\n if (itemB.length < itemA.length) {\n const isSingle = itemA.length - itemB.length === 1\n const unsetItems = itemA.slice(itemB.length)\n\n // If we're revision locked, we can safely unset ranges (eg 5:<end-of-array>).\n // Also, if we don't have unique array keys, we can't use any better approach\n // than array indexes. If we _do_ have unique array keys, we'll want to unset\n // by key, as this is safer in a realtime, collaborative setting\n if (isRevisionLocked(options) || !isUniquelyKeyed(unsetItems)) {\n patches.push({\n op: 'unset',\n path: path.concat([isSingle ? itemB.length : [itemB.length, '']]),\n })\n } else {\n patches.push(\n ...unsetItems.map(\n (item): UnsetPatch => ({op: 'unset', path: path.concat({_key: item._key})}),\n ),\n )\n }\n }\n\n // Check for illegal array contents\n for (let i = 0; i < itemB.length; i++) {\n if (Array.isArray(itemB[i])) {\n throw new DiffError('Multi-dimensional arrays not supported', path.concat(i), itemB[i])\n }\n }\n\n const overlapping = Math.min(itemA.length, itemB.length)\n const segmentA = itemA.slice(0, overlapping)\n const segmentB = itemB.slice(0, overlapping)\n\n return isUniquelyKeyed(segmentA) && isUniquelyKeyed(segmentB)\n ? diffArrayByKey(segmentA, segmentB, options, path, patches)\n : diffArrayByIndex(segmentA, segmentB, options, path, patches)\n}\n\nfunction diffArrayByIndex(\n itemA: unknown[],\n itemB: unknown[],\n options: DiffOptions,\n path: Path,\n patches: Patch[],\n) {\n for (let i = 0; i < itemA.length; i++) {\n diffItem(\n itemA[i],\n nullifyUndefined(itemB[i], path, i, options),\n options,\n path.concat(i),\n patches,\n )\n }\n\n return patches\n}\n\nfunction diffArrayByKey(\n itemA: KeyedSanityObject[],\n itemB: KeyedSanityObject[],\n options: DiffOptions,\n path: Path,\n patches: Patch[],\n) {\n const keyedA = indexByKey(itemA)\n const keyedB = indexByKey(itemB)\n\n // There's a bunch of hard/semi-hard problems related to using keys\n // Unless we have the exact same order, just use indexes for now\n if (!arrayIsEqual(keyedA.keys, keyedB.keys)) {\n return diffArrayByIndex(itemA, itemB, options, path, patches)\n }\n\n for (let i = 0; i < keyedB.keys.length; i++) {\n const key = keyedB.keys[i]\n const valueA = keyedA.index[key]\n const valueB = nullifyUndefined(keyedB.index[key], path, i, options)\n diffItem(valueA, valueB, options, path.concat({_key: key}), patches)\n }\n\n return patches\n}\n\nfunction getDiffMatchPatch(\n itemA: PrimitiveValue,\n itemB: PrimitiveValue,\n options: DiffOptions,\n path: Path,\n): DiffMatchPatch | undefined {\n const {enabled, lengthThresholdRelative, lengthThresholdAbsolute} = options.diffMatchPatch\n const segment = path[path.length - 1]\n if (\n !enabled ||\n // Don't use for anything but strings\n typeof itemA !== 'string' ||\n typeof itemB !== 'string' ||\n // Don't use for `_key`, `_ref` etc\n (typeof segment === 'string' && segment[0] === '_') ||\n // Don't use on short strings\n itemB.length < lengthThresholdAbsolute\n ) {\n return undefined\n }\n\n let strPatch = ''\n try {\n const patch = makeDiff(itemA, itemB)\n const diff = cleanupEfficiency(patch)\n strPatch = stringifyPatches(makePatches(diff))\n } catch (err) {\n // Fall back to using regular set patch\n return undefined\n }\n\n // Don't use patch if it's longer than allowed relative threshold.\n // Allow a 120 character patch for a 100 character string,\n // but don't allow a 800 character patch for a 500 character value.\n return strPatch.length > itemB.length * lengthThresholdRelative\n ? undefined\n : {op: 'diffMatchPatch', path, value: strPatch}\n}\n\nfunction diffPrimitive(\n itemA: PrimitiveValue,\n itemB: PrimitiveValue,\n options: DiffOptions,\n path: Path,\n patches: Patch[],\n): Patch[] {\n const dmp = getDiffMatchPatch(itemA, itemB, options, path)\n\n patches.push(\n dmp || {\n op: 'set',\n path,\n value: itemB,\n },\n )\n\n return patches\n}\n\nfunction isNotIgnoredKey(key: string) {\n return ignoredKeys.indexOf(key) === -1\n}\n\nfunction serializePatches(\n patches: Patch[],\n options: {id: string; ifRevisionID?: string},\n): SanityPatchMutation[] {\n if (patches.length === 0) {\n return []\n }\n\n const {id, ifRevisionID} = options\n const set = patches.filter((patch): patch is SetPatch => patch.op === 'set')\n const unset = patches.filter((patch): patch is UnsetPatch => patch.op === 'unset')\n const insert = patches.filter((patch): patch is InsertAfterPatch => patch.op === 'insert')\n const dmp = patches.filter((patch): patch is DiffMatchPatch => patch.op === 'diffMatchPatch')\n\n const withSet =\n set.length > 0 &&\n set.reduce(\n (patch: SanitySetPatch, item: SetPatch) => {\n const path = pathToString(item.path)\n patch.set[path] = item.value\n return patch\n },\n {id, set: {}},\n )\n\n const withUnset =\n unset.length > 0 &&\n unset.reduce(\n (patch: SanityUnsetPatch, item: UnsetPatch) => {\n const path = pathToString(item.path)\n patch.unset.push(path)\n return patch\n },\n {id, unset: []},\n )\n\n const withInsert = insert.reduce((acc: SanityInsertPatch[], item: InsertAfterPatch) => {\n const after = pathToString(item.after)\n return acc.concat({id, insert: {after, items: item.items}})\n }, [])\n\n const withDmp =\n dmp.length > 0 &&\n dmp.reduce(\n (patch: SanityDiffMatchPatch, item: DiffMatchPatch) => {\n const path = pathToString(item.path)\n patch.diffMatchPatch[path] = item.value\n return patch\n },\n {id, diffMatchPatch: {}},\n )\n\n const patchSet: SanityPatch[] = [withUnset, withSet, withDmp, ...withInsert].filter(\n (item): item is SanityPatch => item !== false,\n )\n\n return patchSet.map((patch, i) => ({\n patch: ifRevisionID && i === 0 ? {...patch, ifRevisionID} : patch,\n }))\n}\n\nfunction isUniquelyKeyed(arr: unknown[]): arr is KeyedSanityObject[] {\n const keys = []\n\n for (let i = 0; i < arr.length; i++) {\n const key = getKey(arr[i])\n if (!key || keys.indexOf(key) !== -1) {\n return false\n }\n\n keys.push(key)\n }\n\n return true\n}\n\nfunction getKey(obj: unknown) {\n return typeof obj === 'object' && obj !== null && (obj as KeyedSanityObject)._key\n}\n\nfunction indexByKey(arr: KeyedSanityObject[]) {\n return arr.reduce(\n (acc, item) => {\n acc.keys.push(item._key)\n acc.index[item._key] = item\n return acc\n },\n {keys: [] as string[], index: {} as {[key: string]: KeyedSanityObject}},\n )\n}\n\nfunction arrayIsEqual(itemA: unknown[], itemB: unknown[]) {\n return itemA.length === itemB.length && itemA.every((item, i) => itemB[i] === item)\n}\n\nfunction nullifyUndefined(item: unknown, path: Path, index: number, options: PatchOptions) {\n if (typeof item !== 'undefined') {\n return item\n }\n\n if (!options.hideWarnings) {\n const serializedPath = pathToString(path.concat(index))\n console.warn(`undefined value in array converted to null (at '${serializedPath}')`)\n }\n\n return null\n}\n\nfunction isRevisionLocked(options: PatchOptions): boolean {\n return Boolean(options.ifRevisionID)\n}\n\nfunction yes(_: unknown) {\n return true\n}\n"],"names":["makeDiff","cleanupEfficiency","stringifyPatches","makePatches"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../src/paths.ts","../src/diffError.ts","../src/validate.ts","../src/setOperations.ts","../src/diffPatch.ts"],"sourcesContent":["const IS_DOTTABLE_RE = /^[A-Za-z_][A-Za-z0-9_]*$/\n\n/**\n * A segment of a path\n *\n * @public\n */\nexport type PathSegment =\n | string // Property\n | number // Array index\n | {_key: string} // Array `_key` lookup\n | [number | '', number | ''] // From/to array index\n\n/**\n * An array of path segments representing a path in a document\n *\n * @public\n */\nexport type Path = PathSegment[]\n\n/**\n * Converts an array path to a string path\n *\n * @param path - The array path to convert\n * @returns A stringified path\n * @internal\n */\nexport function pathToString(path: Path): string {\n return path.reduce((target: string, segment: PathSegment, i: number) => {\n if (Array.isArray(segment)) {\n return `${target}[${segment.join(':')}]`\n }\n\n if (isKeyedObject(segment)) {\n return `${target}[_key==\"${segment._key}\"]`\n }\n\n if (typeof segment === 'number') {\n return `${target}[${segment}]`\n }\n\n if (typeof segment === 'string' && !IS_DOTTABLE_RE.test(segment)) {\n return `${target}['${segment}']`\n }\n\n if (typeof segment === 'string') {\n const separator = i === 0 ? '' : '.'\n return `${target}${separator}${segment}`\n }\n\n throw new Error(`Unsupported path segment \"${segment}\"`)\n }, '')\n}\n\n/**\n * An object (record) that has a `_key` property\n *\n * @internal\n */\nexport interface KeyedSanityObject {\n [key: string]: unknown\n _key: string\n}\n\nexport function isKeyedObject(obj: unknown): obj is KeyedSanityObject {\n return typeof obj === 'object' && !!obj && '_key' in obj && typeof obj._key === 'string'\n}\n","import {type Path, pathToString} from './paths.js'\n\n/**\n * Represents an error that occurred during a diff process.\n * Contains `path`, `value` and `serializedPath` properties,\n * which is helpful for debugging and showing friendly messages.\n *\n * @public\n */\nexport class DiffError extends Error {\n public path: Path\n public value: unknown\n public serializedPath: string\n\n constructor(message: string, path: Path, value?: unknown) {\n const serializedPath = pathToString(path)\n super(`${message} (at '${serializedPath}')`)\n\n this.path = path\n this.serializedPath = serializedPath\n this.value = value\n }\n}\n","import {DiffError} from './diffError.js'\nimport type {Path} from './paths.js'\n\nconst idPattern = /^[a-z0-9][a-z0-9_.-]+$/i\nconst propPattern = /^[a-zA-Z_][a-zA-Z0-9_-]*$/\nconst propStartPattern = /^[a-z_]/i\n\n/**\n * Validate the given document/subtree for Sanity compatibility\n *\n * @param item - The document or subtree to validate\n * @param path - The path to the current item, for error reporting\n * @returns True if valid, throws otherwise\n * @internal\n */\nexport function validateDocument(item: unknown, path: Path = []): boolean {\n if (Array.isArray(item)) {\n return item.every((child, i) => {\n if (Array.isArray(child)) {\n throw new DiffError('Multi-dimensional arrays not supported', path.concat(i))\n }\n\n return validateDocument(child, path.concat(i))\n })\n }\n\n if (typeof item === 'object' && item !== null) {\n const obj = item as {[key: string]: any}\n return Object.keys(obj).every(\n (key) =>\n validateProperty(key, obj[key], path) && validateDocument(obj[key], path.concat(key)),\n )\n }\n\n return true\n}\n\n/**\n * Validate a property for Sanity compatibility\n *\n * @param property - The property to valide\n * @param value - The value of the property\n * @param path - The path of the property, for error reporting\n * @returns The property name, if valid\n * @internal\n */\nexport function validateProperty(property: string, value: unknown, path: Path): string {\n if (!propStartPattern.test(property)) {\n throw new DiffError('Keys must start with a letter (a-z)', path.concat(property), value)\n }\n\n if (!propPattern.test(property)) {\n throw new DiffError(\n 'Keys can only contain letters, numbers and underscores',\n path.concat(property),\n value,\n )\n }\n\n if (property === '_key' || property === '_ref' || property === '_type') {\n if (typeof value !== 'string') {\n throw new DiffError('Keys must be strings', path.concat(property), value)\n }\n\n if (!idPattern.test(value)) {\n throw new DiffError('Invalid key - use less exotic characters', path.concat(property), value)\n }\n }\n\n return property\n}\n","/// <reference lib=\"esnext\" />\n\nexport function difference<T>(source: Set<T>, target: Set<T>): Set<T> {\n if ('difference' in Set.prototype) {\n return source.difference(target)\n }\n\n const result = new Set<T>()\n for (const item of source) {\n if (!target.has(item)) {\n result.add(item)\n }\n }\n return result\n}\n\nexport function intersection<T>(source: Set<T>, target: Set<T>): Set<T> {\n if ('intersection' in Set.prototype) {\n return source.intersection(target)\n }\n\n const result = new Set<T>()\n for (const item of source) {\n if (target.has(item)) {\n result.add(item)\n }\n }\n return result\n}\n","import {makePatches, stringifyPatches} from '@sanity/diff-match-patch'\nimport {DiffError} from './diffError.js'\nimport {isKeyedObject, type KeyedSanityObject, type Path, pathToString} from './paths.js'\nimport {validateProperty} from './validate.js'\nimport {\n type Patch,\n type UnsetPatch,\n type DiffMatchPatch,\n type SanityPatchMutation,\n type SanityPatchOperations,\n type SanitySetPatchOperation,\n type SanityUnsetPatchOperation,\n type SanityInsertPatchOperation,\n type SanityDiffMatchPatchOperation,\n} from './patches.js'\nimport {difference, intersection} from './setOperations.js'\n\n/**\n * Document keys that are ignored during diff operations.\n * These are system-managed fields that should not be included in patches on\n * top-level documents and should not be diffed with diff-match-patch.\n */\nconst SYSTEM_KEYS = ['_id', '_type', '_createdAt', '_updatedAt', '_rev']\n\n/**\n * Maximum size of strings to consider for diff-match-patch (1MB)\n * Based on testing showing consistently good performance up to this size\n */\nconst DMP_MAX_STRING_SIZE = 1_000_000\n\n/**\n * Maximum difference in string length before falling back to set operations (40%)\n * Above this threshold, likely indicates text replacement which can be slow\n */\nconst DMP_MAX_STRING_LENGTH_CHANGE_RATIO = 0.4\n\n/**\n * Minimum string size to apply change ratio check (10KB)\n * Small strings are always fast regardless of change ratio\n */\nconst DMP_MIN_SIZE_FOR_RATIO_CHECK = 10_000\n\n/**\n * An object (record) that _may_ have a `_key` property\n *\n * @internal\n */\nexport type SanityObject = KeyedSanityObject | Partial<KeyedSanityObject>\n\n/**\n * Represents a partial Sanity document (eg a \"stub\").\n *\n * @public\n */\nexport interface DocumentStub {\n _id?: string\n _type?: string\n _rev?: string\n _createdAt?: string\n _updatedAt?: string\n [key: string]: unknown\n}\n\n/**\n * Options for the patch generator\n *\n * @public\n */\nexport interface PatchOptions {\n /**\n * Document ID to apply the patch to.\n *\n * @defaultValue `undefined` - tries to extract `_id` from passed document\n */\n id?: string\n\n /**\n * Base path to apply the patch to - useful if diffing sub-branches of a document.\n *\n * @defaultValue `[]` - eg root of the document\n */\n basePath?: Path\n\n /**\n * Only apply the patch if the document revision matches this value.\n * If the property is the boolean value `true`, it will attempt to extract\n * the revision from the document `_rev` property.\n *\n * @defaultValue `undefined` (do not apply revision check)\n */\n ifRevisionID?: string | true\n}\n\n/**\n * Generates an array of mutations for Sanity, based on the differences between\n * the two passed documents/trees.\n *\n * @param source - The first document/tree to compare\n * @param target - The second document/tree to compare\n * @param opts - Options for the diff generation\n * @returns Array of mutations\n * @public\n */\nexport function diffPatch(\n source: DocumentStub,\n target: DocumentStub,\n options: PatchOptions = {},\n): SanityPatchMutation[] {\n const id = options.id || (source._id === target._id && source._id)\n const revisionLocked = options.ifRevisionID\n const ifRevisionID = typeof revisionLocked === 'boolean' ? source._rev : revisionLocked\n const basePath = options.basePath || []\n if (!id) {\n throw new Error(\n '_id on source and target not present or differs, specify document id the mutations should be applied to',\n )\n }\n\n if (revisionLocked === true && !ifRevisionID) {\n throw new Error(\n '`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.',\n )\n }\n\n if (basePath.length === 0 && source._type !== target._type) {\n throw new Error(`_type is immutable and cannot be changed (${source._type} => ${target._type})`)\n }\n\n const operations = diffItem(source, target, basePath, [])\n return serializePatches(operations).map((patchOperations, i) => ({\n patch: {\n id,\n // only add `ifRevisionID` to the first patch\n ...(i === 0 && ifRevisionID && {ifRevisionID}),\n ...patchOperations,\n },\n }))\n}\n\n/**\n * Generates an array of patch operation objects for Sanity, based on the\n * differences between the two passed values\n *\n * @param source - The source value to start off with\n * @param target - The target value that the patch operations will aim to create\n * @param basePath - An optional path that will be prefixed to all subsequent patch operations\n * @returns Array of mutations\n * @public\n */\nexport function diffValue(\n source: unknown,\n target: unknown,\n basePath: Path = [],\n): SanityPatchOperations[] {\n return serializePatches(diffItem(source, target, basePath))\n}\n\nfunction diffItem(\n source: unknown,\n target: unknown,\n path: Path = [],\n patches: Patch[] = [],\n): Patch[] {\n if (source === target) {\n return patches\n }\n\n if (typeof source === 'string' && typeof target === 'string') {\n diffString(source, target, path, patches)\n return patches\n }\n\n if (Array.isArray(source) && Array.isArray(target)) {\n diffArray(source, target, path, patches)\n return patches\n }\n\n if (isRecord(source) && isRecord(target)) {\n diffObject(source, target, path, patches)\n return patches\n }\n\n if (target === undefined) {\n patches.push({op: 'unset', path})\n return patches\n }\n\n patches.push({op: 'set', path, value: target})\n return patches\n}\n\nfunction diffObject(\n source: Record<string, unknown>,\n target: Record<string, unknown>,\n path: Path,\n patches: Patch[],\n) {\n const atRoot = path.length === 0\n const aKeys = Object.keys(source)\n .filter(atRoot ? isNotIgnoredKey : yes)\n .map((key) => validateProperty(key, source[key], path))\n\n const aKeysLength = aKeys.length\n const bKeys = Object.keys(target)\n .filter(atRoot ? isNotIgnoredKey : yes)\n .map((key) => validateProperty(key, target[key], path))\n\n const bKeysLength = bKeys.length\n\n // Check for deleted items\n for (let i = 0; i < aKeysLength; i++) {\n const key = aKeys[i]\n if (!(key in target)) {\n patches.push({op: 'unset', path: path.concat(key)})\n }\n }\n\n // Check for changed items\n for (let i = 0; i < bKeysLength; i++) {\n const key = bKeys[i]\n diffItem(source[key], target[key], path.concat([key]), patches)\n }\n\n return patches\n}\n\nfunction diffArray(source: unknown[], target: unknown[], path: Path, patches: Patch[]) {\n if (isUniquelyKeyed(source) && isUniquelyKeyed(target)) {\n return diffArrayByKey(source, target, path, patches)\n }\n\n return diffArrayByIndex(source, target, path, patches)\n}\n\nfunction diffArrayByIndex(source: unknown[], target: unknown[], path: Path, patches: Patch[]) {\n // Check for new items\n if (target.length > source.length) {\n patches.push({\n op: 'insert',\n position: 'after',\n path: path.concat([-1]),\n items: target.slice(source.length).map(nullifyUndefined),\n })\n }\n\n // Check for deleted items\n if (target.length < source.length) {\n const isSingle = source.length - target.length === 1\n const unsetItems = source.slice(target.length)\n\n // If we have unique array keys, we'll want to unset by key, as this is\n // safer in a realtime, collaborative setting\n if (isUniquelyKeyed(unsetItems)) {\n patches.push(\n ...unsetItems.map(\n (item): UnsetPatch => ({op: 'unset', path: path.concat({_key: item._key})}),\n ),\n )\n } else {\n patches.push({\n op: 'unset',\n path: path.concat([isSingle ? target.length : [target.length, '']]),\n })\n }\n }\n\n // Check for illegal array contents\n for (let i = 0; i < target.length; i++) {\n if (Array.isArray(target[i])) {\n throw new DiffError('Multi-dimensional arrays not supported', path.concat(i), target[i])\n }\n }\n\n const overlapping = Math.min(source.length, target.length)\n const segmentA = source.slice(0, overlapping)\n const segmentB = target.slice(0, overlapping)\n\n for (let i = 0; i < segmentA.length; i++) {\n diffItem(segmentA[i], nullifyUndefined(segmentB[i]), path.concat(i), patches)\n }\n\n return patches\n}\n\n/**\n * Diffs two arrays of keyed objects by their `_key` properties.\n *\n * This approach is preferred over index-based diffing for collaborative editing scenarios\n * because it generates patches that are more resilient to concurrent modifications.\n * When multiple users edit the same array simultaneously, key-based patches have better\n * conflict resolution characteristics than index-based patches.\n *\n * The function handles three main operations:\n * 1. **Reordering**: When existing items change positions within the array\n * 2. **Content changes**: When the content of existing items is modified\n * 3. **Insertions/Deletions**: When items are added or removed from the array\n *\n * @param source - The original array with keyed objects\n * @param target - The target array with keyed objects\n * @param path - The path to this array within the document\n * @param patches - Array to accumulate generated patches\n * @returns The patches array with new patches appended\n */\nfunction diffArrayByKey(\n source: KeyedSanityObject[],\n target: KeyedSanityObject[],\n path: Path,\n patches: Patch[],\n) {\n // Create lookup maps for efficient key-based access to array items\n const sourceItemsByKey = new Map(source.map((item) => [item._key, item]))\n const targetItemsByKey = new Map(target.map((item) => [item._key, item]))\n\n // Categorize keys by their presence in source vs target arrays\n const sourceKeys = new Set(sourceItemsByKey.keys())\n const targetKeys = new Set(targetItemsByKey.keys())\n const keysRemovedFromSource = difference(sourceKeys, targetKeys)\n const keysAddedToTarget = difference(targetKeys, sourceKeys)\n const keysInBothArrays = intersection(sourceKeys, targetKeys)\n\n // Handle reordering of existing items within the array.\n // We detect reordering by comparing the relative positions of keys that exist in both arrays,\n // excluding keys that were added or removed (since they don't participate in reordering).\n const sourceKeysStillPresent = Array.from(difference(sourceKeys, keysRemovedFromSource))\n const targetKeysAlreadyPresent = Array.from(difference(targetKeys, keysAddedToTarget))\n\n // Track which keys need to be reordered by comparing their relative positions\n const keyReorderOperations: {sourceKey: string; targetKey: string}[] = []\n\n for (let i = 0; i < keysInBothArrays.size; i++) {\n const keyAtPositionInSource = sourceKeysStillPresent[i]\n const keyAtPositionInTarget = targetKeysAlreadyPresent[i]\n\n // If different keys occupy the same relative position, a reorder is needed\n if (keyAtPositionInSource !== keyAtPositionInTarget) {\n keyReorderOperations.push({\n sourceKey: keyAtPositionInSource,\n targetKey: keyAtPositionInTarget,\n })\n }\n }\n\n // Generate reorder patch if any items changed positions\n if (keyReorderOperations.length) {\n patches.push({\n op: 'reorder',\n path,\n snapshot: source,\n reorders: keyReorderOperations,\n })\n }\n\n // Process content changes for items that exist in both arrays\n for (const key of keysInBothArrays) {\n diffItem(sourceItemsByKey.get(key), targetItemsByKey.get(key), [...path, {_key: key}], patches)\n }\n\n // Remove items that no longer exist in the target array\n for (const keyToRemove of keysRemovedFromSource) {\n patches.push({op: 'unset', path: [...path, {_key: keyToRemove}]})\n }\n\n // Insert new items that were added to the target array\n // We batch consecutive insertions for efficiency and insert them at the correct positions\n if (keysAddedToTarget.size) {\n let insertionAnchorKey: string // The key after which we'll insert pending items\n let itemsPendingInsertion: unknown[] = []\n\n const flushPendingInsertions = () => {\n if (itemsPendingInsertion.length) {\n patches.push({\n op: 'insert',\n // Insert after the anchor key if we have one, otherwise insert at the beginning\n ...(insertionAnchorKey\n ? {position: 'after', path: [...path, {_key: insertionAnchorKey}]}\n : {position: 'before', path: [...path, 0]}),\n items: itemsPendingInsertion,\n })\n }\n }\n\n // Walk through the target array to determine where new items should be inserted\n for (const key of targetKeys) {\n if (keysAddedToTarget.has(key)) {\n // This is a new item - add it to the pending insertion batch\n itemsPendingInsertion.push(targetItemsByKey.get(key)!)\n } else if (keysInBothArrays.has(key)) {\n // This is an existing item - flush any pending insertions before it\n flushPendingInsertions()\n insertionAnchorKey = key\n itemsPendingInsertion = []\n }\n }\n\n // Flush any remaining insertions at the end\n flushPendingInsertions()\n }\n\n return patches\n}\n\n/**\n * Determines whether to use diff-match-patch or fallback to a `set` operation\n * when creating a patch to transform a `source` string to `target` string.\n *\n * `diffMatchPatch` patches are typically preferred to `set` operations because\n * they handle conflicts better (when multiple editors work simultaneously) by\n * preserving the user's intended and allowing for 3-way merges.\n *\n * **Heuristic rationale:**\n *\n * Perf analysis revealed that string length has minimal impact on small,\n * keystroke-level changes, but large text replacements (high change ratio) can\n * trigger worst-case algorithm behavior. The 40% change ratio threshold is a\n * simple heuristic that catches problematic replacement scenarios while\n * allowing the algorithm to excel at insertions and deletions.\n *\n * **Performance characteristics (tested on M2 MacBook Pro):**\n *\n * *Keystroke-level editing (most common use case):*\n * - Small strings (1KB-10KB): 0ms for 1-5 keystrokes, consistently sub-millisecond\n * - Medium strings (50KB-200KB): 0ms for 1-5 keystrokes, consistently sub-millisecond\n * - 10 simultaneous keystrokes: ~12ms on 100KB strings\n *\n * *Copy-paste operations (less frequent):*\n * - Small copy-paste operations (<50KB): 0-10ms regardless of string length\n * - Large insertions/deletions (50KB+): 0-50ms (excellent performance)\n * - Large text replacements (50KB+): 70ms-2s+ (can be slow due to algorithm complexity)\n *\n * **Algorithm details:**\n * Uses Myers' diff algorithm with O(ND) time complexity where N=text length and D=edit distance.\n * Includes optimizations: common prefix/suffix removal, line-mode processing, and timeout protection.\n *\n *\n * **Test methodology:**\n * - Generated realistic word-based text patterns\n * - Simulated actual editing behaviors (keystrokes vs copy-paste)\n * - Measured performance across string sizes from 1KB to 10MB\n * - Validated against edge cases including repetitive text and scattered changes\n *\n * @param source - The previous version of the text\n * @param target - The new version of the text\n * @returns true if diff-match-patch should be used, false if fallback to set operation\n *\n * @example\n * ```typescript\n * // Keystroke editing - always fast\n * shouldUseDiffMatchPatch(largeDoc, largeDocWithTypo) // true, ~0ms\n *\n * // Small paste - always fast\n * shouldUseDiffMatchPatch(doc, docWithSmallInsertion) // true, ~0ms\n *\n * // Large replacement - potentially slow\n * shouldUseDiffMatchPatch(article, completelyDifferentArticle) // false, use set\n * ```\n *\n * Compatible with @sanity/diff-match-patch@3.2.0\n */\nexport function shouldUseDiffMatchPatch(source: string, target: string): boolean {\n const maxLength = Math.max(source.length, target.length)\n\n // Always reject strings larger than our tested size limit\n if (maxLength > DMP_MAX_STRING_SIZE) {\n return false\n }\n\n // For small strings, always use diff-match-patch regardless of change ratio\n // Performance testing showed these are always fast (<10ms)\n if (maxLength < DMP_MIN_SIZE_FOR_RATIO_CHECK) {\n return true\n }\n\n // Calculate the change ratio to detect large text replacements\n // High ratios indicate replacement scenarios which can trigger slow algorithm paths\n const lengthDifference = Math.abs(target.length - source.length)\n const changeRatio = lengthDifference / maxLength\n\n // If change ratio is high, likely a replacement operation that could be slow\n // Fall back to set operation for better user experience\n if (changeRatio > DMP_MAX_STRING_LENGTH_CHANGE_RATIO) {\n return false\n }\n\n // All other cases: use diff-match-patch\n // This covers keystroke editing and insertion/deletion scenarios which perform excellently\n return true\n}\n\nfunction getDiffMatchPatch(source: string, target: string, path: Path): DiffMatchPatch | undefined {\n const last = path.at(-1)\n // don't use diff-match-patch for system keys\n if (typeof last === 'string' && last.startsWith('_')) return undefined\n if (!shouldUseDiffMatchPatch(source, target)) return undefined\n\n try {\n // Using `makePatches(string, string)` directly instead of the multi-step approach e.g.\n // `stringifyPatches(makePatches(cleanupEfficiency(makeDiff(source, target))))`.\n // this is because `makePatches` internally handles diff generation and\n // automatically applies both `cleanupSemantic()` and `cleanupEfficiency()`\n // when beneficial, resulting in cleaner code with near identical performance and\n // better error handling.\n // [source](https://github.com/sanity-io/diff-match-patch/blob/v3.2.0/src/patch/make.ts#L67-L76)\n //\n // Performance validation (M2 MacBook Pro):\n // Both approaches measured at identical performance:\n // - 10KB strings: 0-1ms total processing time\n // - 100KB strings: 0-1ms total processing time\n // - Individual step breakdown: makeDiff(0ms) + cleanup(0ms) + makePatches(0ms) + stringify(~1ms)\n const strPatch = stringifyPatches(makePatches(source, target))\n return {op: 'diffMatchPatch', path, value: strPatch}\n } catch (err) {\n // Fall back to using regular set patch\n return undefined\n }\n}\n\nfunction diffString(source: string, target: string, path: Path, patches: Patch[]) {\n const dmp = getDiffMatchPatch(source, target, path)\n patches.push(dmp ?? {op: 'set', path, value: target})\n return patches\n}\n\nfunction isNotIgnoredKey(key: string) {\n return SYSTEM_KEYS.indexOf(key) === -1\n}\n\n// mutually exclusive operations\ntype SanityPatchOperation =\n | SanitySetPatchOperation\n | SanityUnsetPatchOperation\n | SanityInsertPatchOperation\n | SanityDiffMatchPatchOperation\n\nfunction serializePatches(patches: Patch[], curr?: SanityPatchOperation): SanityPatchOperations[] {\n const [patch, ...rest] = patches\n if (!patch) return curr ? [curr] : []\n\n switch (patch.op) {\n case 'set':\n case 'diffMatchPatch': {\n // TODO: reconfigure eslint to use @typescript-eslint/no-unused-vars\n // eslint-disable-next-line no-unused-vars\n type CurrentOp = Extract<SanityPatchOperation, {[K in typeof patch.op]: {}}>\n const emptyOp = {[patch.op]: {}} as CurrentOp\n\n if (!curr) return serializePatches(patches, emptyOp)\n if (!(patch.op in curr)) return [curr, ...serializePatches(patches, emptyOp)]\n\n Object.assign((curr as CurrentOp)[patch.op], {[pathToString(patch.path)]: patch.value})\n return serializePatches(rest, curr)\n }\n case 'unset': {\n const emptyOp = {unset: []}\n if (!curr) return serializePatches(patches, emptyOp)\n if (!('unset' in curr)) return [curr, ...serializePatches(patches, emptyOp)]\n\n curr.unset.push(pathToString(patch.path))\n return serializePatches(rest, curr)\n }\n case 'insert': {\n if (curr) return [curr, ...serializePatches(patches)]\n\n return [\n {\n insert: {\n [patch.position]: pathToString(patch.path),\n items: patch.items,\n },\n } as SanityInsertPatchOperation,\n ...serializePatches(rest),\n ]\n }\n case 'reorder': {\n if (curr) return [curr, ...serializePatches(patches)]\n\n // REORDER STRATEGY: Two-phase approach to avoid key collisions\n //\n // Problem: Direct key swaps can cause collisions. For example, swapping A↔B:\n // - Set A's content to B: ✓\n // - Set B's content to A: ✗ (A's content was already overwritten)\n //\n // Solution: Use temporary keys as an intermediate step\n // Phase 1: Move all items to temporary keys with their final content\n // Phase 2: Update just the _key property to restore the final keys\n\n // Phase 1: Move items to collision-safe temporary keys\n const tempKeyOperations: SanityPatchOperations = {}\n tempKeyOperations.set = {}\n\n for (const {sourceKey, targetKey} of patch.reorders) {\n const temporaryKey = `__temp_reorder_${sourceKey}__`\n const finalContentForThisPosition =\n patch.snapshot[getIndexForKey(patch.snapshot, targetKey)]\n\n Object.assign(tempKeyOperations.set, {\n [pathToString([...patch.path, {_key: sourceKey}])]: {\n ...finalContentForThisPosition,\n _key: temporaryKey,\n },\n })\n }\n\n // Phase 2: Update _key properties to restore the intended final keys\n const finalKeyOperations: SanityPatchOperations = {}\n finalKeyOperations.set = {}\n\n for (const {sourceKey, targetKey} of patch.reorders) {\n const temporaryKey = `__temp_reorder_${sourceKey}__`\n\n Object.assign(finalKeyOperations.set, {\n [pathToString([...patch.path, {_key: temporaryKey}, '_key'])]: targetKey,\n })\n }\n\n return [tempKeyOperations, finalKeyOperations, ...serializePatches(rest)]\n }\n default: {\n return []\n }\n }\n}\n\nfunction isUniquelyKeyed(arr: unknown[]): arr is KeyedSanityObject[] {\n const seenKeys = new Set<string>()\n\n for (const item of arr) {\n // Each item must be a keyed object with a _key property\n if (!isKeyedObject(item)) return false\n\n // Each _key must be unique within the array\n if (seenKeys.has(item._key)) return false\n\n seenKeys.add(item._key)\n }\n\n return true\n}\n\n// Cache to avoid recomputing key-to-index mappings for the same array\nconst keyToIndexCache = new WeakMap<KeyedSanityObject[], Record<string, number>>()\n\nfunction getIndexForKey(keyedArray: KeyedSanityObject[], targetKey: string) {\n const cachedMapping = keyToIndexCache.get(keyedArray)\n if (cachedMapping) return cachedMapping[targetKey]\n\n // Build a mapping from _key to array index\n const keyToIndexMapping = keyedArray.reduce<Record<string, number>>(\n (mapping, {_key}, arrayIndex) => {\n mapping[_key] = arrayIndex\n return mapping\n },\n {},\n )\n\n keyToIndexCache.set(keyedArray, keyToIndexMapping)\n\n return keyToIndexMapping[targetKey]\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && !!value && !Array.isArray(value)\n}\n\n/**\n * Simplify returns `null` if the value given was `undefined`. This behavior\n * is the same as how `JSON.stringify` works so this is relatively expected\n * behavior.\n */\nfunction nullifyUndefined(item: unknown) {\n if (item === undefined) return null\n return item\n}\n\nfunction yes(_: unknown) {\n return true\n}\n"],"names":["stringifyPatches","makePatches"],"mappings":";;;AAAA,MAAM,iBAAiB;AA2BhB,SAAS,aAAa,MAAoB;AAC/C,SAAO,KAAK,OAAO,CAAC,QAAgB,SAAsB,MAAc;AAClE,QAAA,MAAM,QAAQ,OAAO;AACvB,aAAO,GAAG,MAAM,IAAI,QAAQ,KAAK,GAAG,CAAC;AAGvC,QAAI,cAAc,OAAO;AACvB,aAAO,GAAG,MAAM,WAAW,QAAQ,IAAI;AAGzC,QAAI,OAAO,WAAY;AACd,aAAA,GAAG,MAAM,IAAI,OAAO;AAG7B,QAAI,OAAO,WAAY,YAAY,CAAC,eAAe,KAAK,OAAO;AACtD,aAAA,GAAG,MAAM,KAAK,OAAO;AAG9B,QAAI,OAAO,WAAY;AAEd,aAAA,GAAG,MAAM,GADE,MAAM,IAAI,KAAK,GACL,GAAG,OAAO;AAGxC,UAAM,IAAI,MAAM,6BAA6B,OAAO,GAAG;AAAA,KACtD,EAAE;AACP;AAYO,SAAS,cAAc,KAAwC;AAC7D,SAAA,OAAO,OAAQ,YAAY,CAAC,CAAC,OAAO,UAAU,OAAO,OAAO,IAAI,QAAS;AAClF;ACzDO,MAAM,kBAAkB,MAAM;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EAEP,YAAY,SAAiB,MAAY,OAAiB;AAClD,UAAA,iBAAiB,aAAa,IAAI;AACxC,UAAM,GAAG,OAAO,SAAS,cAAc,IAAI,GAE3C,KAAK,OAAO,MACZ,KAAK,iBAAiB,gBACtB,KAAK,QAAQ;AAAA,EAAA;AAEjB;ACnBA,MAAM,YAAY,2BACZ,cAAc,6BACd,mBAAmB;AAyCT,SAAA,iBAAiB,UAAkB,OAAgB,MAAoB;AACjF,MAAA,CAAC,iBAAiB,KAAK,QAAQ;AACjC,UAAM,IAAI,UAAU,uCAAuC,KAAK,OAAO,QAAQ,GAAG,KAAK;AAGrF,MAAA,CAAC,YAAY,KAAK,QAAQ;AAC5B,UAAM,IAAI;AAAA,MACR;AAAA,MACA,KAAK,OAAO,QAAQ;AAAA,MACpB;AAAA,IACF;AAGF,MAAI,aAAa,UAAU,aAAa,UAAU,aAAa,SAAS;AACtE,QAAI,OAAO,SAAU;AACnB,YAAM,IAAI,UAAU,wBAAwB,KAAK,OAAO,QAAQ,GAAG,KAAK;AAGtE,QAAA,CAAC,UAAU,KAAK,KAAK;AACvB,YAAM,IAAI,UAAU,4CAA4C,KAAK,OAAO,QAAQ,GAAG,KAAK;AAAA,EAAA;AAIzF,SAAA;AACT;ACpEgB,SAAA,WAAc,QAAgB,QAAwB;AACpE,MAAI,gBAAgB,IAAI;AACf,WAAA,OAAO,WAAW,MAAM;AAG3B,QAAA,6BAAa,IAAO;AAC1B,aAAW,QAAQ;AACZ,WAAO,IAAI,IAAI,KAClB,OAAO,IAAI,IAAI;AAGZ,SAAA;AACT;AAEgB,SAAA,aAAgB,QAAgB,QAAwB;AACtE,MAAI,kBAAkB,IAAI;AACjB,WAAA,OAAO,aAAa,MAAM;AAG7B,QAAA,6BAAa,IAAO;AAC1B,aAAW,QAAQ;AACb,WAAO,IAAI,IAAI,KACjB,OAAO,IAAI,IAAI;AAGZ,SAAA;AACT;ACNA,MAAM,cAAc,CAAC,OAAO,SAAS,cAAc,cAAc,MAAM,GAMjE,sBAAsB,KAMtB,qCAAqC,KAMrC,+BAA+B;AA+D9B,SAAS,UACd,QACA,QACA,UAAwB,CAAA,GACD;AACjB,QAAA,KAAK,QAAQ,MAAO,OAAO,QAAQ,OAAO,OAAO,OAAO,KACxD,iBAAiB,QAAQ,cACzB,eAAe,OAAO,kBAAmB,YAAY,OAAO,OAAO,gBACnE,WAAW,QAAQ,YAAY,CAAC;AACtC,MAAI,CAAC;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAGE,MAAA,mBAAmB,MAAQ,CAAC;AAC9B,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAGF,MAAI,SAAS,WAAW,KAAK,OAAO,UAAU,OAAO;AAC7C,UAAA,IAAI,MAAM,6CAA6C,OAAO,KAAK,OAAO,OAAO,KAAK,GAAG;AAGjG,QAAM,aAAa,SAAS,QAAQ,QAAQ,UAAU,CAAA,CAAE;AACxD,SAAO,iBAAiB,UAAU,EAAE,IAAI,CAAC,iBAAiB,OAAO;AAAA,IAC/D,OAAO;AAAA,MACL;AAAA;AAAA,MAEA,GAAI,MAAM,KAAK,gBAAgB,EAAC,aAAY;AAAA,MAC5C,GAAG;AAAA,IAAA;AAAA,EACL,EACA;AACJ;AAYO,SAAS,UACd,QACA,QACA,WAAiB,CAAA,GACQ;AACzB,SAAO,iBAAiB,SAAS,QAAQ,QAAQ,QAAQ,CAAC;AAC5D;AAEA,SAAS,SACP,QACA,QACA,OAAa,CACb,GAAA,UAAmB,IACV;AACT,SAAI,WAAW,SACN,UAGL,OAAO,UAAW,YAAY,OAAO,UAAW,YAClD,WAAW,QAAQ,QAAQ,MAAM,OAAO,GACjC,WAGL,MAAM,QAAQ,MAAM,KAAK,MAAM,QAAQ,MAAM,KAC/C,UAAU,QAAQ,QAAQ,MAAM,OAAO,GAChC,WAGL,SAAS,MAAM,KAAK,SAAS,MAAM,KACrC,WAAW,QAAQ,QAAQ,MAAM,OAAO,GACjC,WAGL,WAAW,UACb,QAAQ,KAAK,EAAC,IAAI,SAAS,KAAI,CAAC,GACzB,YAGT,QAAQ,KAAK,EAAC,IAAI,OAAO,MAAM,OAAO,OAAO,CAAA,GACtC;AACT;AAEA,SAAS,WACP,QACA,QACA,MACA,SACA;AACM,QAAA,SAAS,KAAK,WAAW,GACzB,QAAQ,OAAO,KAAK,MAAM,EAC7B,OAAO,SAAS,kBAAkB,GAAG,EACrC,IAAI,CAAC,QAAQ,iBAAiB,KAAK,OAAO,GAAG,GAAG,IAAI,CAAC,GAElD,cAAc,MAAM,QACpB,QAAQ,OAAO,KAAK,MAAM,EAC7B,OAAO,SAAS,kBAAkB,GAAG,EACrC,IAAI,CAAC,QAAQ,iBAAiB,KAAK,OAAO,GAAG,GAAG,IAAI,CAAC,GAElD,cAAc,MAAM;AAG1B,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AAC9B,UAAA,MAAM,MAAM,CAAC;AACb,WAAO,UACX,QAAQ,KAAK,EAAC,IAAI,SAAS,MAAM,KAAK,OAAO,GAAG,EAAA,CAAE;AAAA,EAAA;AAKtD,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AAC9B,UAAA,MAAM,MAAM,CAAC;AACnB,aAAS,OAAO,GAAG,GAAG,OAAO,GAAG,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO;AAAA,EAAA;AAGzD,SAAA;AACT;AAEA,SAAS,UAAU,QAAmB,QAAmB,MAAY,SAAkB;AACrF,SAAI,gBAAgB,MAAM,KAAK,gBAAgB,MAAM,IAC5C,eAAe,QAAQ,QAAQ,MAAM,OAAO,IAG9C,iBAAiB,QAAQ,QAAQ,MAAM,OAAO;AACvD;AAEA,SAAS,iBAAiB,QAAmB,QAAmB,MAAY,SAAkB;AAY5F,MAVI,OAAO,SAAS,OAAO,UACzB,QAAQ,KAAK;AAAA,IACX,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC;AAAA,IACtB,OAAO,OAAO,MAAM,OAAO,MAAM,EAAE,IAAI,gBAAgB;AAAA,EACxD,CAAA,GAIC,OAAO,SAAS,OAAO,QAAQ;AAC3B,UAAA,WAAW,OAAO,SAAS,OAAO,WAAW,GAC7C,aAAa,OAAO,MAAM,OAAO,MAAM;AAIzC,oBAAgB,UAAU,IAC5B,QAAQ;AAAA,MACN,GAAG,WAAW;AAAA,QACZ,CAAC,UAAsB,EAAC,IAAI,SAAS,MAAM,KAAK,OAAO,EAAC,MAAM,KAAK,KAAK,CAAA,EAAC;AAAA,MAAA;AAAA,IAC3E,IAGF,QAAQ,KAAK;AAAA,MACX,IAAI;AAAA,MACJ,MAAM,KAAK,OAAO,CAAC,WAAW,OAAO,SAAS,CAAC,OAAO,QAAQ,EAAE,CAAC,CAAC;AAAA,IAAA,CACnE;AAAA,EAAA;AAKL,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ;AACjC,QAAI,MAAM,QAAQ,OAAO,CAAC,CAAC;AACnB,YAAA,IAAI,UAAU,0CAA0C,KAAK,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;AAI3F,QAAM,cAAc,KAAK,IAAI,OAAO,QAAQ,OAAO,MAAM,GACnD,WAAW,OAAO,MAAM,GAAG,WAAW,GACtC,WAAW,OAAO,MAAM,GAAG,WAAW;AAE5C,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ;AACnC,aAAS,SAAS,CAAC,GAAG,iBAAiB,SAAS,CAAC,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG,OAAO;AAGvE,SAAA;AACT;AAqBA,SAAS,eACP,QACA,QACA,MACA,SACA;AAEA,QAAM,mBAAmB,IAAI,IAAI,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,GAClE,mBAAmB,IAAI,IAAI,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC,GAGlE,aAAa,IAAI,IAAI,iBAAiB,KAAK,CAAC,GAC5C,aAAa,IAAI,IAAI,iBAAiB,KAAM,CAAA,GAC5C,wBAAwB,WAAW,YAAY,UAAU,GACzD,oBAAoB,WAAW,YAAY,UAAU,GACrD,mBAAmB,aAAa,YAAY,UAAU,GAKtD,yBAAyB,MAAM,KAAK,WAAW,YAAY,qBAAqB,CAAC,GACjF,2BAA2B,MAAM,KAAK,WAAW,YAAY,iBAAiB,CAAC,GAG/E,uBAAiE,CAAC;AAExE,WAAS,IAAI,GAAG,IAAI,iBAAiB,MAAM,KAAK;AAC9C,UAAM,wBAAwB,uBAAuB,CAAC,GAChD,wBAAwB,yBAAyB,CAAC;AAGpD,8BAA0B,yBAC5B,qBAAqB,KAAK;AAAA,MACxB,WAAW;AAAA,MACX,WAAW;AAAA,IAAA,CACZ;AAAA,EAAA;AAKD,uBAAqB,UACvB,QAAQ,KAAK;AAAA,IACX,IAAI;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,EAAA,CACX;AAIH,aAAW,OAAO;AAChB,aAAS,iBAAiB,IAAI,GAAG,GAAG,iBAAiB,IAAI,GAAG,GAAG,CAAC,GAAG,MAAM,EAAC,MAAM,IAAI,CAAA,GAAG,OAAO;AAIhG,aAAW,eAAe;AACxB,YAAQ,KAAK,EAAC,IAAI,SAAS,MAAM,CAAC,GAAG,MAAM,EAAC,MAAM,YAAW,CAAC,GAAE;AAKlE,MAAI,kBAAkB,MAAM;AACtB,QAAA,oBACA,wBAAmC,CAAC;AAExC,UAAM,yBAAyB,MAAM;AAC/B,4BAAsB,UACxB,QAAQ,KAAK;AAAA,QACX,IAAI;AAAA;AAAA,QAEJ,GAAI,qBACA,EAAC,UAAU,SAAS,MAAM,CAAC,GAAG,MAAM,EAAC,MAAM,oBAAmB,EAC9D,IAAA,EAAC,UAAU,UAAU,MAAM,CAAC,GAAG,MAAM,CAAC,EAAC;AAAA,QAC3C,OAAO;AAAA,MAAA,CACR;AAAA,IAEL;AAGA,eAAW,OAAO;AACZ,wBAAkB,IAAI,GAAG,IAE3B,sBAAsB,KAAK,iBAAiB,IAAI,GAAG,CAAE,IAC5C,iBAAiB,IAAI,GAAG,MAEjC,uBAAA,GACA,qBAAqB,KACrB,wBAAwB;AAKL,2BAAA;AAAA,EAAA;AAGlB,SAAA;AACT;AA2DgB,SAAA,wBAAwB,QAAgB,QAAyB;AAC/E,QAAM,YAAY,KAAK,IAAI,OAAO,QAAQ,OAAO,MAAM;AAGvD,SAAI,YAAY,sBACP,KAKL,YAAY,+BACP,KAUL,EALqB,KAAK,IAAI,OAAO,SAAS,OAAO,MAAM,IACxB,YAIrB;AAOpB;AAEA,SAAS,kBAAkB,QAAgB,QAAgB,MAAwC;AAC3F,QAAA,OAAO,KAAK,GAAG,EAAE;AAEnB,MAAA,EAAA,OAAO,QAAS,YAAY,KAAK,WAAW,GAAG,MAC9C,wBAAwB,QAAQ,MAAM;AAEvC,QAAA;AAcF,YAAM,WAAWA,eAAA,iBAAiBC,eAAY,YAAA,QAAQ,MAAM,CAAC;AAC7D,aAAO,EAAC,IAAI,kBAAkB,MAAM,OAAO,SAAQ;AAAA,IAAA,QACvC;AAEZ;AAAA,IAAA;AAEJ;AAEA,SAAS,WAAW,QAAgB,QAAgB,MAAY,SAAkB;AAChF,QAAM,MAAM,kBAAkB,QAAQ,QAAQ,IAAI;AAC1C,SAAA,QAAA,KAAK,OAAO,EAAC,IAAI,OAAO,MAAM,OAAO,OAAO,CAAA,GAC7C;AACT;AAEA,SAAS,gBAAgB,KAAa;AAC7B,SAAA,YAAY,QAAQ,GAAG,MAAM;AACtC;AASA,SAAS,iBAAiB,SAAkB,MAAsD;AAChG,QAAM,CAAC,OAAO,GAAG,IAAI,IAAI;AACzB,MAAI,CAAC,MAAO,QAAO,OAAO,CAAC,IAAI,IAAI,CAAC;AAEpC,UAAQ,MAAM,IAAI;AAAA,IAChB,KAAK;AAAA,IACL,KAAK,kBAAkB;AAIrB,YAAM,UAAU,EAAC,CAAC,MAAM,EAAE,GAAG,CAAA,EAAE;AAE/B,aAAK,OACC,MAAM,MAAM,QAElB,OAAO,OAAQ,KAAmB,MAAM,EAAE,GAAG,EAAC,CAAC,aAAa,MAAM,IAAI,CAAC,GAAG,MAAM,MAAA,CAAM,GAC/E,iBAAiB,MAAM,IAAI,KAHF,CAAC,MAAM,GAAG,iBAAiB,SAAS,OAAO,CAAC,IAD1D,iBAAiB,SAAS,OAAO;AAAA,IAAA;AAAA,IAMrD,KAAK,SAAS;AACZ,YAAM,UAAU,EAAC,OAAO,GAAE;AACrB,aAAA,OACC,WAAW,QAEjB,KAAK,MAAM,KAAK,aAAa,MAAM,IAAI,CAAC,GACjC,iBAAiB,MAAM,IAAI,KAHH,CAAC,MAAM,GAAG,iBAAiB,SAAS,OAAO,CAAC,IADzD,iBAAiB,SAAS,OAAO;AAAA,IAAA;AAAA,IAMrD,KAAK;AACH,aAAI,OAAa,CAAC,MAAM,GAAG,iBAAiB,OAAO,CAAC,IAE7C;AAAA,QACL;AAAA,UACE,QAAQ;AAAA,YACN,CAAC,MAAM,QAAQ,GAAG,aAAa,MAAM,IAAI;AAAA,YACzC,OAAO,MAAM;AAAA,UAAA;AAAA,QAEjB;AAAA,QACA,GAAG,iBAAiB,IAAI;AAAA,MAC1B;AAAA,IAEF,KAAK,WAAW;AACd,UAAI,KAAa,QAAA,CAAC,MAAM,GAAG,iBAAiB,OAAO,CAAC;AAapD,YAAM,oBAA2C,CAAC;AAClD,wBAAkB,MAAM,CAAC;AAEzB,iBAAW,EAAC,WAAW,UAAS,KAAK,MAAM,UAAU;AAC7C,cAAA,eAAe,kBAAkB,SAAS,MAC1C,8BACJ,MAAM,SAAS,eAAe,MAAM,UAAU,SAAS,CAAC;AAEnD,eAAA,OAAO,kBAAkB,KAAK;AAAA,UACnC,CAAC,aAAa,CAAC,GAAG,MAAM,MAAM,EAAC,MAAM,UAAU,CAAA,CAAC,CAAC,GAAG;AAAA,YAClD,GAAG;AAAA,YACH,MAAM;AAAA,UAAA;AAAA,QACR,CACD;AAAA,MAAA;AAIH,YAAM,qBAA4C,CAAC;AACnD,yBAAmB,MAAM,CAAC;AAE1B,iBAAW,EAAC,WAAW,UAAS,KAAK,MAAM,UAAU;AAC7C,cAAA,eAAe,kBAAkB,SAAS;AAEzC,eAAA,OAAO,mBAAmB,KAAK;AAAA,UACpC,CAAC,aAAa,CAAC,GAAG,MAAM,MAAM,EAAC,MAAM,aAAY,GAAG,MAAM,CAAC,CAAC,GAAG;AAAA,QAAA,CAChE;AAAA,MAAA;AAGH,aAAO,CAAC,mBAAmB,oBAAoB,GAAG,iBAAiB,IAAI,CAAC;AAAA,IAAA;AAAA,IAE1E;AACE,aAAO,CAAC;AAAA,EAAA;AAGd;AAEA,SAAS,gBAAgB,KAA4C;AAC7D,QAAA,+BAAe,IAAY;AAEjC,aAAW,QAAQ,KAAK;AAElB,QAAA,CAAC,cAAc,IAAI,KAGnB,SAAS,IAAI,KAAK,IAAI,EAAU,QAAA;AAE3B,aAAA,IAAI,KAAK,IAAI;AAAA,EAAA;AAGjB,SAAA;AACT;AAGA,MAAM,sCAAsB,QAAqD;AAEjF,SAAS,eAAe,YAAiC,WAAmB;AACpE,QAAA,gBAAgB,gBAAgB,IAAI,UAAU;AAChD,MAAA,cAAsB,QAAA,cAAc,SAAS;AAGjD,QAAM,oBAAoB,WAAW;AAAA,IACnC,CAAC,SAAS,EAAC,QAAO,gBAChB,QAAQ,IAAI,IAAI,YACT;AAAA,IAET,CAAA;AAAA,EACF;AAEA,SAAA,gBAAgB,IAAI,YAAY,iBAAiB,GAE1C,kBAAkB,SAAS;AACpC;AAEA,SAAS,SAAS,OAAkD;AAC3D,SAAA,OAAO,SAAU,YAAY,CAAC,CAAC,SAAS,CAAC,MAAM,QAAQ,KAAK;AACrE;AAOA,SAAS,iBAAiB,MAAe;AACnC,SAAA,SAAS,SAAkB,OACxB;AACT;AAEA,SAAS,IAAI,GAAY;AAChB,SAAA;AACT;;;;"}