From 3e874f5cad2f35efda703b65ecca063f97580f4d Mon Sep 17 00:00:00 2001 From: Will Schurman Date: Wed, 4 Oct 2023 11:03:24 -0700 Subject: [PATCH] feat: add loadManyByIDsNullableAsync loader method --- packages/entity/src/EnforcingEntityLoader.ts | 9 ++++ packages/entity/src/EntityLoader.ts | 17 ++++++++ .../__tests__/EnforcingEntityLoader-test.ts | 43 +++++++++++++++++++ .../entity/src/__tests__/EntityLoader-test.ts | 6 ++- 4 files changed, 74 insertions(+), 1 deletion(-) diff --git a/packages/entity/src/EnforcingEntityLoader.ts b/packages/entity/src/EnforcingEntityLoader.ts index 7d0fe927..4213405d 100644 --- a/packages/entity/src/EnforcingEntityLoader.ts +++ b/packages/entity/src/EnforcingEntityLoader.ts @@ -114,6 +114,15 @@ export default class EnforcingEntityLoader< return mapMap(entityResults, (result) => result.enforceValue()); } + /** + * Enforcing version of entity loader method by the same name. + * @throws EntityNotAuthorizedError when viewer is not authorized to view one or more of the returned entities + */ + async loadManyByIDsNullableAsync(ids: readonly TID[]): Promise> { + const entityResults = await this.entityLoader.loadManyByIDsNullableAsync(ids); + return mapMap(entityResults, (result) => result?.enforceValue() ?? null); + } + /** * Enforcing version of entity loader method by the same name. * @throws EntityNotAuthorizedError when viewer is not authorized to view one or more of the returned entities diff --git a/packages/entity/src/EntityLoader.ts b/packages/entity/src/EntityLoader.ts index e5e76c90..4e0c74f6 100644 --- a/packages/entity/src/EntityLoader.ts +++ b/packages/entity/src/EntityLoader.ts @@ -185,6 +185,23 @@ export default class EntityLoader< }); } + /** + * Loads many entities for a list of IDs, returning null for any IDs that are non-existent. + * @param ids - IDs of the entities to load + * @returns map from ID to nullable corresponding entity result, where result error can be UnauthorizedError or EntityNotFoundError. + */ + async loadManyByIDsNullableAsync( + ids: readonly TID[] + ): Promise | null>> { + const entityResults = (await this.loadManyByFieldEqualingManyAsync( + this.entityConfiguration.idField as TSelectedFields, + ids + )) as ReadonlyMap[]>; + return mapMap(entityResults, (entityResultsForId) => { + return entityResultsForId[0] ?? null; + }); + } + /** * Loads the first entity matching the selection constructed from the conjunction of specified * operands, or null if no matching entity exists. Entities loaded using this method are not diff --git a/packages/entity/src/__tests__/EnforcingEntityLoader-test.ts b/packages/entity/src/__tests__/EnforcingEntityLoader-test.ts index d2e01813..5787b018 100644 --- a/packages/entity/src/__tests__/EnforcingEntityLoader-test.ts +++ b/packages/entity/src/__tests__/EnforcingEntityLoader-test.ts @@ -222,6 +222,49 @@ describe(EnforcingEntityLoader, () => { }); }); + describe('loadManyByIDsNullableAsync', () => { + it('throws when result is unsuccessful even when there is a null result', async () => { + const entityLoaderMock = mock>(EntityLoader); + const rejection = new Error(); + when(entityLoaderMock.loadManyByIDsNullableAsync(anything())).thenResolve( + new Map( + Object.entries({ + hello: result(rejection), + world: null, + }) + ) + ); + const entityLoader = instance(entityLoaderMock); + const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader); + await expect(enforcingEntityLoader.loadManyByIDsNullableAsync(anything())).rejects.toThrow( + rejection + ); + }); + + it('returns value when result is successful', async () => { + const entityLoaderMock = mock>(EntityLoader); + const resolved = {}; + when(entityLoaderMock.loadManyByIDsNullableAsync(anything())).thenResolve( + new Map( + Object.entries({ + hello: result(resolved), + world: null, + }) + ) + ); + const entityLoader = instance(entityLoaderMock); + const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader); + await expect(enforcingEntityLoader.loadManyByIDsNullableAsync(anything())).resolves.toEqual( + new Map( + Object.entries({ + hello: resolved, + world: null, + }) + ) + ); + }); + }); + describe('loadFirstByFieldEqualityConjunction', () => { it('throws when result is unsuccessful', async () => { const entityLoaderMock = mock>(EntityLoader); diff --git a/packages/entity/src/__tests__/EntityLoader-test.ts b/packages/entity/src/__tests__/EntityLoader-test.ts index bf3fb466..76c6a509 100644 --- a/packages/entity/src/__tests__/EntityLoader-test.ts +++ b/packages/entity/src/__tests__/EntityLoader-test.ts @@ -538,7 +538,11 @@ describe(EntityLoader, () => { await expect(entityLoader.loadByIDAsync(loadByValue)).rejects.toEqual(error); await expect(entityLoader.enforcing().loadByIDAsync(loadByValue)).rejects.toEqual(error); await expect(entityLoader.loadManyByIDsAsync([loadByValue])).rejects.toEqual(error); - await expect(entityLoader.loadManyByIDsAsync([loadByValue])).rejects.toEqual(error); + await expect(entityLoader.enforcing().loadManyByIDsAsync([loadByValue])).rejects.toEqual(error); + await expect(entityLoader.loadManyByIDsNullableAsync([loadByValue])).rejects.toEqual(error); + await expect( + entityLoader.enforcing().loadManyByIDsNullableAsync([loadByValue]) + ).rejects.toEqual(error); await expect( entityLoader.loadManyByFieldEqualingAsync('customIdField', loadByValue) ).rejects.toEqual(error);