Use same lock values as mobile clients
[ProtonMail-WebClient.git] / packages / shared / lib / helpers / updateCollection.ts
blobf6ee4a6af58f229ef2cca62113648c393340b04e
1 import { EVENT_ACTIONS } from '../constants';
3 const { DELETE, CREATE, UPDATE } = EVENT_ACTIONS;
5 const defaultMerge = <T, Y>(oldModel: T, newModel: Partial<Y>): T => {
6     return {
7         ...oldModel,
8         ...newModel,
9     };
12 const defaultCreate = <T, Y>(newModel: T): Y => {
13     return newModel as unknown as Y;
16 export type EventItemModelPartial<EventItemModel> = Partial<EventItemModel>;
18 export type CreateEventItemUpdate<EventItemModel, EventItemKey extends string> = {
19     ID: string;
20     Action: EVENT_ACTIONS.CREATE;
21 } & { [key in EventItemKey]: EventItemModel };
23 export type UpdateEventItemUpdate<EventItemModel, EventItemKey extends string> = {
24     ID: string;
25     Action: EVENT_ACTIONS.UPDATE;
26 } & { [key in EventItemKey]: EventItemModelPartial<EventItemModel> };
28 export type DeleteEventItemUpdate = {
29     ID: string;
30     Action: EVENT_ACTIONS.DELETE;
33 export type EventItemUpdate<EventItemModel, EventItemKey extends string> =
34     | CreateEventItemUpdate<EventItemModel, EventItemKey>
35     | UpdateEventItemUpdate<EventItemModel, EventItemKey>
36     | DeleteEventItemUpdate;
38 interface Model {
39     ID: string;
42 export const sortCollection = <T>(sortByKey: keyof T | undefined, array: T[]) => {
43     if (!sortByKey) {
44         return array;
45     }
46     return array.sort((a, b) => Number(a[sortByKey]) - Number(b[sortByKey]));
49 /**
50  * Update a model collection with incoming events.
51  */
52 const updateCollection = <
53     EventItemUpdateModel extends Model,
54     EventItemModel extends Model,
55     EventItemKey extends string,
56 >({
57     model = [],
58     events = [],
59     item,
60     itemKey,
61     merge = defaultMerge,
62     create = defaultCreate,
63 }: {
64     model: readonly EventItemModel[] | undefined;
65     events: readonly EventItemUpdate<EventItemUpdateModel, EventItemKey>[];
66     item?: (
67         event:
68             | CreateEventItemUpdate<EventItemUpdateModel, EventItemKey>
69             | UpdateEventItemUpdate<EventItemUpdateModel, EventItemKey>
70     ) => EventItemModelPartial<EventItemUpdateModel> | undefined;
71     create?: (a: EventItemUpdateModel) => EventItemModel;
72     merge?: (a: EventItemModel, B: EventItemModelPartial<EventItemUpdateModel>) => EventItemModel;
73     itemKey: EventItemKey;
74 }): EventItemModel[] => {
75     const copy = [...model];
77     const todo = events.reduce<{
78         [UPDATE]: EventItemModelPartial<EventItemUpdateModel>[];
79         [CREATE]: EventItemModelPartial<EventItemUpdateModel>[];
80         [DELETE]: { [key: string]: boolean };
81     }>(
82         (acc, task) => {
83             const { Action, ID } = task;
85             if (Action === DELETE) {
86                 acc[DELETE][ID] = true;
87                 return acc;
88             }
90             if (Action === UPDATE || Action === CREATE) {
91                 const value = item?.(task) ?? task[itemKey];
92                 if (value) {
93                     acc[Action].push(value);
94                 }
95             }
97             return acc;
98         },
99         { [UPDATE]: [], [CREATE]: [], [DELETE]: {} }
100     );
102     const todos = [...todo[CREATE], ...todo[UPDATE]];
104     const copiedMap = copy.reduce<{ [key: string]: number }>((acc, element, index) => {
105         acc[element.ID] = index;
106         return acc;
107     }, Object.create(null));
109     // NOTE: We cannot trust Action so "create" and "update" events need to be handled in this way by going through the original model.
110     const { collection } = todos.reduce<{ collection: EventItemModel[]; map: { [key: string]: number } }>(
111         (acc, element) => {
112             const id = element.ID;
113             if (id === undefined) {
114                 return acc;
115             }
117             // Update.
118             const index = acc.map[id];
119             if (index !== undefined) {
120                 acc.collection[index] = merge(acc.collection[index], element);
121                 return acc;
122             }
124             // Create. Assume it is never partial.
125             const length = acc.collection.push(create(element as EventItemUpdateModel));
126             // Set index in case there is an UPDATE on this CREATEd item afterwards.
127             acc.map[id] = length - 1; // index
129             return acc;
130         },
131         {
132             collection: copy,
133             map: copiedMap,
134         }
135     );
137     return collection.filter(({ ID }) => !todo[DELETE][ID]);
140 export default updateCollection;