File size: 2,313 Bytes
d9494a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import { isDefined } from '@/utils/validation';

import { fastDeepEqual } from './json/fast-deep-equal';

type Diff<T extends { id: string }> = {
  toCreate: T[];
  toUpdate: T[];
  toRestoreAndUpdate: T[];
  idsToRemove: string[];
};

const extractProperties = <T extends { id: string }>(
  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<T>) => boolean;
};

export const computeDiffBetweenObjects = <
  T extends { id: string },
  K extends { id: string },
>({
  existingObjects,
  receivedObjects,
  propertiesToCompare,
  isEntityIncluded,
}: ComputeDiffBetweenObjectsParams<T, K>): Diff<K> => {
  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,
  };
};