1 import { EVENT_ACTIONS } from '../constants';
3 const { DELETE, CREATE, UPDATE } = EVENT_ACTIONS;
5 const defaultMerge = <T, Y>(oldModel: T, newModel: Partial<Y>): T => {
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> = {
20 Action: EVENT_ACTIONS.CREATE;
21 } & { [key in EventItemKey]: EventItemModel };
23 export type UpdateEventItemUpdate<EventItemModel, EventItemKey extends string> = {
25 Action: EVENT_ACTIONS.UPDATE;
26 } & { [key in EventItemKey]: EventItemModelPartial<EventItemModel> };
28 export type DeleteEventItemUpdate = {
30 Action: EVENT_ACTIONS.DELETE;
33 export type EventItemUpdate<EventItemModel, EventItemKey extends string> =
34 | CreateEventItemUpdate<EventItemModel, EventItemKey>
35 | UpdateEventItemUpdate<EventItemModel, EventItemKey>
36 | DeleteEventItemUpdate;
42 export const sortCollection = <T>(sortByKey: keyof T | undefined, array: T[]) => {
46 return array.sort((a, b) => Number(a[sortByKey]) - Number(b[sortByKey]));
50 * Update a model collection with incoming events.
52 const updateCollection = <
53 EventItemUpdateModel extends Model,
54 EventItemModel extends Model,
55 EventItemKey extends string,
62 create = defaultCreate,
64 model: readonly EventItemModel[] | undefined;
65 events: readonly EventItemUpdate<EventItemUpdateModel, EventItemKey>[];
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 };
83 const { Action, ID } = task;
85 if (Action === DELETE) {
86 acc[DELETE][ID] = true;
90 if (Action === UPDATE || Action === CREATE) {
91 const value = item?.(task) ?? task[itemKey];
93 acc[Action].push(value);
99 { [UPDATE]: [], [CREATE]: [], [DELETE]: {} }
102 const todos = [...todo[CREATE], ...todo[UPDATE]];
104 const copiedMap = copy.reduce<{ [key: string]: number }>((acc, element, index) => {
105 acc[element.ID] = index;
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 } }>(
112 const id = element.ID;
113 if (id === undefined) {
118 const index = acc.map[id];
119 if (index !== undefined) {
120 acc.collection[index] = merge(acc.collection[index], element);
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
137 return collection.filter(({ ID }) => !todo[DELETE][ID]);
140 export default updateCollection;