UNPKG

@bjoerge/mutiny

Version:

Tiny toolkit for working with Sanity mutations in JavaScript & TypeScript

214 lines (213 loc) 7.36 kB
import { isKeyedElement, isArrayElement, isPropertyElement, stringify } from "./stringify.esm.js"; import { isObject } from "./isObject.esm.js"; import { applyPatches as applyPatches$1, parsePatch } from "@sanity/diff-match-patch"; function keyOf(value) { return value !== null && typeof value == "object" && typeof value._key == "string" && value._key || null; } function findTargetIndex(array, pathSegment) { if (typeof pathSegment == "number") return normalizeIndex(array.length, pathSegment); if (isKeyedElement(pathSegment)) { const idx = array.findIndex((value) => keyOf(value) === pathSegment._key); return idx === -1 ? null : idx; } throw new Error( `Expected path segment to be addressing a single array item either by numeric index or by '_key'. Instead saw ${JSON.stringify( pathSegment )}` ); } function getTargetIdx(position, index) { return position === "before" ? index : index + 1; } function normalizeIndex(length, index) { if (length === 0 && (index === -1 || index === 0)) return 0; const normalized = index < 0 ? length + index : index; return normalized >= length || normalized < 0 ? null : normalized; } function splice(arr, start, deleteCount, items) { const copy = arr.slice(); return copy.splice(start, deleteCount, ...items || []), copy; } function insert(op, currentValue) { if (!Array.isArray(currentValue)) throw new TypeError('Cannot apply "insert()" on non-array value'); const index = findTargetIndex(currentValue, op.referenceItem); if (index === null) throw new Error(`Found no matching array element to insert ${op.position}`); return currentValue.length === 0 ? op.items : splice(currentValue, getTargetIdx(op.position, index), 0, op.items); } function upsert(op, currentValue) { if (!Array.isArray(currentValue)) throw new TypeError('Cannot apply "upsert()" on non-array value'); if (op.items.length === 0) return currentValue; const replaceItemsMap = [], insertItems = []; if (op.items.forEach((itemToBeUpserted, i) => { const existingIndex = currentValue.findIndex( (existingItem) => (existingItem == null ? void 0 : existingItem._key) === itemToBeUpserted._key ); existingIndex >= 0 ? replaceItemsMap[existingIndex] = i : insertItems.push(itemToBeUpserted); }), replaceItemsMap.length === 0 && insertItems.length == 0) return currentValue; const next = [...currentValue]; for (const i of replaceItemsMap) next[i] = op.items[replaceItemsMap[i]]; return insert( { type: "insert", items: insertItems, referenceItem: op.referenceItem, position: op.position }, next ); } function replace(op, currentValue) { if (!Array.isArray(currentValue)) throw new TypeError('Cannot apply "replace()" on non-array value'); const index = findTargetIndex(currentValue, op.referenceItem); if (index === null) throw new Error("Found no matching array element to replace"); return splice(currentValue, index, op.items.length, op.items); } function truncate(op, currentValue) { if (!Array.isArray(currentValue)) throw new TypeError('Cannot apply "truncate()" on non-array value'); return typeof op.endIndex == "number" ? currentValue.slice(0, op.startIndex).concat(currentValue.slice(op.endIndex)) : currentValue.slice(0, op.startIndex); } function set(op, currentValue) { return op.value; } function setIfMissing(op, currentValue) { return currentValue != null ? currentValue : op.value; } function unset(op) { } function inc(op, currentValue) { if (typeof currentValue != "number") throw new TypeError('Cannot apply "inc()" on non-numeric value'); return currentValue + op.amount; } function dec(op, currentValue) { if (typeof currentValue != "number") throw new TypeError('Cannot apply "dec()" on non-numeric value'); return currentValue - op.amount; } const hasOwn = Object.prototype.hasOwnProperty.call.bind( Object.prototype.hasOwnProperty ); function isEmpty(v) { for (const key in v) if (hasOwn(v, key)) return !1; return !0; } function omit(val, props) { const copy = { ...val }; for (const prop of props) delete copy[prop]; return copy; } function unassign(op, currentValue) { if (!isObject(currentValue)) throw new TypeError('Cannot apply "unassign()" on non-object value'); return op.keys.length === 0 ? currentValue : omit(currentValue, op.keys); } function assign(op, currentValue) { if (!isObject(currentValue)) throw new TypeError('Cannot apply "assign()" on non-object value'); return isEmpty(op.value) ? currentValue : { ...currentValue, ...op.value }; } function diffMatchPatch(op, currentValue) { if (typeof currentValue != "string") throw new TypeError('Cannot apply "diffMatchPatch()" on non-string value'); return applyPatches$1(parsePatch(op.value), currentValue)[0]; } var operations = /* @__PURE__ */ Object.freeze({ __proto__: null, assign, dec, diffMatchPatch, inc, insert, replace, set, setIfMissing, truncate, unassign, unset, upsert }); function applyOp(op, currentValue) { if (!(op.type in operations)) throw new Error(`Invalid operation type: "${op.type}"`); return operations[op.type](op, currentValue); } function applyPatches(patches, document) { return patches.reduce( (prev, patch) => applyNodePatch(patch, prev), document ); } function applyNodePatch(patch, document) { return applyAtPath(patch.path, patch.op, document); } function applyAtPath(path, op, value) { if (!isNonEmptyArray(path)) return applyOp(op, value); const [head, ...tail] = path; if (isArrayElement(head) && Array.isArray(value)) return applyInArray(head, tail, op, value); if (isPropertyElement(head) && isObject(value)) return applyInObject(head, tail, op, value); throw new Error( `Cannot apply operation of type "${op.type}" to path ${stringify( path )} on ${typeof value} value` ); } function applyInObject(head, tail, op, object) { const current = object[head]; if (current === void 0 && tail.length > 0) return object; const patchedValue = applyAtPath(tail, op, current); return patchedValue === current ? object : { ...object, [head]: patchedValue }; } function applyInArray(head, tail, op, value) { const index = findTargetIndex(value, head); if (index === null || index === -1) return value; const current = value[index], patchedItem = applyAtPath(tail, op, current); return patchedItem === current ? current : splice(value, index, 1, [patchedItem]); } function isNonEmptyArray(a) { return a.length > 0; } function applyPatchMutation(mutation, document) { var _a; if ((_a = mutation.options) != null && _a.ifRevision && document._rev !== mutation.options.ifRevision) throw new Error("Revision mismatch"); if (mutation.id !== document._id) throw new Error( `Document id mismatch. Refusing to apply mutation for document with id="${mutation.id}" on the given document with id="${document._id}"` ); return applyPatches(mutation.patches, document); } function hasId(doc) { return "_id" in doc; } function assignId(doc, generateId) { return hasId(doc) ? doc : { ...doc, _id: generateId() }; } export { applyNodePatch, applyOp, applyPatchMutation, applyPatches, assignId, hasId, splice }; //# sourceMappingURL=utils.esm.js.map