Skip to content

Commit f871540

Browse files
sandangelMikeRyanDev
authored andcommitted
feat(Entity): Add upsertOne and upsertMany functions to entity adapters (#780)
Closes #421
1 parent c11504f commit f871540

File tree

5 files changed

+218
-1
lines changed

5 files changed

+218
-1
lines changed

modules/entity/spec/sorted_state_adapter.spec.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EntityStateAdapter, EntityState } from '../src/models';
1+
import { EntityStateAdapter, EntityState, Update } from '../src/models';
22
import { createEntityAdapter } from '../src/create_adapter';
33
import {
44
BookModel,
@@ -277,4 +277,72 @@ describe('Sorted State Adapter', () => {
277277
},
278278
});
279279
});
280+
281+
it('should let you add one entity to the state with upsert()', () => {
282+
const withOneEntity = adapter.upsertOne(
283+
{
284+
id: TheGreatGatsby.id,
285+
changes: TheGreatGatsby,
286+
},
287+
state
288+
);
289+
290+
expect(withOneEntity).toEqual({
291+
ids: [TheGreatGatsby.id],
292+
entities: {
293+
[TheGreatGatsby.id]: TheGreatGatsby,
294+
},
295+
});
296+
});
297+
298+
it('should let you update an entity in the state with upsert()', () => {
299+
const withOne = adapter.addOne(TheGreatGatsby, state);
300+
const changes = { title: 'A New Hope' };
301+
302+
const withUpdates = adapter.upsertOne(
303+
{
304+
id: TheGreatGatsby.id,
305+
changes,
306+
},
307+
withOne
308+
);
309+
310+
expect(withUpdates).toEqual({
311+
ids: [TheGreatGatsby.id],
312+
entities: {
313+
[TheGreatGatsby.id]: {
314+
...TheGreatGatsby,
315+
...changes,
316+
},
317+
},
318+
});
319+
});
320+
321+
it('should let you upsert many entities in the state', () => {
322+
const firstChange = { title: 'Zack' };
323+
const secondChange = { title: 'Aaron' };
324+
const withMany = adapter.addAll([TheGreatGatsby], state);
325+
326+
const withUpserts = adapter.upsertMany(
327+
[
328+
{ id: TheGreatGatsby.id, changes: firstChange },
329+
{ id: AClockworkOrange.id, changes: secondChange },
330+
],
331+
withMany
332+
);
333+
334+
expect(withUpserts).toEqual({
335+
ids: [AClockworkOrange.id, TheGreatGatsby.id],
336+
entities: {
337+
[TheGreatGatsby.id]: {
338+
...TheGreatGatsby,
339+
...firstChange,
340+
},
341+
[AClockworkOrange.id]: {
342+
...AClockworkOrange,
343+
...secondChange,
344+
},
345+
},
346+
});
347+
});
280348
});

modules/entity/spec/unsorted_state_adapter.spec.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,72 @@ describe('Unsorted State Adapter', () => {
217217
},
218218
});
219219
});
220+
221+
it('should let you add one entity to the state with upsert()', () => {
222+
const withOneEntity = adapter.upsertOne(
223+
{
224+
id: TheGreatGatsby.id,
225+
changes: TheGreatGatsby,
226+
},
227+
state
228+
);
229+
230+
expect(withOneEntity).toEqual({
231+
ids: [TheGreatGatsby.id],
232+
entities: {
233+
[TheGreatGatsby.id]: TheGreatGatsby,
234+
},
235+
});
236+
});
237+
238+
it('should let you update an entity in the state with upsert()', () => {
239+
const withOne = adapter.addOne(TheGreatGatsby, state);
240+
const changes = { title: 'A New Hope' };
241+
242+
const withUpdates = adapter.upsertOne(
243+
{
244+
id: TheGreatGatsby.id,
245+
changes,
246+
},
247+
withOne
248+
);
249+
250+
expect(withUpdates).toEqual({
251+
ids: [TheGreatGatsby.id],
252+
entities: {
253+
[TheGreatGatsby.id]: {
254+
...TheGreatGatsby,
255+
...changes,
256+
},
257+
},
258+
});
259+
});
260+
261+
it('should let you upsert many entities in the state', () => {
262+
const firstChange = { title: 'First Change' };
263+
const secondChange = { title: 'Second Change' };
264+
const withMany = adapter.addAll([TheGreatGatsby], state);
265+
266+
const withUpserts = adapter.upsertMany(
267+
[
268+
{ id: TheGreatGatsby.id, changes: firstChange },
269+
{ id: AClockworkOrange.id, changes: secondChange },
270+
],
271+
withMany
272+
);
273+
274+
expect(withUpserts).toEqual({
275+
ids: [TheGreatGatsby.id, AClockworkOrange.id],
276+
entities: {
277+
[TheGreatGatsby.id]: {
278+
...TheGreatGatsby,
279+
...firstChange,
280+
},
281+
[AClockworkOrange.id]: {
282+
...AClockworkOrange,
283+
...secondChange,
284+
},
285+
},
286+
});
287+
});
220288
});

modules/entity/src/models.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ export interface EntityStateAdapter<T> {
6363

6464
updateOne<S extends EntityState<T>>(update: Update<T>, state: S): S;
6565
updateMany<S extends EntityState<T>>(updates: Update<T>[], state: S): S;
66+
67+
upsertOne<S extends EntityState<T>>(update: Update<T>, state: S): S;
68+
upsertMany<S extends EntityState<T>>(updates: Update<T>[], state: S): S;
6669
}
6770

6871
export type EntitySelectors<T, V> = {

modules/entity/src/sorted_state_adapter.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,43 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
106106
}
107107
}
108108

109+
function upsertOneMutably(update: Update<T>, state: R): DidMutate;
110+
function upsertOneMutably(update: any, state: any): DidMutate {
111+
return upsertManyMutably([update], state);
112+
}
113+
114+
function upsertManyMutably(updates: Update<T>[], state: R): DidMutate;
115+
function upsertManyMutably(updates: any[], state: any): DidMutate {
116+
const added: T[] = [];
117+
const updated: Update<T>[] = [];
118+
119+
for (let index in updates) {
120+
const update = updates[index];
121+
if (update.id in state.entities) {
122+
updated.push(update);
123+
} else {
124+
added.push({
125+
...update.changes,
126+
id: update.id,
127+
});
128+
}
129+
}
130+
131+
const didMutateByUpdated = updateManyMutably(updated, state);
132+
const didMutateByAdded = addManyMutably(added, state);
133+
134+
switch (true) {
135+
case didMutateByAdded === DidMutate.None &&
136+
didMutateByUpdated === DidMutate.None:
137+
return DidMutate.None;
138+
case didMutateByAdded === DidMutate.Both ||
139+
didMutateByUpdated === DidMutate.Both:
140+
return DidMutate.Both;
141+
default:
142+
return DidMutate.EntitiesOnly;
143+
}
144+
}
145+
109146
function merge(models: T[], state: R): void;
110147
function merge(models: any[], state: any): void {
111148
models.sort(sort);
@@ -147,8 +184,10 @@ export function createSortedStateAdapter<T>(selectId: any, sort: any): any {
147184
removeAll,
148185
addOne: createStateOperator(addOneMutably),
149186
updateOne: createStateOperator(updateOneMutably),
187+
upsertOne: createStateOperator(upsertOneMutably),
150188
addAll: createStateOperator(addAllMutably),
151189
addMany: createStateOperator(addManyMutably),
152190
updateMany: createStateOperator(updateManyMutably),
191+
upsertMany: createStateOperator(upsertManyMutably),
153192
};
154193
}

modules/entity/src/unsorted_state_adapter.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,13 +123,52 @@ export function createUnsortedStateAdapter<T>(selectId: IdSelector<T>): any {
123123
return DidMutate.None;
124124
}
125125

126+
function upsertOneMutably(update: Update<T>, state: R): DidMutate;
127+
function upsertOneMutably(update: any, state: any): DidMutate {
128+
return upsertManyMutably([update], state);
129+
}
130+
131+
function upsertManyMutably(updates: Update<T>[], state: R): DidMutate;
132+
function upsertManyMutably(updates: any[], state: any): DidMutate {
133+
const added: T[] = [];
134+
const updated: any[] = [];
135+
136+
for (let index in updates) {
137+
const update = updates[index];
138+
if (update.id in state.entities) {
139+
updated.push(update);
140+
} else {
141+
added.push({
142+
...update.changes,
143+
id: update.id,
144+
});
145+
}
146+
}
147+
148+
const didMutateByUpdated = updateManyMutably(updated, state);
149+
const didMutateByAdded = addManyMutably(added, state);
150+
151+
switch (true) {
152+
case didMutateByAdded === DidMutate.None &&
153+
didMutateByUpdated === DidMutate.None:
154+
return DidMutate.None;
155+
case didMutateByAdded === DidMutate.Both ||
156+
didMutateByUpdated === DidMutate.Both:
157+
return DidMutate.Both;
158+
default:
159+
return DidMutate.EntitiesOnly;
160+
}
161+
}
162+
126163
return {
127164
removeAll,
128165
addOne: createStateOperator(addOneMutably),
129166
addMany: createStateOperator(addManyMutably),
130167
addAll: createStateOperator(addAllMutably),
131168
updateOne: createStateOperator(updateOneMutably),
132169
updateMany: createStateOperator(updateManyMutably),
170+
upsertOne: createStateOperator(upsertOneMutably),
171+
upsertMany: createStateOperator(upsertManyMutably),
133172
removeOne: createStateOperator(removeOneMutably),
134173
removeMany: createStateOperator(removeManyMutably),
135174
};

0 commit comments

Comments
 (0)