diff --git a/modules/entity/spec/sorted_state_adapter.spec.ts b/modules/entity/spec/sorted_state_adapter.spec.ts index ca4fe2ef48..21f61acd5a 100644 --- a/modules/entity/spec/sorted_state_adapter.spec.ts +++ b/modules/entity/spec/sorted_state_adapter.spec.ts @@ -231,4 +231,72 @@ describe('Sorted State Adapter', () => { }, }); }); + + it('should let you add one entity to the state with upsert()', () => { + const withOneEntity = adapter.upsertOne( + { + id: TheGreatGatsby.id, + changes: TheGreatGatsby, + }, + state + ); + + expect(withOneEntity).toEqual({ + ids: [TheGreatGatsby.id], + entities: { + [TheGreatGatsby.id]: TheGreatGatsby, + }, + }); + }); + + it('should let you update an entity in the state with upsert()', () => { + const withOne = adapter.addOne(TheGreatGatsby, state); + const changes = { title: 'A New Hope' }; + + const withUpdates = adapter.upsertOne( + { + id: TheGreatGatsby.id, + changes, + }, + withOne + ); + + expect(withUpdates).toEqual({ + ids: [TheGreatGatsby.id], + entities: { + [TheGreatGatsby.id]: { + ...TheGreatGatsby, + ...changes, + }, + }, + }); + }); + + it('should let you upsert many entities in the state', () => { + const firstChange = { title: 'Zack' }; + const secondChange = { title: 'Aaron' }; + const withMany = adapter.addAll([TheGreatGatsby], state); + + const withUpserts = adapter.upsertMany( + [ + { id: TheGreatGatsby.id, changes: firstChange }, + { id: AClockworkOrange.id, changes: secondChange }, + ], + withMany + ); + + expect(withUpserts).toEqual({ + ids: [AClockworkOrange.id, TheGreatGatsby.id], + entities: { + [TheGreatGatsby.id]: { + ...TheGreatGatsby, + ...firstChange, + }, + [AClockworkOrange.id]: { + ...AClockworkOrange, + ...secondChange, + }, + }, + }); + }); }); diff --git a/modules/entity/spec/unsorted_state_adapter.spec.ts b/modules/entity/spec/unsorted_state_adapter.spec.ts index 941ab87088..e61a7cb281 100644 --- a/modules/entity/spec/unsorted_state_adapter.spec.ts +++ b/modules/entity/spec/unsorted_state_adapter.spec.ts @@ -202,4 +202,72 @@ describe('Unsorted State Adapter', () => { }, }); }); + + it('should let you add one entity to the state with upsert()', () => { + const withOneEntity = adapter.upsertOne( + { + id: TheGreatGatsby.id, + changes: TheGreatGatsby, + }, + state + ); + + expect(withOneEntity).toEqual({ + ids: [TheGreatGatsby.id], + entities: { + [TheGreatGatsby.id]: TheGreatGatsby, + }, + }); + }); + + it('should let you update an entity in the state with upsert()', () => { + const withOne = adapter.addOne(TheGreatGatsby, state); + const changes = { title: 'A New Hope' }; + + const withUpdates = adapter.upsertOne( + { + id: TheGreatGatsby.id, + changes, + }, + withOne + ); + + expect(withUpdates).toEqual({ + ids: [TheGreatGatsby.id], + entities: { + [TheGreatGatsby.id]: { + ...TheGreatGatsby, + ...changes, + }, + }, + }); + }); + + it('should let you upsert many entities in the state', () => { + const firstChange = { title: 'First Change' }; + const secondChange = { title: 'Second Change' }; + const withMany = adapter.addAll([TheGreatGatsby], state); + + const withUpserts = adapter.upsertMany( + [ + { id: TheGreatGatsby.id, changes: firstChange }, + { id: AClockworkOrange.id, changes: secondChange }, + ], + withMany + ); + + expect(withUpserts).toEqual({ + ids: [TheGreatGatsby.id, AClockworkOrange.id], + entities: { + [TheGreatGatsby.id]: { + ...TheGreatGatsby, + ...firstChange, + }, + [AClockworkOrange.id]: { + ...AClockworkOrange, + ...secondChange, + }, + }, + }); + }); }); diff --git a/modules/entity/src/models.ts b/modules/entity/src/models.ts index 416bb6ae30..9f763391b9 100644 --- a/modules/entity/src/models.ts +++ b/modules/entity/src/models.ts @@ -63,6 +63,9 @@ export interface EntityStateAdapter { updateOne>(update: Update, state: S): S; updateMany>(updates: Update[], state: S): S; + + upsertOne>(update: Update, state: S): S; + upsertMany>(updates: Update[], state: S): S; } export type EntitySelectors = { diff --git a/modules/entity/src/sorted_state_adapter.ts b/modules/entity/src/sorted_state_adapter.ts index 4ae8e694b6..c98dcaaec8 100644 --- a/modules/entity/src/sorted_state_adapter.ts +++ b/modules/entity/src/sorted_state_adapter.ts @@ -76,6 +76,32 @@ export function createSortedStateAdapter(selectId: any, sort: any): any { return merge(models, state); } + function upsertOneMutably(update: Update, state: R): boolean; + function upsertOneMutably(update: any, state: any): boolean { + return upsertManyMutably([update], state); + } + + function upsertManyMutably(updates: Update[], state: R): boolean; + function upsertManyMutably(updates: any[], state: any): boolean { + const added: T[] = []; + const updated: Update[] = []; + + for (let index in updates) { + const update = updates[index]; + if (update.id in state.entities) { + updated.push(update); + } else { + added.push({ + ...update.changes, + id: update.id, + }); + } + } + + const didMutate = updateManyMutably(updated, state); + return addManyMutably(added, state) || didMutate; + } + function merge(models: T[], state: R): boolean; function merge(models: any[], state: any): boolean { if (models.length === 0) { @@ -123,8 +149,10 @@ export function createSortedStateAdapter(selectId: any, sort: any): any { removeAll, addOne: createStateOperator(addOneMutably), updateOne: createStateOperator(updateOneMutably), + upsertOne: createStateOperator(upsertOneMutably), addAll: createStateOperator(addAllMutably), addMany: createStateOperator(addManyMutably), updateMany: createStateOperator(updateManyMutably), + upsertMany: createStateOperator(upsertManyMutably), }; } diff --git a/modules/entity/src/unsorted_state_adapter.ts b/modules/entity/src/unsorted_state_adapter.ts index 37057137ab..43186684be 100644 --- a/modules/entity/src/unsorted_state_adapter.ts +++ b/modules/entity/src/unsorted_state_adapter.ts @@ -112,6 +112,32 @@ export function createUnsortedStateAdapter(selectId: IdSelector): any { return didMutate; } + function upsertOneMutably(update: Update, state: R): boolean; + function upsertOneMutably(update: any, state: any): boolean { + return upsertManyMutably([update], state); + } + + function upsertManyMutably(updates: Update[], state: R): boolean; + function upsertManyMutably(updates: any[], state: any): boolean { + const added: T[] = []; + const updated: Update[] = []; + + for (let index in updates) { + const update = updates[index]; + if (update.id in state.entities) { + updated.push(update); + } else { + added.push({ + ...update.changes, + id: update.id, + }); + } + } + + const didMutate = updateManyMutably(updated, state); + return addManyMutably(added, state) || didMutate; + } + return { removeAll, addOne: createStateOperator(addOneMutably), @@ -119,6 +145,8 @@ export function createUnsortedStateAdapter(selectId: IdSelector): any { addAll: createStateOperator(addAllMutably), updateOne: createStateOperator(updateOneMutably), updateMany: createStateOperator(updateManyMutably), + upsertOne: createStateOperator(upsertOneMutably), + upsertMany: createStateOperator(upsertManyMutably), removeOne: createStateOperator(removeOneMutably), removeMany: createStateOperator(removeManyMutably), };