diff --git a/src/diffPatch.ts b/src/diffPatch.ts index 678cd87..fdd381d 100644 --- a/src/diffPatch.ts +++ b/src/diffPatch.ts @@ -4,16 +4,14 @@ import {type Path, pathToString} from './paths.js' import {validateProperty} from './validate.js' import { type Patch, - type SetPatch, type UnsetPatch, - type InsertAfterPatch, type DiffMatchPatch, - type SanityInsertPatch, - type SanityPatch, - type SanitySetPatch, - type SanityUnsetPatch, - type SanityDiffMatchPatch, type SanityPatchMutation, + type SanityPatchOperations, + type SanitySetPatchOperation, + type SanityUnsetPatchOperation, + type SanityInsertPatchOperation, + type SanityDiffMatchPatchOperation, } from './patches.js' /** @@ -140,27 +138,35 @@ export function diffPatch( } const operations = diffItem(itemA, itemB, basePath, []) - return serializePatches(operations, {id, ifRevisionID: revisionLocked ? ifRevisionID : undefined}) + return serializePatches(operations).map((patchOperations, i) => ({ + patch: { + id, + // only add `ifRevisionID` to the first patch + ...(i === 0 && ifRevisionID && {ifRevisionID}), + ...patchOperations, + }, + })) } /** - * Diffs two items and returns an array of patches. - * Note that this is different from `diffPatch`, which generates _mutations_. + * Generates an array of patch operation objects for Sanity, based on the + * differences between the two passed values * - * @param itemA - The first item to compare - * @param itemB - The second item to compare - * @param opts - Options for the diff generation - * @param path - Path to the current item - * @param patches - Array of patches to append the results to. Note that this is MUTATED. - * @returns Array of patches + * @param source - The source value to start off with + * @param target - The target value that the patch operations will aim to create + * @param basePath - An optional path that will be prefixed to all subsequent patch operations + * @returns Array of mutations * @public */ -export function diffItem( - itemA: unknown, - itemB: unknown, - path: Path = [], - patches: Patch[] = [], -): Patch[] { +export function diffValue( + source: unknown, + target: unknown, + basePath: Path = [], +): SanityPatchOperations[] { + return serializePatches(diffItem(source, target, basePath)) +} + +function diffItem(itemA: unknown, itemB: unknown, path: Path = [], patches: Patch[] = []): Patch[] { if (itemA === itemB) { return patches } @@ -451,65 +457,62 @@ function isNotIgnoredKey(key: string) { return SYSTEM_KEYS.indexOf(key) === -1 } -function serializePatches( - patches: Patch[], - options: {id: string; ifRevisionID?: string}, -): SanityPatchMutation[] { +// mutually exclusive operations +type SanityPatchOperation = + | SanitySetPatchOperation + | SanityUnsetPatchOperation + | SanityInsertPatchOperation + | SanityDiffMatchPatchOperation + +function serializePatches(patches: Patch[]): SanityPatchOperation[] { if (patches.length === 0) { return [] } - const {id, ifRevisionID} = options - const set = patches.filter((patch): patch is SetPatch => patch.op === 'set') - const unset = patches.filter((patch): patch is UnsetPatch => patch.op === 'unset') - const insert = patches.filter((patch): patch is InsertAfterPatch => patch.op === 'insert') - const dmp = patches.filter((patch): patch is DiffMatchPatch => patch.op === 'diffMatchPatch') + const set = patches.filter((patch) => patch.op === 'set') + const unset = patches.filter((patch) => patch.op === 'unset') + const insert = patches.filter((patch) => patch.op === 'insert') + const dmp = patches.filter((patch) => patch.op === 'diffMatchPatch') const withSet = set.length > 0 && - set.reduce( - (patch: SanitySetPatch, item: SetPatch) => { + set.reduce( + (patch, item) => { const path = pathToString(item.path) patch.set[path] = item.value return patch }, - {id, set: {}}, + {set: {}}, ) const withUnset = unset.length > 0 && - unset.reduce( - (patch: SanityUnsetPatch, item: UnsetPatch) => { + unset.reduce( + (patch, item) => { const path = pathToString(item.path) patch.unset.push(path) return patch }, - {id, unset: []}, + {unset: []}, ) - const withInsert = insert.reduce((acc: SanityInsertPatch[], item: InsertAfterPatch) => { + const withInsert = insert.reduce((acc, item) => { const after = pathToString(item.after) - return acc.concat({id, insert: {after, items: item.items}}) + return acc.concat({insert: {after, items: item.items}}) }, []) const withDmp = dmp.length > 0 && - dmp.reduce( - (patch: SanityDiffMatchPatch, item: DiffMatchPatch) => { + dmp.reduce( + (patch, item) => { const path = pathToString(item.path) patch.diffMatchPatch[path] = item.value return patch }, - {id, diffMatchPatch: {}}, + {diffMatchPatch: {}}, ) - const patchSet: SanityPatch[] = [withUnset, withSet, withDmp, ...withInsert].filter( - (item): item is SanityPatch => item !== false, - ) - - return patchSet.map((patch, i) => ({ - patch: ifRevisionID && i === 0 ? {...patch, ifRevisionID} : patch, - })) + return [withUnset, withSet, withDmp, ...withInsert].filter((i) => i !== false) } function isUniquelyKeyed(arr: unknown[]): arr is KeyedSanityObject[] { diff --git a/src/index.ts b/src/index.ts index 2b61b0d..5c60307 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,7 @@ -export {diffPatch, diffItem} from './diffPatch.js' +export {diffPatch, diffValue} from './diffPatch.js' export {DiffError} from './diffError.js' -export type { - DiffMatchPatch, - InsertAfterPatch, - Patch, - SanityDiffMatchPatch, - SanityInsertPatch, - SanityPatch, - SanityPatchMutation, - SanitySetPatch, - SanityUnsetPatch, - SetPatch, - UnsetPatch, -} from './patches.js' +export type {SanityPatch, SanityPatchMutation, SanityPatchOperations} from './patches.js' export type {DocumentStub, PatchOptions} from './diffPatch.js' diff --git a/src/patches.ts b/src/patches.ts index 21fb5aa..ccd0ddd 100644 --- a/src/patches.ts +++ b/src/patches.ts @@ -3,9 +3,8 @@ import type {Path} from './paths.js' /** * A `set` operation * Replaces the current path, does not merge - * Note: NOT a serializable mutation, see {@link SanitySetPatch} for that * - * @public + * @internal */ export interface SetPatch { op: 'set' @@ -16,9 +15,8 @@ export interface SetPatch { /** * A `unset` operation * Unsets the entire value of the given path - * Note: NOT a serializable mutation, see {@link SanityUnsetPatch} for that * - * @public + * @internal */ export interface UnsetPatch { op: 'unset' @@ -28,9 +26,8 @@ export interface UnsetPatch { /** * A `insert` operation * Inserts the given items _after_ the given path - * Note: NOT a serializable mutation, see {@link SanityInsertPatch} for that * - * @public + * @internal */ export interface InsertAfterPatch { op: 'insert' @@ -41,9 +38,8 @@ export interface InsertAfterPatch { /** * A `diffMatchPatch` operation * Applies the given `value` (unidiff format) to the given path. Must be a string. - * Note: NOT a serializable mutation, see {@link SanityDiffMatchPatch} for that * - * @public + * @internal */ export interface DiffMatchPatch { op: 'diffMatchPatch' @@ -52,9 +48,9 @@ export interface DiffMatchPatch { } /** - * A patch containing either a Sanity set, unset, insert or diffMatchPatch operation + * Internal patch representation used during diff generation * - * @public + * @internal */ export type Patch = SetPatch | UnsetPatch | InsertAfterPatch | DiffMatchPatch @@ -64,9 +60,8 @@ export type Patch = SetPatch | UnsetPatch | InsertAfterPatch | DiffMatchPatch * * @public */ -export interface SanitySetPatch { - id: string - set: {[key: string]: any} +export interface SanitySetPatchOperation { + set: Record } /** @@ -75,8 +70,7 @@ export interface SanitySetPatch { * * @public */ -export interface SanityUnsetPatch { - id: string +export interface SanityUnsetPatchOperation { unset: string[] } @@ -86,12 +80,11 @@ export interface SanityUnsetPatch { * * @public */ -export interface SanityInsertPatch { - id: string +export interface SanityInsertPatchOperation { insert: - | {before: string; items: any[]} - | {after: string; items: any[]} - | {replace: string; items: any[]} + | {before: string; items: unknown[]} + | {after: string; items: unknown[]} + | {replace: string; items: unknown[]} } /** @@ -100,21 +93,34 @@ export interface SanityInsertPatch { * * @public */ -export interface SanityDiffMatchPatch { - id: string - diffMatchPatch: {[key: string]: string} +export interface SanityDiffMatchPatchOperation { + diffMatchPatch: Record } /** - * A patch containing either a set, unset, insert or diffMatchPatch operation + * Serializable patch operations that can be applied to a Sanity document. * * @public */ -export type SanityPatch = - | SanitySetPatch - | SanityUnsetPatch - | SanityInsertPatch - | SanityDiffMatchPatch +export type SanityPatchOperations = Partial< + SanitySetPatchOperation & + SanityUnsetPatchOperation & + SanityInsertPatchOperation & + SanityDiffMatchPatchOperation +> + +/** + * Meant to be used as the body of a {@link SanityPatchMutation}'s `patch` key. + * + * Contains additional properties to target a particular ID and optionally add + * an optimistic lock via [`ifRevisionID`](https://www.sanity.io/docs/content-lake/transactions#k29b2c75639d5). + * + * @public + */ +export interface SanityPatch extends SanityPatchOperations { + id: string + ifRevisionID?: string +} /** * A mutation containing a single patch