import { isDefined } from '@/utils/validation'; import { fastDeepEqual } from './json/fast-deep-equal'; type Diff = { toCreate: T[]; toUpdate: T[]; toRestoreAndUpdate: T[]; idsToRemove: string[]; }; const extractProperties = ( object: T, properties: (keyof T)[], ) => { return properties.reduce((acc, property) => { return { ...acc, [property]: object[property], }; }, {}); }; type ComputeDiffBetweenObjectsParams< T extends { id: string }, K extends { id: string }, > = { existingObjects: T[]; receivedObjects: K[]; propertiesToCompare: (keyof K & keyof T)[]; isEntityIncluded: (entity: NoInfer) => boolean; }; export const computeDiffBetweenObjects = < T extends { id: string }, K extends { id: string }, >({ existingObjects, receivedObjects, propertiesToCompare, isEntityIncluded, }: ComputeDiffBetweenObjectsParams): Diff => { const toCreate: K[] = []; const toUpdate: K[] = []; const toRestoreAndUpdate: K[] = []; const existingEntitiesMap = new Map( existingObjects.map((entity) => [entity.id, entity]), ); const receivedEntitiesMap = new Map( receivedObjects.map((entity) => [entity.id, entity]), ); for (const receivedObject of receivedObjects) { const existingEntity = existingEntitiesMap.get(receivedObject.id); if (isDefined(existingEntity)) { if (!isEntityIncluded(existingEntity)) { toRestoreAndUpdate.push(receivedObject); } else { const comparableExistingEntity = extractProperties( existingEntity, propertiesToCompare, ); const comparableReceivedEntity = extractProperties( receivedObject, propertiesToCompare, ); if ( !fastDeepEqual(comparableExistingEntity, comparableReceivedEntity) ) { toUpdate.push(receivedObject); } } } else { toCreate.push(receivedObject); } } const idsToRemove = existingObjects .filter((existingEntity) => isEntityIncluded(existingEntity)) .filter((existingEntity) => !receivedEntitiesMap.has(existingEntity.id)) .map((entity) => entity.id); return { toCreate, toUpdate, toRestoreAndUpdate, idsToRemove, }; };