UNPKG

@dabbott/mutative

Version:

A JavaScript library for efficient immutable updates

213 lines (203 loc) 5.79 kB
import { CreateArrayPatches, CreateArrayPatchesParameters, DraftType, Operation, Patches, ProxyDraft, } from './interface'; import { cloneIfNeeded, escapePath, get, has, isEqual } from './utils'; function generateArrayPatches( proxyState: ProxyDraft<Array<any>>, basePath: any[], patches: Patches, inversePatches: Patches, pathAsArray: boolean, createArrayPatches?: CreateArrayPatches ) { let { original, assignedMap, options } = proxyState; let copy = proxyState.copy!; const params: CreateArrayPatchesParameters = { isAssigned: (index: number) => { return !!assignedMap!.get(index.toString()); }, cloneIfNeeded, concatPath: (segment: number | string) => { const _path = basePath.concat([segment]); const path = escapePath(_path, pathAsArray); return path; }, basePath, original, copy, }; if (createArrayPatches) { const arrayPatches = createArrayPatches(params); if (arrayPatches) { patches.push(...arrayPatches.patches); inversePatches.push(...arrayPatches.inversePatches); return; } } if (copy.length < original.length) { [original, copy] = [copy, original]; [patches, inversePatches] = [inversePatches, patches]; } for (let index = 0; index < original.length; index += 1) { if (assignedMap!.get(index.toString()) && copy[index] !== original[index]) { const path = params.concatPath(index); patches.push({ op: Operation.Replace, path, // If it is a draft, it needs to be deep cloned, and it may also be non-draft. value: params.cloneIfNeeded(copy[index]), }); inversePatches.push({ op: Operation.Replace, path, // If it is a draft, it needs to be deep cloned, and it may also be non-draft. value: params.cloneIfNeeded(original[index]), }); } } for (let index = original.length; index < copy.length; index += 1) { const path = params.concatPath(index); patches.push({ op: Operation.Add, path, // If it is a draft, it needs to be deep cloned, and it may also be non-draft. value: params.cloneIfNeeded(copy[index]), }); } if (original.length < copy.length) { // https://www.rfc-editor.org/rfc/rfc6902#appendix-A.4 // For performance, here we only generate an operation that replaces the length of the array, // which is inconsistent with JSON Patch specification const { arrayLengthAssignment = true } = options.enablePatches; if (arrayLengthAssignment) { const path = params.concatPath('length'); inversePatches.push({ op: Operation.Replace, path, value: original.length, }); } else { for (let index = copy.length; original.length < index; index -= 1) { const path = params.concatPath(index - 1); inversePatches.push({ op: Operation.Remove, path, }); } } } } function generatePatchesFromAssigned( { original, copy, assignedMap }: ProxyDraft<Record<string, any>>, basePath: any[], patches: Patches, inversePatches: Patches, pathAsArray: boolean ) { assignedMap!.forEach((assignedValue, key) => { const originalValue = get(original, key); const value = cloneIfNeeded(get(copy, key)); const op = !assignedValue ? Operation.Remove : has(original, key) ? Operation.Replace : Operation.Add; if (isEqual(originalValue, value) && op === Operation.Replace) return; const _path = basePath.concat(key); const path = escapePath(_path, pathAsArray); patches.push(op === Operation.Remove ? { op, path } : { op, path, value }); inversePatches.push( op === Operation.Add ? { op: Operation.Remove, path } : op === Operation.Remove ? { op: Operation.Add, path, value: originalValue } : { op: Operation.Replace, path, value: originalValue } ); }); } function generateSetPatches( { original, copy }: ProxyDraft<Set<any>>, basePath: any[], patches: Patches, inversePatches: Patches, pathAsArray: boolean ) { let index = 0; original.forEach((value: any) => { if (!copy!.has(value)) { const _path = basePath.concat([index]); const path = escapePath(_path, pathAsArray); patches.push({ op: Operation.Remove, path, value, }); inversePatches.unshift({ op: Operation.Add, path, value, }); } index += 1; }); index = 0; copy!.forEach((value: any) => { if (!original.has(value)) { const _path = basePath.concat([index]); const path = escapePath(_path, pathAsArray); patches.push({ op: Operation.Add, path, value, }); inversePatches.unshift({ op: Operation.Remove, path, value, }); } index += 1; }); } export function generatePatches( proxyState: ProxyDraft, basePath: any[], patches: Patches, inversePatches: Patches ) { const { pathAsArray = true } = proxyState.options.enablePatches; const createArrayPatches = proxyState.options.createArrayPatches; switch (proxyState.type) { case DraftType.Object: case DraftType.Map: return generatePatchesFromAssigned( proxyState, basePath, patches, inversePatches, pathAsArray ); case DraftType.Array: return generateArrayPatches( proxyState, basePath, patches, inversePatches, pathAsArray, createArrayPatches ); case DraftType.Set: return generateSetPatches( proxyState, basePath, patches, inversePatches, pathAsArray ); } }