mirror of
https://github.com/expo/entity.git
synced 2026-11-13 20:28:06 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b749a0633 |
No files matched your search
+10
-12
@@ -32,9 +32,9 @@ describe(LocalMemoryCacheAdapter, () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
// loading an entity should put it in cache
|
||||
const entity1 = await LocalMemoryTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity1Created.getID());
|
||||
const entity1 = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync(
|
||||
entity1Created.getID()
|
||||
);
|
||||
|
||||
const entitySpecificGenericCacher =
|
||||
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
|
||||
@@ -97,15 +97,13 @@ describe(LocalMemoryCacheAdapter, () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
// loading an entity should put it in cache
|
||||
await LocalMemoryTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity1Created.getID());
|
||||
await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync(entity1Created.getID());
|
||||
|
||||
// load entity with a different request
|
||||
const viewerContext2 = new TestViewerContext(createLocalMemoryTestEntityCompanionProvider());
|
||||
const entity1WithVc2 = await LocalMemoryTestEntity.loader(viewerContext2)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity1Created.getID());
|
||||
const entity1WithVc2 = await LocalMemoryTestEntity.loader(viewerContext2).loadByIDAsync(
|
||||
entity1Created.getID()
|
||||
);
|
||||
|
||||
const cacheAdapter = viewerContext.entityCompanionProvider.getCompanionForEntity(
|
||||
LocalMemoryTestEntity,
|
||||
@@ -140,9 +138,9 @@ describe(LocalMemoryCacheAdapter, () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
// loading an entity will try to put it in cache but it's a noop cache, so it should be a miss
|
||||
const entity1 = await LocalMemoryTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity1Created.getID());
|
||||
const entity1 = await LocalMemoryTestEntity.loader(viewerContext).loadByIDAsync(
|
||||
entity1Created.getID()
|
||||
);
|
||||
|
||||
const entitySpecificGenericCacher =
|
||||
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
|
||||
|
||||
+10
-13
@@ -52,9 +52,9 @@ describe(RedisCacheAdapter, () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
// loading an entity should put it in cache
|
||||
const entity1 = await RedisTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity1Created.getID());
|
||||
const entity1 = await RedisTestEntity.loader(viewerContext).loadByIDAsync(
|
||||
entity1Created.getID()
|
||||
);
|
||||
|
||||
const cachedJSON = await redisCacheAdapterContext.redisClient.get(
|
||||
cacheKeyMaker('id', entity1.getID())
|
||||
@@ -102,16 +102,14 @@ describe(RedisCacheAdapter, () => {
|
||||
);
|
||||
expect(entity1.getField('dateField')).toEqual(date);
|
||||
|
||||
const entity2 = await RedisTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity1.getID());
|
||||
const entity2 = await RedisTestEntity.loader(viewerContext).loadByIDAsync(entity1.getID());
|
||||
expect(entity2.getField('dateField')).toEqual(date);
|
||||
|
||||
// simulate new request
|
||||
const vc2 = new TestViewerContext(
|
||||
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
|
||||
);
|
||||
const entity3 = await RedisTestEntity.loader(vc2).enforcing().loadByIDAsync(entity1.getID());
|
||||
const entity3 = await RedisTestEntity.loader(vc2).loadByIDAsync(entity1.getID());
|
||||
expect(entity3.getField('dateField')).toEqual(date);
|
||||
});
|
||||
|
||||
@@ -122,18 +120,17 @@ describe(RedisCacheAdapter, () => {
|
||||
const entity1 = await enforceAsyncResult(
|
||||
RedisTestEntity.creator(viewerContext).setField('name', '').createAsync()
|
||||
);
|
||||
const entity2 = await RedisTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', '');
|
||||
const entity2 = await RedisTestEntity.loader(viewerContext).loadByFieldEqualingAsync(
|
||||
'name',
|
||||
''
|
||||
);
|
||||
expect(entity2?.getID()).toEqual(entity1.getID());
|
||||
|
||||
// simulate new request
|
||||
const vc2 = new TestViewerContext(
|
||||
createRedisIntegrationTestEntityCompanionProvider(redisCacheAdapterContext)
|
||||
);
|
||||
const entity3 = await RedisTestEntity.loader(vc2)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', '');
|
||||
const entity3 = await RedisTestEntity.loader(vc2).loadByFieldEqualingAsync('name', '');
|
||||
expect(entity3?.getID()).toEqual(entity1.getID());
|
||||
});
|
||||
});
|
||||
+86
-111
@@ -50,9 +50,7 @@ describe('postgres entity integration', () => {
|
||||
PostgresTestEntity.updater(entity).setField('hasADog', false).updateAsync(),
|
||||
]);
|
||||
|
||||
const loadedEntity = await PostgresTestEntity.loader(vc)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity.getID());
|
||||
const loadedEntity = await PostgresTestEntity.loader(vc).loadByIDAsync(entity.getID());
|
||||
|
||||
expect(loadedEntity.getField('hasACat')).toBe(true);
|
||||
expect(loadedEntity.getField('hasADog')).toBe(false);
|
||||
@@ -185,26 +183,22 @@ describe('postgres entity integration', () => {
|
||||
.createAsync()
|
||||
);
|
||||
|
||||
const results = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualityConjunctionAsync([
|
||||
{
|
||||
fieldName: 'hasACat',
|
||||
fieldValue: false,
|
||||
},
|
||||
{
|
||||
fieldName: 'hasADog',
|
||||
fieldValue: true,
|
||||
},
|
||||
]);
|
||||
const results = await PostgresTestEntity.loader(vc1).loadManyByFieldEqualityConjunctionAsync([
|
||||
{
|
||||
fieldName: 'hasACat',
|
||||
fieldValue: false,
|
||||
},
|
||||
{
|
||||
fieldName: 'hasADog',
|
||||
fieldValue: true,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
|
||||
const results2 = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualityConjunctionAsync([
|
||||
{ fieldName: 'hasADog', fieldValues: [true, false] },
|
||||
]);
|
||||
const results2 = await PostgresTestEntity.loader(vc1).loadManyByFieldEqualityConjunctionAsync(
|
||||
[{ fieldName: 'hasADog', fieldValues: [true, false] }]
|
||||
);
|
||||
expect(results2).toHaveLength(3);
|
||||
});
|
||||
|
||||
@@ -217,9 +211,9 @@ describe('postgres entity integration', () => {
|
||||
|
||||
await enforceAsyncResult(PostgresTestEntity.creator(vc1).setField('name', 'c').createAsync());
|
||||
|
||||
const results = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualityConjunctionAsync([], {
|
||||
const results = await PostgresTestEntity.loader(vc1).loadManyByFieldEqualityConjunctionAsync(
|
||||
[],
|
||||
{
|
||||
limit: 2,
|
||||
offset: 1,
|
||||
orderBy: [
|
||||
@@ -228,7 +222,8 @@ describe('postgres entity integration', () => {
|
||||
order: OrderByOrdering.DESCENDING,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
);
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results.map((e) => e.getField('name'))).toEqual(['b', 'a']);
|
||||
});
|
||||
@@ -260,28 +255,26 @@ describe('postgres entity integration', () => {
|
||||
.createAsync()
|
||||
);
|
||||
|
||||
const results = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualityConjunctionAsync([{ fieldName: 'name', fieldValue: null }]);
|
||||
const results = await PostgresTestEntity.loader(vc1).loadManyByFieldEqualityConjunctionAsync([
|
||||
{ fieldName: 'name', fieldValue: null },
|
||||
]);
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results[0]!.getField('name')).toBeNull();
|
||||
|
||||
const results2 = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualityConjunctionAsync(
|
||||
[
|
||||
{ fieldName: 'name', fieldValues: ['a', null] },
|
||||
{ fieldName: 'hasADog', fieldValue: true },
|
||||
const results2 = await PostgresTestEntity.loader(vc1).loadManyByFieldEqualityConjunctionAsync(
|
||||
[
|
||||
{ fieldName: 'name', fieldValues: ['a', null] },
|
||||
{ fieldName: 'hasADog', fieldValue: true },
|
||||
],
|
||||
{
|
||||
orderBy: [
|
||||
{
|
||||
fieldName: 'name',
|
||||
order: OrderByOrdering.DESCENDING,
|
||||
},
|
||||
],
|
||||
{
|
||||
orderBy: [
|
||||
{
|
||||
fieldName: 'name',
|
||||
order: OrderByOrdering.DESCENDING,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
expect(results2).toHaveLength(2);
|
||||
expect(results2.map((e) => e.getField('name'))).toEqual([null, 'a']);
|
||||
});
|
||||
@@ -298,9 +291,10 @@ describe('postgres entity integration', () => {
|
||||
.createAsync()
|
||||
);
|
||||
|
||||
const results = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByRawWhereClauseAsync('name = ?', ['hello']);
|
||||
const results = await PostgresTestEntity.loader(vc1).loadManyByRawWhereClauseAsync(
|
||||
'name = ?',
|
||||
['hello']
|
||||
);
|
||||
|
||||
expect(results).toHaveLength(1);
|
||||
});
|
||||
@@ -316,9 +310,9 @@ describe('postgres entity integration', () => {
|
||||
);
|
||||
|
||||
await expect(
|
||||
PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByRawWhereClauseAsync('invalid_column = ?', ['hello'])
|
||||
PostgresTestEntity.loader(vc1).loadManyByRawWhereClauseAsync('invalid_column = ?', [
|
||||
'hello',
|
||||
])
|
||||
).rejects.toThrow();
|
||||
});
|
||||
|
||||
@@ -346,9 +340,10 @@ describe('postgres entity integration', () => {
|
||||
.createAsync()
|
||||
);
|
||||
|
||||
const results = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByRawWhereClauseAsync('has_a_dog = ?', [true], {
|
||||
const results = await PostgresTestEntity.loader(vc1).loadManyByRawWhereClauseAsync(
|
||||
'has_a_dog = ?',
|
||||
[true],
|
||||
{
|
||||
limit: 2,
|
||||
offset: 1,
|
||||
orderBy: [
|
||||
@@ -357,25 +352,26 @@ describe('postgres entity integration', () => {
|
||||
order: OrderByOrdering.ASCENDING,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
expect(results).toHaveLength(2);
|
||||
expect(results.map((e) => e.getField('name'))).toEqual(['b', 'c']);
|
||||
|
||||
const resultsMultipleOrderBy = await PostgresTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadManyByRawWhereClauseAsync('has_a_dog = ?', [true], {
|
||||
orderBy: [
|
||||
{
|
||||
fieldName: 'hasADog',
|
||||
order: OrderByOrdering.ASCENDING,
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
order: OrderByOrdering.DESCENDING,
|
||||
},
|
||||
],
|
||||
});
|
||||
const resultsMultipleOrderBy = await PostgresTestEntity.loader(
|
||||
vc1
|
||||
).loadManyByRawWhereClauseAsync('has_a_dog = ?', [true], {
|
||||
orderBy: [
|
||||
{
|
||||
fieldName: 'hasADog',
|
||||
order: OrderByOrdering.ASCENDING,
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
order: OrderByOrdering.DESCENDING,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(resultsMultipleOrderBy).toHaveLength(3);
|
||||
expect(resultsMultipleOrderBy.map((e) => e.getField('name'))).toEqual(['c', 'b', 'a']);
|
||||
@@ -395,9 +391,7 @@ describe('postgres entity integration', () => {
|
||||
.enforceCreateAsync()
|
||||
).rejects.toThrowError('name cannot have value beforeCreate');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'beforeCreate')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'beforeCreate')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
@@ -406,27 +400,21 @@ describe('postgres entity integration', () => {
|
||||
.enforceCreateAsync()
|
||||
).rejects.toThrowError('name cannot have value afterCreate');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'afterCreate')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'afterCreate')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.creator(vc1).setField('name', 'beforeAll').enforceCreateAsync()
|
||||
).rejects.toThrowError('name cannot have value beforeAll');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'beforeAll')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'beforeAll')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.creator(vc1).setField('name', 'afterAll').enforceCreateAsync()
|
||||
).rejects.toThrowError('name cannot have value afterAll');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'afterAll')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'afterAll')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
@@ -435,9 +423,7 @@ describe('postgres entity integration', () => {
|
||||
.enforceCreateAsync()
|
||||
).rejects.toThrowError('name cannot have value afterCommit');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'afterCommit')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'afterCommit')
|
||||
).resolves.not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -458,9 +444,7 @@ describe('postgres entity integration', () => {
|
||||
.enforceUpdateAsync()
|
||||
).rejects.toThrowError('name cannot have value beforeUpdate');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'beforeUpdate')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'beforeUpdate')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
@@ -469,9 +453,7 @@ describe('postgres entity integration', () => {
|
||||
.enforceUpdateAsync()
|
||||
).rejects.toThrowError('name cannot have value afterUpdate');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'afterUpdate')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'afterUpdate')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
@@ -480,9 +462,7 @@ describe('postgres entity integration', () => {
|
||||
.enforceUpdateAsync()
|
||||
).rejects.toThrowError('name cannot have value beforeAll');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'beforeAll')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'beforeAll')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
@@ -491,9 +471,7 @@ describe('postgres entity integration', () => {
|
||||
.enforceUpdateAsync()
|
||||
).rejects.toThrowError('name cannot have value afterAll');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'afterAll')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'afterAll')
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
@@ -502,9 +480,7 @@ describe('postgres entity integration', () => {
|
||||
.enforceUpdateAsync()
|
||||
).rejects.toThrowError('name cannot have value afterCommit');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'afterCommit')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'afterCommit')
|
||||
).resolves.not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -522,9 +498,7 @@ describe('postgres entity integration', () => {
|
||||
PostgresTriggerTestEntity.enforceDeleteAsync(entityBeforeDelete)
|
||||
).rejects.toThrowError('name cannot have value beforeDelete');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'beforeDelete')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'beforeDelete')
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
const entityAfterDelete = await PostgresTriggerTestEntity.creator(vc1)
|
||||
@@ -534,9 +508,7 @@ describe('postgres entity integration', () => {
|
||||
PostgresTriggerTestEntity.enforceDeleteAsync(entityAfterDelete)
|
||||
).rejects.toThrowError('name cannot have value afterDelete');
|
||||
await expect(
|
||||
PostgresTriggerTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'afterDelete')
|
||||
PostgresTriggerTestEntity.loader(vc1).loadByFieldEqualingAsync('name', 'afterDelete')
|
||||
).resolves.not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -553,9 +525,10 @@ describe('postgres entity integration', () => {
|
||||
.enforceCreateAsync()
|
||||
).rejects.toThrowError('name cannot have value beforeCreateAndBeforeUpdate');
|
||||
await expect(
|
||||
PostgresValidatorTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'beforeCreateAndBeforeUpdate')
|
||||
PostgresValidatorTestEntity.loader(vc1).loadByFieldEqualingAsync(
|
||||
'name',
|
||||
'beforeCreateAndBeforeUpdate'
|
||||
)
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -575,9 +548,10 @@ describe('postgres entity integration', () => {
|
||||
.enforceUpdateAsync()
|
||||
).rejects.toThrowError('name cannot have value beforeCreateAndBeforeUpdate');
|
||||
await expect(
|
||||
PostgresValidatorTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'beforeCreateAndBeforeUpdate')
|
||||
PostgresValidatorTestEntity.loader(vc1).loadByFieldEqualingAsync(
|
||||
'name',
|
||||
'beforeCreateAndBeforeUpdate'
|
||||
)
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -592,9 +566,10 @@ describe('postgres entity integration', () => {
|
||||
.enforceCreateAsync();
|
||||
await PostgresValidatorTestEntity.enforceDeleteAsync(entityToDelete);
|
||||
await expect(
|
||||
PostgresValidatorTestEntity.loader(vc1)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('name', 'shouldBeDeleted')
|
||||
PostgresValidatorTestEntity.loader(vc1).loadByFieldEqualingAsync(
|
||||
'name',
|
||||
'shouldBeDeleted'
|
||||
)
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('postgres errors', () => {
|
||||
const vc2 = new ViewerContext(
|
||||
createKnexIntegrationTestEntityCompanionProvider(shortTimeoutKnexInstance)
|
||||
);
|
||||
await expect(ErrorsTestEntity.loader(vc2).enforcing().loadByIDAsync(1)).rejects.toThrow(
|
||||
await expect(ErrorsTestEntity.loader(vc2).loadByIDAsync(1)).rejects.toThrow(
|
||||
EntityDatabaseAdapterTransientError
|
||||
);
|
||||
await shortTimeoutKnexInstance.destroy();
|
||||
|
||||
@@ -19,9 +19,10 @@ router.get('/', async (ctx: ExampleContext) => {
|
||||
const viewerContext = ctx.state.viewerContext;
|
||||
let notes: readonly NoteEntity[] = [];
|
||||
if (viewerContext.isUserViewerContext()) {
|
||||
notes = await NoteEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualingAsync('userID', viewerContext.userID);
|
||||
notes = await NoteEntity.loader(viewerContext).loadManyByFieldEqualingAsync(
|
||||
'userID',
|
||||
viewerContext.userID
|
||||
);
|
||||
}
|
||||
ctx.body = {
|
||||
notes: notes.map((note) => note.getAllFields()),
|
||||
|
||||
@@ -46,16 +46,14 @@ export const resolvers: IResolvers<any, GraphqlContext> = {
|
||||
return viewerContext.userID;
|
||||
},
|
||||
async noteByID(_root, args, { viewerContext }) {
|
||||
return await NoteEntity.loader(viewerContext).enforcing().loadByIDAsync(args.id);
|
||||
return await NoteEntity.loader(viewerContext).loadByIDAsync(args.id);
|
||||
},
|
||||
} as IResolverObject<any, GraphqlContext>,
|
||||
|
||||
User: {
|
||||
id: (root) => root,
|
||||
async notes(root, _args, { viewerContext }) {
|
||||
return await NoteEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualingAsync('userID', root);
|
||||
return await NoteEntity.loader(viewerContext).loadManyByFieldEqualingAsync('userID', root);
|
||||
},
|
||||
} as IResolverObject<string, GraphqlContext>,
|
||||
|
||||
@@ -79,18 +77,14 @@ export const resolvers: IResolvers<any, GraphqlContext> = {
|
||||
.enforceCreateAsync();
|
||||
},
|
||||
async updateNote(_root, args, { viewerContext }) {
|
||||
const existingNote = await NoteEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(args.id);
|
||||
const existingNote = await NoteEntity.loader(viewerContext).loadByIDAsync(args.id);
|
||||
return await NoteEntity.updater(existingNote)
|
||||
.setField('title', args.note.title)
|
||||
.setField('body', args.note.body)
|
||||
.enforceUpdateAsync();
|
||||
},
|
||||
async deleteNote(_root, args, { viewerContext }) {
|
||||
const existingNote = await NoteEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(args.id);
|
||||
const existingNote = await NoteEntity.loader(viewerContext).loadByIDAsync(args.id);
|
||||
await NoteEntity.enforceDeleteAsync(existingNote);
|
||||
return existingNote;
|
||||
},
|
||||
|
||||
+8
-11
@@ -153,10 +153,8 @@ describe('Entity cache inconsistency', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
// put entities in cache and dataloader
|
||||
await TestEntity.loader(viewerContext).enforcing().loadByIDAsync(entity1.getID());
|
||||
await TestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('other_string', 'hello');
|
||||
await TestEntity.loader(viewerContext).loadByIDAsync(entity1.getID());
|
||||
await TestEntity.loader(viewerContext).loadByFieldEqualingAsync('other_string', 'hello');
|
||||
|
||||
let openBarrier1: () => void;
|
||||
const barrier1 = new Promise<void>((resolve) => {
|
||||
@@ -179,7 +177,7 @@ describe('Entity cache inconsistency', () => {
|
||||
const viewerContextInternal = new ViewerContext(
|
||||
createFullIntegrationTestEntityCompanionProvider(knexInstance, redisCacheAdapterContext)
|
||||
);
|
||||
await TestEntity.loader(viewerContextInternal).enforcing().loadByIDAsync(entity1.getID());
|
||||
await TestEntity.loader(viewerContextInternal).loadByIDAsync(entity1.getID());
|
||||
|
||||
openBarrier2!();
|
||||
})(),
|
||||
@@ -203,12 +201,11 @@ describe('Entity cache inconsistency', () => {
|
||||
createFullIntegrationTestEntityCompanionProvider(knexInstance, redisCacheAdapterContext)
|
||||
);
|
||||
|
||||
const loadedById = await TestEntity.loader(viewerContextLast)
|
||||
.enforcing()
|
||||
.loadByIDAsync(entity1.getID());
|
||||
const loadedByField = await TestEntity.loader(viewerContextLast)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('other_string', 'hello');
|
||||
const loadedById = await TestEntity.loader(viewerContextLast).loadByIDAsync(entity1.getID());
|
||||
const loadedByField = await TestEntity.loader(viewerContextLast).loadByFieldEqualingAsync(
|
||||
'other_string',
|
||||
'hello'
|
||||
);
|
||||
|
||||
expect(loadedById.getField('third_string')).toEqual('updated');
|
||||
expect(loadedByField!.getField('third_string')).toEqual('updated');
|
||||
|
||||
+4
-6
@@ -87,22 +87,20 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
ChildEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('parent_id', parent.getID())
|
||||
ChildEntity.loader(viewerContext).loadByFieldEqualingAsync('parent_id', parent.getID())
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
await ParentEntity.enforceDeleteAsync(parent);
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.toBeNull();
|
||||
|
||||
await expect(
|
||||
ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID())
|
||||
ChildEntity.loader(viewerContext).loadByIDNullableAsync(child.getID())
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
+6
-6
@@ -224,18 +224,18 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
|
||||
if (edgeDeletionBehavior === EntityEdgeDeletionBehavior.SET_NULL) {
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(category1.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(category1.getID())
|
||||
).resolves.toBeNull();
|
||||
const otherLoaded = await OtherEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDNullableAsync(other1.getID());
|
||||
const otherLoaded = await OtherEntity.loader(viewerContext).loadByIDNullableAsync(
|
||||
other1.getID()
|
||||
);
|
||||
expect(otherLoaded?.getField('parent_category_id')).toBeNull();
|
||||
} else {
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(category1.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(category1.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
OtherEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(other1.getID())
|
||||
OtherEntity.loader(viewerContext).loadByIDNullableAsync(other1.getID())
|
||||
).resolves.toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
+3
-5
@@ -159,11 +159,9 @@ class TestSecondaryLocalMemoryCacheLoader extends EntitySecondaryCacheLoader<
|
||||
}
|
||||
return nullthrows(
|
||||
(
|
||||
await this.entityLoader
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualityConjunctionAsync([
|
||||
{ fieldName: 'id', fieldValue: loadParams.id },
|
||||
])
|
||||
await this.entityLoader.loadManyByFieldEqualityConjunctionAsync([
|
||||
{ fieldName: 'id', fieldValue: loadParams.id },
|
||||
])
|
||||
)[0]
|
||||
).getAllFields();
|
||||
});
|
||||
|
||||
+3
-5
@@ -40,11 +40,9 @@ class TestSecondaryRedisCacheLoader extends EntitySecondaryCacheLoader<
|
||||
}
|
||||
return nullthrows(
|
||||
(
|
||||
await this.entityLoader
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualityConjunctionAsync([
|
||||
{ fieldName: 'id', fieldValue: loadParams.id },
|
||||
])
|
||||
await this.entityLoader.loadManyByFieldEqualityConjunctionAsync([
|
||||
{ fieldName: 'id', fieldValue: loadParams.id },
|
||||
])
|
||||
)[0]
|
||||
).getAllFields();
|
||||
});
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
import { FieldEqualityCondition, QuerySelectionModifiers } from './EntityDatabaseAdapter';
|
||||
import EntityLoader from './EntityLoader';
|
||||
import EntityPrivacyPolicy from './EntityPrivacyPolicy';
|
||||
import ReadonlyEntity from './ReadonlyEntity';
|
||||
import ViewerContext from './ViewerContext';
|
||||
import { mapMap } from './utils/collections/maps';
|
||||
|
||||
/**
|
||||
* Enforcing view on an entity loader. All loads through this loader will throw
|
||||
* if the loads are not successful.
|
||||
*/
|
||||
export default class EnforcingEntityLoader<
|
||||
TFields,
|
||||
TID extends NonNullable<TFields[TSelectedFields]>,
|
||||
TViewerContext extends ViewerContext,
|
||||
TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
|
||||
TPrivacyPolicy extends EntityPrivacyPolicy<
|
||||
TFields,
|
||||
TID,
|
||||
TViewerContext,
|
||||
TEntity,
|
||||
TSelectedFields
|
||||
>,
|
||||
TSelectedFields extends keyof TFields
|
||||
> {
|
||||
constructor(
|
||||
private readonly entityLoader: EntityLoader<
|
||||
TFields,
|
||||
TID,
|
||||
TViewerContext,
|
||||
TEntity,
|
||||
TPrivacyPolicy,
|
||||
TSelectedFields
|
||||
>
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view one or more of the returned entities
|
||||
*/
|
||||
async loadManyByFieldEqualingManyAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly TEntity[]>> {
|
||||
const fieldValuesToResults = await this.entityLoader.loadManyByFieldEqualingManyAsync(
|
||||
fieldName,
|
||||
fieldValues
|
||||
);
|
||||
return mapMap(fieldValuesToResults, (results) =>
|
||||
results.map((result) => result.enforceValue())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view one or more of the returned entities
|
||||
*/
|
||||
async loadManyByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValue: NonNullable<TFields[N]>
|
||||
): Promise<readonly TEntity[]> {
|
||||
const entityResults = await this.entityLoader.loadManyByFieldEqualingAsync(
|
||||
fieldName,
|
||||
fieldValue
|
||||
);
|
||||
return entityResults.map((result) => result.enforceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view the returned entity
|
||||
* @throws when multiple entities are found matching the condition
|
||||
*/
|
||||
async loadByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
uniqueFieldName: N,
|
||||
fieldValue: NonNullable<TFields[N]>
|
||||
): Promise<TEntity | null> {
|
||||
const entityResult = await this.entityLoader.loadByFieldEqualingAsync(
|
||||
uniqueFieldName,
|
||||
fieldValue
|
||||
);
|
||||
return entityResult ? entityResult.enforceValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view the returned entity
|
||||
*/
|
||||
async loadByIDAsync(id: TID): Promise<TEntity> {
|
||||
const entityResult = await this.entityLoader.loadByIDAsync(id);
|
||||
return entityResult.enforceValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view the returned entity
|
||||
* @throws when multiple entities are found matching the condition
|
||||
*/
|
||||
async loadByIDNullableAsync(id: TID): Promise<TEntity | null> {
|
||||
const entityResult = await this.entityLoader.loadByIDNullableAsync(id);
|
||||
return entityResult ? entityResult.enforceValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view one or more of the returned entities
|
||||
*/
|
||||
async loadManyByIDsAsync(ids: readonly TID[]): Promise<ReadonlyMap<TID, TEntity>> {
|
||||
const entityResults = await this.entityLoader.loadManyByIDsAsync(ids);
|
||||
return mapMap(entityResults, (result) => result.enforceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view one or more of the returned entities
|
||||
*/
|
||||
async loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
||||
querySelectionModifiers: QuerySelectionModifiers<TFields> = {}
|
||||
): Promise<readonly TEntity[]> {
|
||||
const entityResults = await this.entityLoader.loadManyByFieldEqualityConjunctionAsync(
|
||||
fieldEqualityOperands,
|
||||
querySelectionModifiers
|
||||
);
|
||||
return entityResults.map((result) => result.enforceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Enforcing version of entity loader method by the same name.
|
||||
* @throws {@link EntityNotAuthorizedError} when viewer is not authorized to view one or more of the returned entities
|
||||
*/
|
||||
async loadManyByRawWhereClauseAsync(
|
||||
rawWhereClause: string,
|
||||
bindings: any[] | object,
|
||||
querySelectionModifiers: QuerySelectionModifiers<TFields> = {}
|
||||
): Promise<readonly TEntity[]> {
|
||||
const entityResults = await this.entityLoader.loadManyByRawWhereClauseAsync(
|
||||
rawWhereClause,
|
||||
bindings,
|
||||
querySelectionModifiers
|
||||
);
|
||||
return entityResults.map((result) => result.enforceValue());
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,9 @@ export default class EntityAssociationLoader<
|
||||
.getLoaderFactory()
|
||||
.forLoad(queryContext, { cascadingDeleteCause: null });
|
||||
|
||||
return (await loader.loadByIDAsync(associatedEntityID as unknown as TAssociatedID)) as Result<
|
||||
return (await loader.resultLoader.loadByIDAsync(
|
||||
associatedEntityID as unknown as TAssociatedID
|
||||
)) as Result<
|
||||
null extends TFields[TIdentifyingField] ? TAssociatedEntity | null : TAssociatedEntity
|
||||
>;
|
||||
}
|
||||
@@ -124,7 +126,7 @@ export default class EntityAssociationLoader<
|
||||
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
|
||||
.getLoaderFactory()
|
||||
.forLoad(queryContext, { cascadingDeleteCause: null });
|
||||
return await loader.loadManyByFieldEqualingAsync(
|
||||
return await loader.resultLoader.loadManyByFieldEqualingAsync(
|
||||
associatedEntityFieldContainingThisID,
|
||||
thisID as any
|
||||
);
|
||||
@@ -181,7 +183,7 @@ export default class EntityAssociationLoader<
|
||||
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
|
||||
.getLoaderFactory()
|
||||
.forLoad(queryContext, { cascadingDeleteCause: null });
|
||||
return await loader.loadByFieldEqualingAsync(
|
||||
return await loader.resultLoader.loadByFieldEqualingAsync(
|
||||
associatedEntityLookupByField,
|
||||
associatedFieldValue as any
|
||||
);
|
||||
@@ -239,7 +241,7 @@ export default class EntityAssociationLoader<
|
||||
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
|
||||
.getLoaderFactory()
|
||||
.forLoad(queryContext, { cascadingDeleteCause: null });
|
||||
return await loader.loadManyByFieldEqualingAsync(
|
||||
return await loader.resultLoader.loadManyByFieldEqualingAsync(
|
||||
associatedEntityLookupByField,
|
||||
associatedFieldValue as any
|
||||
);
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
import { Result, asyncResult, result } from '@expo/results';
|
||||
import invariant from 'invariant';
|
||||
|
||||
import EnforcingEntityLoader from './EnforcingEntityLoader';
|
||||
import { IEntityClass } from './Entity';
|
||||
import EntityConfiguration from './EntityConfiguration';
|
||||
import {
|
||||
FieldEqualityCondition,
|
||||
QuerySelectionModifiers,
|
||||
isSingleValueFieldEqualityCondition,
|
||||
} from './EntityDatabaseAdapter';
|
||||
import { FieldEqualityCondition, QuerySelectionModifiers } from './EntityDatabaseAdapter';
|
||||
import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
|
||||
import { EntityQueryContext } from './EntityQueryContext';
|
||||
import EntityResultLoader from './EntityResultLoader';
|
||||
import ReadonlyEntity from './ReadonlyEntity';
|
||||
import ViewerContext from './ViewerContext';
|
||||
import EntityInvalidFieldValueError from './errors/EntityInvalidFieldValueError';
|
||||
import EntityNotFoundError from './errors/EntityNotFoundError';
|
||||
import EntityDataManager from './internal/EntityDataManager';
|
||||
import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
|
||||
import { mapMap, mapMapAsync } from './utils/collections/maps';
|
||||
import { mapMap } from './utils/collections/maps';
|
||||
|
||||
/**
|
||||
* The primary interface for loading entities. All normal loads are batched,
|
||||
@@ -37,12 +28,24 @@ export default class EntityLoader<
|
||||
>,
|
||||
TSelectedFields extends keyof TFields
|
||||
> {
|
||||
/**
|
||||
* Result-based view on an entity loader. All loads through this loader will wrap their results in Result.
|
||||
*/
|
||||
public readonly resultLoader: EntityResultLoader<
|
||||
TFields,
|
||||
TID,
|
||||
TViewerContext,
|
||||
TEntity,
|
||||
TPrivacyPolicy,
|
||||
TSelectedFields
|
||||
>;
|
||||
|
||||
constructor(
|
||||
private readonly viewerContext: TViewerContext,
|
||||
private readonly queryContext: EntityQueryContext,
|
||||
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
|
||||
private readonly entityConfiguration: EntityConfiguration<TFields>,
|
||||
private readonly entityClass: IEntityClass<
|
||||
viewerContext: TViewerContext,
|
||||
queryContext: EntityQueryContext,
|
||||
privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
|
||||
entityConfiguration: EntityConfiguration<TFields>,
|
||||
entityClass: IEntityClass<
|
||||
TFields,
|
||||
TID,
|
||||
TViewerContext,
|
||||
@@ -50,133 +53,105 @@ export default class EntityLoader<
|
||||
TPrivacyPolicy,
|
||||
TSelectedFields
|
||||
>,
|
||||
private readonly privacyPolicy: TPrivacyPolicy,
|
||||
privacyPolicy: TPrivacyPolicy,
|
||||
private readonly dataManager: EntityDataManager<TFields>,
|
||||
protected readonly metricsAdapter: IEntityMetricsAdapter
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Enforcing view on this entity loader. All loads through this view are
|
||||
* guaranteed to be the values of successful results (or null for some loader methods),
|
||||
* and will throw otherwise.
|
||||
*/
|
||||
enforcing(): EnforcingEntityLoader<
|
||||
TFields,
|
||||
TID,
|
||||
TViewerContext,
|
||||
TEntity,
|
||||
TPrivacyPolicy,
|
||||
TSelectedFields
|
||||
> {
|
||||
return new EnforcingEntityLoader(this);
|
||||
) {
|
||||
this.resultLoader = new EntityResultLoader(
|
||||
viewerContext,
|
||||
queryContext,
|
||||
privacyPolicyEvaluationContext,
|
||||
entityConfiguration,
|
||||
entityClass,
|
||||
privacyPolicy,
|
||||
dataManager,
|
||||
metricsAdapter
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load many entities where fieldName is one of fieldValues.
|
||||
* @param fieldName - entity field being queried
|
||||
* @param fieldValues - fieldName field values being queried
|
||||
* @returns map from fieldValue to entity results that match the query for that fieldValue,
|
||||
* where result errors can be UnauthorizedError
|
||||
* @returns map from fieldValue to entities that match the query for that fieldValue
|
||||
*/
|
||||
async loadManyByFieldEqualingManyAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly Result<TEntity>[]>> {
|
||||
this.validateFieldValues(fieldName, fieldValues);
|
||||
|
||||
const fieldValuesToFieldObjects = await this.dataManager.loadManyByFieldEqualingAsync(
|
||||
this.queryContext,
|
||||
): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly TEntity[]>> {
|
||||
const fieldValuesToResults = await this.resultLoader.loadManyByFieldEqualingManyAsync(
|
||||
fieldName,
|
||||
fieldValues
|
||||
);
|
||||
|
||||
return await this.constructAndAuthorizeEntitiesAsync(fieldValuesToFieldObjects);
|
||||
return mapMap(fieldValuesToResults, (results) =>
|
||||
results.map((result) => result.enforceValue())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load many entities where fieldName equals fieldValue.
|
||||
* @param fieldName - entity field being queried
|
||||
* @param fieldValue - fieldName field value being queried
|
||||
* @returns array of entity results that match the query for fieldValue, where result error can be UnauthorizedError
|
||||
* @returns array of entities that match the query for fieldValue
|
||||
*/
|
||||
async loadManyByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValue: NonNullable<TFields[N]>
|
||||
): Promise<readonly Result<TEntity>[]> {
|
||||
const entityResults = await this.loadManyByFieldEqualingManyAsync(fieldName, [fieldValue]);
|
||||
const entityResultsForFieldValue = entityResults.get(fieldValue);
|
||||
invariant(
|
||||
entityResultsForFieldValue !== undefined,
|
||||
`${fieldValue} should be guaranteed to be present in returned map of entities`
|
||||
): Promise<readonly TEntity[]> {
|
||||
const entityResults = await this.resultLoader.loadManyByFieldEqualingAsync(
|
||||
fieldName,
|
||||
fieldValue
|
||||
);
|
||||
return entityResultsForFieldValue!;
|
||||
return entityResults.map((result) => result.enforceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an entity where fieldName equals fieldValue, or null if no entity exists.
|
||||
* @param uniqueFieldName - entity field being queried
|
||||
* @param fieldValue - uniqueFieldName field value being queried
|
||||
* @returns entity result where uniqueFieldName equals fieldValue, or null if no entity matches the condition.
|
||||
* @returns entity where uniqueFieldName equals fieldValue, or null if no entity matches the condition.
|
||||
* @throws when multiple entities match the condition
|
||||
*/
|
||||
async loadByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
uniqueFieldName: N,
|
||||
fieldValue: NonNullable<TFields[N]>
|
||||
): Promise<Result<TEntity> | null> {
|
||||
const entityResults = await this.loadManyByFieldEqualingAsync(uniqueFieldName, fieldValue);
|
||||
invariant(
|
||||
entityResults.length <= 1,
|
||||
`loadByFieldEqualing: Multiple entities of type ${this.entityClass.name} found for ${uniqueFieldName}=${fieldValue}`
|
||||
): Promise<TEntity | null> {
|
||||
const entityResult = await this.resultLoader.loadByFieldEqualingAsync(
|
||||
uniqueFieldName,
|
||||
fieldValue
|
||||
);
|
||||
return entityResults[0] ?? null;
|
||||
return entityResult ? entityResult.enforceValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an entity by a specified ID.
|
||||
* @param id - ID of the entity
|
||||
* @returns entity result for matching ID, where result error can be UnauthorizedError or EntityNotFoundError.
|
||||
* @returns entity for matching ID
|
||||
*/
|
||||
async loadByIDAsync(id: TID): Promise<Result<TEntity>> {
|
||||
const entityResults = await this.loadManyByIDsAsync([id]);
|
||||
const entityResult = entityResults.get(id);
|
||||
if (entityResult === undefined) {
|
||||
return result(
|
||||
new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id)
|
||||
);
|
||||
}
|
||||
return entityResult;
|
||||
async loadByIDAsync(id: TID): Promise<TEntity> {
|
||||
const entityResult = await this.resultLoader.loadByIDAsync(id);
|
||||
return entityResult.enforceValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an entity by a specified ID, or return null if non-existent.
|
||||
* @param id - ID of the entity
|
||||
* @returns entity result for matching ID, or null if no entity exists for ID.
|
||||
* @returns entity for matching ID, or null if no entity exists for ID.
|
||||
*/
|
||||
async loadByIDNullableAsync(id: TID): Promise<Result<TEntity> | null> {
|
||||
return await this.loadByFieldEqualingAsync(
|
||||
this.entityConfiguration.idField as TSelectedFields,
|
||||
id
|
||||
);
|
||||
async loadByIDNullableAsync(id: TID): Promise<TEntity | null> {
|
||||
const entityResult = await this.resultLoader.loadByIDNullableAsync(id);
|
||||
return entityResult ? entityResult.enforceValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads many entities for a list of IDs.
|
||||
* @param viewerContext - viewer context of loading user
|
||||
* @param ids - IDs of the entities to load
|
||||
* @returns map from ID to corresponding entity result, where result error can be UnauthorizedError or EntityNotFoundError.
|
||||
* @returns map from ID to corresponding entity
|
||||
*/
|
||||
async loadManyByIDsAsync(ids: readonly TID[]): Promise<ReadonlyMap<TID, Result<TEntity>>> {
|
||||
const entityResults = (await this.loadManyByFieldEqualingManyAsync(
|
||||
this.entityConfiguration.idField as TSelectedFields,
|
||||
ids
|
||||
)) as ReadonlyMap<TID, readonly Result<TEntity>[]>;
|
||||
return mapMap(entityResults, (entityResultsForId, id) => {
|
||||
const entityResult = entityResultsForId[0];
|
||||
return (
|
||||
entityResult ??
|
||||
result(new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id))
|
||||
);
|
||||
});
|
||||
async loadManyByIDsAsync(ids: readonly TID[]): Promise<ReadonlyMap<TID, TEntity>> {
|
||||
const entityResults = await this.resultLoader.loadManyByIDsAsync(ids);
|
||||
return mapMap(entityResults, (result) => result.enforceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -191,41 +166,17 @@ export default class EntityLoader<
|
||||
*
|
||||
* @param fieldEqualityOperands - list of field equality WHERE clause operand specifications
|
||||
* @param querySelectionModifiers - limit, offset, and orderBy for the query
|
||||
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
||||
* @returns array of entities that match the query
|
||||
*/
|
||||
async loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
||||
querySelectionModifiers: QuerySelectionModifiers<TFields> = {}
|
||||
): Promise<readonly Result<TEntity>[]> {
|
||||
for (const fieldEqualityOperand of fieldEqualityOperands) {
|
||||
const fieldValues = isSingleValueFieldEqualityCondition(fieldEqualityOperand)
|
||||
? [fieldEqualityOperand.fieldValue]
|
||||
: fieldEqualityOperand.fieldValues;
|
||||
this.validateFieldValues(fieldEqualityOperand.fieldName, fieldValues);
|
||||
}
|
||||
|
||||
const fieldObjects = await this.dataManager.loadManyByFieldEqualityConjunctionAsync(
|
||||
this.queryContext,
|
||||
): Promise<readonly TEntity[]> {
|
||||
const entityResults = await this.resultLoader.loadManyByFieldEqualityConjunctionAsync(
|
||||
fieldEqualityOperands,
|
||||
querySelectionModifiers
|
||||
);
|
||||
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
||||
return await Promise.all(
|
||||
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
||||
if (!uncheckedEntityResult.ok) {
|
||||
return uncheckedEntityResult;
|
||||
}
|
||||
return await asyncResult(
|
||||
this.privacyPolicy.authorizeReadAsync(
|
||||
this.viewerContext,
|
||||
this.queryContext,
|
||||
this.privacyPolicyEvaluationContext,
|
||||
uncheckedEntityResult.value,
|
||||
this.metricsAdapter
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
return entityResults.map((result) => result.enforceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,7 +199,7 @@ export default class EntityLoader<
|
||||
* @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
|
||||
* @param bindings - array of positional bindings or object of named bindings
|
||||
* @param querySelectionModifiers - limit, offset, and orderBy for the query
|
||||
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
||||
* @returns array of entities that match the query
|
||||
* @throws Error when rawWhereClause or bindings are invalid
|
||||
*
|
||||
* @deprecated prefer caching loaders
|
||||
@@ -257,30 +208,13 @@ export default class EntityLoader<
|
||||
rawWhereClause: string,
|
||||
bindings: any[] | object,
|
||||
querySelectionModifiers: QuerySelectionModifiers<TFields> = {}
|
||||
): Promise<readonly Result<TEntity>[]> {
|
||||
const fieldObjects = await this.dataManager.loadManyByRawWhereClauseAsync(
|
||||
this.queryContext,
|
||||
): Promise<readonly TEntity[]> {
|
||||
const entityResults = await this.resultLoader.loadManyByRawWhereClauseAsync(
|
||||
rawWhereClause,
|
||||
bindings,
|
||||
querySelectionModifiers
|
||||
);
|
||||
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
||||
return await Promise.all(
|
||||
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
||||
if (!uncheckedEntityResult.ok) {
|
||||
return uncheckedEntityResult;
|
||||
}
|
||||
return await asyncResult(
|
||||
this.privacyPolicy.authorizeReadAsync(
|
||||
this.viewerContext,
|
||||
this.queryContext,
|
||||
this.privacyPolicyEvaluationContext,
|
||||
uncheckedEntityResult.value,
|
||||
this.metricsAdapter
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
return entityResults.map((result) => result.enforceValue());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -299,63 +233,4 @@ export default class EntityLoader<
|
||||
async invalidateEntityAsync(entity: TEntity): Promise<void> {
|
||||
await this.invalidateFieldsAsync(entity.getAllDatabaseFields());
|
||||
}
|
||||
|
||||
private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
|
||||
return fieldsObjects.map((fieldsObject) => {
|
||||
try {
|
||||
return result(new this.entityClass(this.viewerContext, fieldsObject));
|
||||
} catch (e) {
|
||||
if (!(e instanceof Error)) {
|
||||
throw e;
|
||||
}
|
||||
return result(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct and authorize entities from fields map, returning error results for entities that fail
|
||||
* to construct or fail to authorize.
|
||||
*
|
||||
* @param map - map from an arbitrary key type to an array of entity field objects
|
||||
*/
|
||||
public async constructAndAuthorizeEntitiesAsync<K>(
|
||||
map: ReadonlyMap<K, readonly Readonly<TFields>[]>
|
||||
): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
|
||||
const uncheckedEntityResultsMap = mapMap(map, (fieldObjects) =>
|
||||
this.tryConstructEntities(fieldObjects)
|
||||
);
|
||||
return await mapMapAsync(uncheckedEntityResultsMap, async (uncheckedEntityResults) => {
|
||||
return await Promise.all(
|
||||
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
||||
if (!uncheckedEntityResult.ok) {
|
||||
return uncheckedEntityResult;
|
||||
}
|
||||
return await asyncResult(
|
||||
this.privacyPolicy.authorizeReadAsync(
|
||||
this.viewerContext,
|
||||
this.queryContext,
|
||||
this.privacyPolicyEvaluationContext,
|
||||
uncheckedEntityResult.value,
|
||||
this.metricsAdapter
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private validateFieldValues<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValues: readonly TFields[N][]
|
||||
): void {
|
||||
const fieldDefinition = this.entityConfiguration.schema.get(fieldName);
|
||||
invariant(fieldDefinition, `must have field definition for field = ${fieldName}`);
|
||||
for (const fieldValue of fieldValues) {
|
||||
const isInputValid = fieldDefinition.validateInputValue(fieldValue);
|
||||
if (!isInputValid) {
|
||||
throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,9 +261,7 @@ export class CreateMutator<
|
||||
);
|
||||
|
||||
const unauthorizedEntityAfterInsert = new this.entityClass(this.viewerContext, insertResult);
|
||||
const newEntity = await entityLoader
|
||||
.enforcing()
|
||||
.loadByIDAsync(unauthorizedEntityAfterInsert.getID());
|
||||
const newEntity = await entityLoader.loadByIDAsync(unauthorizedEntityAfterInsert.getID());
|
||||
|
||||
await this.executeMutationTriggersAsync(
|
||||
this.mutationTriggers.afterCreate,
|
||||
@@ -469,9 +467,7 @@ export class UpdateMutator<
|
||||
);
|
||||
|
||||
const unauthorizedEntityAfterUpdate = new this.entityClass(this.viewerContext, updateResult);
|
||||
const updatedEntity = await entityLoader
|
||||
.enforcing()
|
||||
.loadByIDAsync(unauthorizedEntityAfterUpdate.getID());
|
||||
const updatedEntity = await entityLoader.loadByIDAsync(unauthorizedEntityAfterUpdate.getID());
|
||||
|
||||
await this.executeMutationTriggersAsync(
|
||||
this.mutationTriggers.afterUpdate,
|
||||
@@ -759,7 +755,7 @@ export class DeleteMutator<
|
||||
if (associatedEntityLookupByField) {
|
||||
inboundReferenceEntities = await loaderFactory
|
||||
.forLoad(queryContext, { cascadingDeleteCause: newCascadingDeleteCause })
|
||||
.enforcing()
|
||||
|
||||
.loadManyByFieldEqualingAsync(
|
||||
fieldName,
|
||||
entity.getField(associatedEntityLookupByField as any)
|
||||
@@ -767,7 +763,7 @@ export class DeleteMutator<
|
||||
} else {
|
||||
inboundReferenceEntities = await loaderFactory
|
||||
.forLoad(queryContext, { cascadingDeleteCause: newCascadingDeleteCause })
|
||||
.enforcing()
|
||||
|
||||
.loadManyByFieldEqualingAsync(fieldName, entity.getID());
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
import { Result, asyncResult, result } from '@expo/results';
|
||||
import invariant from 'invariant';
|
||||
|
||||
import { IEntityClass } from './Entity';
|
||||
import EntityConfiguration from './EntityConfiguration';
|
||||
import {
|
||||
FieldEqualityCondition,
|
||||
QuerySelectionModifiers,
|
||||
isSingleValueFieldEqualityCondition,
|
||||
} from './EntityDatabaseAdapter';
|
||||
import EntityPrivacyPolicy, { EntityPrivacyPolicyEvaluationContext } from './EntityPrivacyPolicy';
|
||||
import { EntityQueryContext } from './EntityQueryContext';
|
||||
import ReadonlyEntity from './ReadonlyEntity';
|
||||
import ViewerContext from './ViewerContext';
|
||||
import EntityInvalidFieldValueError from './errors/EntityInvalidFieldValueError';
|
||||
import EntityNotFoundError from './errors/EntityNotFoundError';
|
||||
import EntityDataManager from './internal/EntityDataManager';
|
||||
import IEntityMetricsAdapter from './metrics/IEntityMetricsAdapter';
|
||||
import { mapMap, mapMapAsync } from './utils/collections/maps';
|
||||
|
||||
/**
|
||||
* Result-based view on an entity loader. All loads through this loader will wrap their results in Result.
|
||||
*/
|
||||
export default class EntityResultLoader<
|
||||
TFields,
|
||||
TID extends NonNullable<TFields[TSelectedFields]>,
|
||||
TViewerContext extends ViewerContext,
|
||||
TEntity extends ReadonlyEntity<TFields, TID, TViewerContext, TSelectedFields>,
|
||||
TPrivacyPolicy extends EntityPrivacyPolicy<
|
||||
TFields,
|
||||
TID,
|
||||
TViewerContext,
|
||||
TEntity,
|
||||
TSelectedFields
|
||||
>,
|
||||
TSelectedFields extends keyof TFields
|
||||
> {
|
||||
constructor(
|
||||
private readonly viewerContext: TViewerContext,
|
||||
private readonly queryContext: EntityQueryContext,
|
||||
private readonly privacyPolicyEvaluationContext: EntityPrivacyPolicyEvaluationContext,
|
||||
private readonly entityConfiguration: EntityConfiguration<TFields>,
|
||||
private readonly entityClass: IEntityClass<
|
||||
TFields,
|
||||
TID,
|
||||
TViewerContext,
|
||||
TEntity,
|
||||
TPrivacyPolicy,
|
||||
TSelectedFields
|
||||
>,
|
||||
private readonly privacyPolicy: TPrivacyPolicy,
|
||||
private readonly dataManager: EntityDataManager<TFields>,
|
||||
protected readonly metricsAdapter: IEntityMetricsAdapter
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Load many entity results where fieldName is one of fieldValues.
|
||||
* @param fieldName - entity field being queried
|
||||
* @param fieldValues - fieldName field values being queried
|
||||
* @returns map from fieldValue to entity results that match the query for that fieldValue,
|
||||
* where result errors can be UnauthorizedError
|
||||
*/
|
||||
async loadManyByFieldEqualingManyAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly Result<TEntity>[]>> {
|
||||
this.validateFieldValues(fieldName, fieldValues);
|
||||
|
||||
const fieldValuesToFieldObjects = await this.dataManager.loadManyByFieldEqualingAsync(
|
||||
this.queryContext,
|
||||
fieldName,
|
||||
fieldValues
|
||||
);
|
||||
|
||||
return await this.constructAndAuthorizeEntitiesAsync(fieldValuesToFieldObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load many entity results where fieldName equals fieldValue.
|
||||
* @param fieldName - entity field being queried
|
||||
* @param fieldValue - fieldName field value being queried
|
||||
* @returns array of entity results that match the query for fieldValue, where result error can be UnauthorizedError
|
||||
*/
|
||||
async loadManyByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValue: NonNullable<TFields[N]>
|
||||
): Promise<readonly Result<TEntity>[]> {
|
||||
const entityResults = await this.loadManyByFieldEqualingManyAsync(fieldName, [fieldValue]);
|
||||
const entityResultsForFieldValue = entityResults.get(fieldValue);
|
||||
invariant(
|
||||
entityResultsForFieldValue !== undefined,
|
||||
`${fieldValue} should be guaranteed to be present in returned map of entities`
|
||||
);
|
||||
return entityResultsForFieldValue!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an entity result where fieldName equals fieldValue, or null if no entity exists.
|
||||
* @param uniqueFieldName - entity field being queried
|
||||
* @param fieldValue - uniqueFieldName field value being queried
|
||||
* @returns entity result where uniqueFieldName equals fieldValue, or null if no entity matches the condition.
|
||||
* @throws when multiple entities match the condition
|
||||
*/
|
||||
async loadByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
uniqueFieldName: N,
|
||||
fieldValue: NonNullable<TFields[N]>
|
||||
): Promise<Result<TEntity> | null> {
|
||||
const entityResults = await this.loadManyByFieldEqualingAsync(uniqueFieldName, fieldValue);
|
||||
invariant(
|
||||
entityResults.length <= 1,
|
||||
`loadByFieldEqualing: Multiple entities of type ${this.entityClass.name} found for ${uniqueFieldName}=${fieldValue}`
|
||||
);
|
||||
return entityResults[0] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads an entityresult by a specified ID.
|
||||
* @param id - ID of the entity
|
||||
* @returns entity result for matching ID, where result error can be UnauthorizedError or EntityNotFoundError.
|
||||
*/
|
||||
async loadByIDAsync(id: TID): Promise<Result<TEntity>> {
|
||||
const entityResults = await this.loadManyByIDsAsync([id]);
|
||||
const entityResult = entityResults.get(id);
|
||||
if (entityResult === undefined) {
|
||||
return result(
|
||||
new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id)
|
||||
);
|
||||
}
|
||||
return entityResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an entity result by a specified ID, or return null if non-existent.
|
||||
* @param id - ID of the entity
|
||||
* @returns entity result for matching ID, or null if no entity exists for ID.
|
||||
*/
|
||||
async loadByIDNullableAsync(id: TID): Promise<Result<TEntity> | null> {
|
||||
return await this.loadByFieldEqualingAsync(
|
||||
this.entityConfiguration.idField as TSelectedFields,
|
||||
id
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads many entity results for a list of IDs.
|
||||
* @param viewerContext - viewer context of loading user
|
||||
* @param ids - IDs of the entities to load
|
||||
* @returns map from ID to corresponding entity result, where result error can be UnauthorizedError or EntityNotFoundError.
|
||||
*/
|
||||
async loadManyByIDsAsync(ids: readonly TID[]): Promise<ReadonlyMap<TID, Result<TEntity>>> {
|
||||
const entityResults = (await this.loadManyByFieldEqualingManyAsync(
|
||||
this.entityConfiguration.idField as TSelectedFields,
|
||||
ids
|
||||
)) as ReadonlyMap<TID, readonly Result<TEntity>[]>;
|
||||
return mapMap(entityResults, (entityResultsForId, id) => {
|
||||
const entityResult = entityResultsForId[0];
|
||||
return (
|
||||
entityResult ??
|
||||
result(new EntityNotFoundError(this.entityClass, this.entityConfiguration.idField, id))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads many entity results matching the conjunction of WHERE clauses constructed from specified operands.
|
||||
* Entities loaded using this method are not batched or cached.
|
||||
*
|
||||
* @example
|
||||
* fieldEqualityOperands:
|
||||
* `[{fieldName: 'hello', fieldValue: 1}, {fieldName: 'world', fieldValues: [2, 3]}]`
|
||||
* Entities returned:
|
||||
* `WHERE hello = 1 AND world = ANY({2, 3})`
|
||||
*
|
||||
* @param fieldEqualityOperands - list of field equality WHERE clause operand specifications
|
||||
* @param querySelectionModifiers - limit, offset, and orderBy for the query
|
||||
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
||||
*/
|
||||
async loadManyByFieldEqualityConjunctionAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldEqualityOperands: FieldEqualityCondition<TFields, N>[],
|
||||
querySelectionModifiers: QuerySelectionModifiers<TFields> = {}
|
||||
): Promise<readonly Result<TEntity>[]> {
|
||||
for (const fieldEqualityOperand of fieldEqualityOperands) {
|
||||
const fieldValues = isSingleValueFieldEqualityCondition(fieldEqualityOperand)
|
||||
? [fieldEqualityOperand.fieldValue]
|
||||
: fieldEqualityOperand.fieldValues;
|
||||
this.validateFieldValues(fieldEqualityOperand.fieldName, fieldValues);
|
||||
}
|
||||
|
||||
const fieldObjects = await this.dataManager.loadManyByFieldEqualityConjunctionAsync(
|
||||
this.queryContext,
|
||||
fieldEqualityOperands,
|
||||
querySelectionModifiers
|
||||
);
|
||||
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
||||
return await Promise.all(
|
||||
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
||||
if (!uncheckedEntityResult.ok) {
|
||||
return uncheckedEntityResult;
|
||||
}
|
||||
return await asyncResult(
|
||||
this.privacyPolicy.authorizeReadAsync(
|
||||
this.viewerContext,
|
||||
this.queryContext,
|
||||
this.privacyPolicyEvaluationContext,
|
||||
uncheckedEntityResult.value,
|
||||
this.metricsAdapter
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads many entity results matching the raw WHERE clause. Corresponds to the knex `whereRaw` argument format.
|
||||
*
|
||||
* @remarks
|
||||
* Important notes:
|
||||
* - Fields in clause are database column names instead of transformed entity field names.
|
||||
* - Entities loaded using this method are not batched or cached.
|
||||
* - Not all database adapters implement the ability to execute this method of fetching entities.
|
||||
*
|
||||
* @example
|
||||
* rawWhereClause: `id = ?`
|
||||
* bindings: `[1]`
|
||||
* Entites returned `WHERE id = 1`
|
||||
*
|
||||
* {@link http://knexjs.org/#Builder-whereRaw}
|
||||
* {@link http://knexjs.org/#Raw-Bindings}
|
||||
*
|
||||
* @param rawWhereClause - parameterized SQL WHERE clause with positional binding placeholders or named binding placeholders
|
||||
* @param bindings - array of positional bindings or object of named bindings
|
||||
* @param querySelectionModifiers - limit, offset, and orderBy for the query
|
||||
* @returns array of entity results that match the query, where result error can be UnauthorizedError
|
||||
* @throws Error when rawWhereClause or bindings are invalid
|
||||
*
|
||||
* @deprecated prefer caching loaders
|
||||
*/
|
||||
async loadManyByRawWhereClauseAsync(
|
||||
rawWhereClause: string,
|
||||
bindings: any[] | object,
|
||||
querySelectionModifiers: QuerySelectionModifiers<TFields> = {}
|
||||
): Promise<readonly Result<TEntity>[]> {
|
||||
const fieldObjects = await this.dataManager.loadManyByRawWhereClauseAsync(
|
||||
this.queryContext,
|
||||
rawWhereClause,
|
||||
bindings,
|
||||
querySelectionModifiers
|
||||
);
|
||||
const uncheckedEntityResults = this.tryConstructEntities(fieldObjects);
|
||||
return await Promise.all(
|
||||
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
||||
if (!uncheckedEntityResult.ok) {
|
||||
return uncheckedEntityResult;
|
||||
}
|
||||
return await asyncResult(
|
||||
this.privacyPolicy.authorizeReadAsync(
|
||||
this.viewerContext,
|
||||
this.queryContext,
|
||||
this.privacyPolicyEvaluationContext,
|
||||
uncheckedEntityResult.value,
|
||||
this.metricsAdapter
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private tryConstructEntities(fieldsObjects: readonly TFields[]): readonly Result<TEntity>[] {
|
||||
return fieldsObjects.map((fieldsObject) => {
|
||||
try {
|
||||
return result(new this.entityClass(this.viewerContext, fieldsObject));
|
||||
} catch (e) {
|
||||
if (!(e instanceof Error)) {
|
||||
throw e;
|
||||
}
|
||||
return result(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct and authorize entities from fields map, returning error results for entities that fail
|
||||
* to construct or fail to authorize.
|
||||
*
|
||||
* @param map - map from an arbitrary key type to an array of entity field objects
|
||||
*/
|
||||
public async constructAndAuthorizeEntitiesAsync<K>(
|
||||
map: ReadonlyMap<K, readonly Readonly<TFields>[]>
|
||||
): Promise<ReadonlyMap<K, readonly Result<TEntity>[]>> {
|
||||
const uncheckedEntityResultsMap = mapMap(map, (fieldObjects) =>
|
||||
this.tryConstructEntities(fieldObjects)
|
||||
);
|
||||
return await mapMapAsync(uncheckedEntityResultsMap, async (uncheckedEntityResults) => {
|
||||
return await Promise.all(
|
||||
uncheckedEntityResults.map(async (uncheckedEntityResult) => {
|
||||
if (!uncheckedEntityResult.ok) {
|
||||
return uncheckedEntityResult;
|
||||
}
|
||||
return await asyncResult(
|
||||
this.privacyPolicy.authorizeReadAsync(
|
||||
this.viewerContext,
|
||||
this.queryContext,
|
||||
this.privacyPolicyEvaluationContext,
|
||||
uncheckedEntityResult.value,
|
||||
this.metricsAdapter
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private validateFieldValues<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
fieldName: N,
|
||||
fieldValues: readonly TFields[N][]
|
||||
): void {
|
||||
const fieldDefinition = this.entityConfiguration.schema.get(fieldName);
|
||||
invariant(fieldDefinition, `must have field definition for field = ${fieldName}`);
|
||||
for (const fieldValue of fieldValues) {
|
||||
const isInputValid = fieldDefinition.validateInputValue(fieldValue);
|
||||
if (!isInputValid) {
|
||||
throw new EntityInvalidFieldValueError(this.entityClass, fieldName, fieldValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
import { result } from '@expo/results';
|
||||
import { mock, instance, when, anything } from 'ts-mockito';
|
||||
|
||||
import EnforcingEntityLoader from '../EnforcingEntityLoader';
|
||||
import EntityLoader from '../EntityLoader';
|
||||
import EnforcingEntityLoader from '../EntityResultLoader';
|
||||
|
||||
describe(EnforcingEntityLoader, () => {
|
||||
describe('loadManyByFieldEqualingManyAsync', () => {
|
||||
it('throws when result is unsuccessful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(entityLoaderMock.loadManyByFieldEqualingManyAsync(anything(), anything())).thenResolve(
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadManyByFieldEqualingManyAsync(anything(), anything())
|
||||
).thenResolve(
|
||||
new Map(
|
||||
Object.entries({
|
||||
hello: [result(rejection)],
|
||||
@@ -26,7 +28,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns value when result is successful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(entityLoaderMock.loadManyByFieldEqualingManyAsync(anything(), anything())).thenResolve(
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadManyByFieldEqualingManyAsync(anything(), anything())
|
||||
).thenResolve(
|
||||
new Map(
|
||||
Object.entries({
|
||||
hello: [result(resolved)],
|
||||
@@ -51,9 +55,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('throws when result is unsuccessful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(entityLoaderMock.loadManyByFieldEqualingAsync(anything(), anything())).thenResolve([
|
||||
result(rejection),
|
||||
]);
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadManyByFieldEqualingAsync(anything(), anything())
|
||||
).thenResolve([result(rejection)]);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(
|
||||
@@ -64,9 +68,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns value when result is successful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(entityLoaderMock.loadManyByFieldEqualingAsync(anything(), anything())).thenResolve([
|
||||
result(resolved),
|
||||
]);
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadManyByFieldEqualingAsync(anything(), anything())
|
||||
).thenResolve([result(resolved)]);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(
|
||||
@@ -79,9 +83,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('throws when result is unsuccessful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(entityLoaderMock.loadByFieldEqualingAsync(anything(), anything())).thenResolve(
|
||||
result(rejection)
|
||||
);
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadByFieldEqualingAsync(anything(), anything())
|
||||
).thenResolve(result(rejection));
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(
|
||||
@@ -92,9 +96,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns value when result is successful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(entityLoaderMock.loadByFieldEqualingAsync(anything(), anything())).thenResolve(
|
||||
result(resolved)
|
||||
);
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadByFieldEqualingAsync(anything(), anything())
|
||||
).thenResolve(result(resolved));
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(
|
||||
@@ -105,9 +109,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns null when result is successful and no entity is found', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = null;
|
||||
when(entityLoaderMock.loadByFieldEqualingAsync(anything(), anything())).thenResolve(
|
||||
result(resolved)
|
||||
);
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadByFieldEqualingAsync(anything(), anything())
|
||||
).thenResolve(result(resolved));
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(
|
||||
@@ -118,9 +122,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('throws when multiple matching entities are found', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const multipleEntitiesError = new Error();
|
||||
when(entityLoaderMock.loadByFieldEqualingAsync(anything(), anything())).thenReject(
|
||||
multipleEntitiesError
|
||||
);
|
||||
when(
|
||||
entityLoaderMock.resultLoader.loadByFieldEqualingAsync(anything(), anything())
|
||||
).thenReject(multipleEntitiesError);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(
|
||||
@@ -133,7 +137,7 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('throws when result is unsuccessful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(entityLoaderMock.loadByIDAsync(anything())).thenResolve(result(rejection));
|
||||
when(entityLoaderMock.resultLoader.loadByIDAsync(anything())).thenResolve(result(rejection));
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(enforcingEntityLoader.loadByIDAsync(anything())).rejects.toThrow(rejection);
|
||||
@@ -142,7 +146,7 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns value when result is successful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(entityLoaderMock.loadByIDAsync(anything())).thenResolve(result(resolved));
|
||||
when(entityLoaderMock.resultLoader.loadByIDAsync(anything())).thenResolve(result(resolved));
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(enforcingEntityLoader.loadByIDAsync(anything())).resolves.toEqual(resolved);
|
||||
@@ -153,7 +157,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('throws when result is unsuccessful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(entityLoaderMock.loadByIDNullableAsync(anything())).thenResolve(result(rejection));
|
||||
when(entityLoaderMock.resultLoader.loadByIDNullableAsync(anything())).thenResolve(
|
||||
result(rejection)
|
||||
);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(enforcingEntityLoader.loadByIDNullableAsync(anything())).rejects.toThrow(
|
||||
@@ -164,7 +170,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns value when result is successful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(entityLoaderMock.loadByIDNullableAsync(anything())).thenResolve(result(resolved));
|
||||
when(entityLoaderMock.resultLoader.loadByIDNullableAsync(anything())).thenResolve(
|
||||
result(resolved)
|
||||
);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(enforcingEntityLoader.loadByIDNullableAsync(anything())).resolves.toEqual(
|
||||
@@ -175,7 +183,9 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns null when non-existent object', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = null;
|
||||
when(entityLoaderMock.loadByIDNullableAsync(anything())).thenResolve(result(resolved));
|
||||
when(entityLoaderMock.resultLoader.loadByIDNullableAsync(anything())).thenResolve(
|
||||
result(resolved)
|
||||
);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
await expect(enforcingEntityLoader.loadByIDNullableAsync(anything())).resolves.toEqual(
|
||||
@@ -188,7 +198,7 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('throws when result is unsuccessful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(entityLoaderMock.loadManyByIDsAsync(anything())).thenResolve(
|
||||
when(entityLoaderMock.resultLoader.loadManyByIDsAsync(anything())).thenResolve(
|
||||
new Map(
|
||||
Object.entries({
|
||||
hello: result(rejection),
|
||||
@@ -203,7 +213,7 @@ describe(EnforcingEntityLoader, () => {
|
||||
it('returns value when result is successful', async () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(entityLoaderMock.loadManyByIDsAsync(anything())).thenResolve(
|
||||
when(entityLoaderMock.resultLoader.loadManyByIDsAsync(anything())).thenResolve(
|
||||
new Map(
|
||||
Object.entries({
|
||||
hello: result(resolved),
|
||||
@@ -227,7 +237,10 @@ describe(EnforcingEntityLoader, () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(
|
||||
entityLoaderMock.loadManyByFieldEqualityConjunctionAsync(anything(), anything())
|
||||
entityLoaderMock.resultLoader.loadManyByFieldEqualityConjunctionAsync(
|
||||
anything(),
|
||||
anything()
|
||||
)
|
||||
).thenResolve([result(rejection)]);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
@@ -240,7 +253,10 @@ describe(EnforcingEntityLoader, () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(
|
||||
entityLoaderMock.loadManyByFieldEqualityConjunctionAsync(anything(), anything())
|
||||
entityLoaderMock.resultLoader.loadManyByFieldEqualityConjunctionAsync(
|
||||
anything(),
|
||||
anything()
|
||||
)
|
||||
).thenResolve([result(resolved)]);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
@@ -255,7 +271,11 @@ describe(EnforcingEntityLoader, () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const rejection = new Error();
|
||||
when(
|
||||
entityLoaderMock.loadManyByRawWhereClauseAsync(anything(), anything(), anything())
|
||||
entityLoaderMock.resultLoader.loadManyByRawWhereClauseAsync(
|
||||
anything(),
|
||||
anything(),
|
||||
anything()
|
||||
)
|
||||
).thenResolve([result(rejection)]);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
@@ -268,7 +288,11 @@ describe(EnforcingEntityLoader, () => {
|
||||
const entityLoaderMock = mock<EntityLoader<any, any, any, any, any, any>>(EntityLoader);
|
||||
const resolved = {};
|
||||
when(
|
||||
entityLoaderMock.loadManyByRawWhereClauseAsync(anything(), anything(), anything())
|
||||
entityLoaderMock.resultLoader.loadManyByRawWhereClauseAsync(
|
||||
anything(),
|
||||
anything(),
|
||||
anything()
|
||||
)
|
||||
).thenResolve([result(resolved)]);
|
||||
const entityLoader = instance(entityLoaderMock);
|
||||
const enforcingEntityLoader = new EnforcingEntityLoader(entityLoader);
|
||||
|
||||
@@ -129,15 +129,15 @@ it('runs through a common workflow', async () => {
|
||||
|
||||
// check that two people can't read each others data
|
||||
await expect(
|
||||
enforceAsyncResult(BlahEntity.loader(vc1).loadByIDAsync(blahOwner2.getID()))
|
||||
enforceAsyncResult(BlahEntity.loader(vc1).resultLoader.loadByIDAsync(blahOwner2.getID()))
|
||||
).rejects.toBeInstanceOf(EntityNotAuthorizedError);
|
||||
await expect(
|
||||
enforceAsyncResult(BlahEntity.loader(vc2).loadByIDAsync(blahOwner1.getID()))
|
||||
enforceAsyncResult(BlahEntity.loader(vc2).resultLoader.loadByIDAsync(blahOwner1.getID()))
|
||||
).rejects.toBeInstanceOf(EntityNotAuthorizedError);
|
||||
|
||||
// check that all of owner 1's objects can be loaded
|
||||
const results = await enforceResultsAsync(
|
||||
BlahEntity.loader(vc1).loadManyByFieldEqualingAsync('ownerID', vc1.getUserID()!)
|
||||
BlahEntity.loader(vc1).resultLoader.loadManyByFieldEqualingAsync('ownerID', vc1.getUserID()!)
|
||||
);
|
||||
expect(results).toHaveLength(2);
|
||||
|
||||
@@ -149,7 +149,10 @@ it('runs through a common workflow', async () => {
|
||||
).rejects.toBeInstanceOf(EntityNotAuthorizedError);
|
||||
|
||||
// check that empty load many returns nothing
|
||||
const results2 = await BlahEntity.loader(vc1).loadManyByFieldEqualingManyAsync('ownerID', []);
|
||||
const results2 = await BlahEntity.loader(vc1).resultLoader.loadManyByFieldEqualingManyAsync(
|
||||
'ownerID',
|
||||
[]
|
||||
);
|
||||
for (const value in results2.values) {
|
||||
expect(value).toHaveLength(0);
|
||||
}
|
||||
|
||||
@@ -374,13 +374,13 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID())
|
||||
ChildEntity.loader(viewerContext).loadByIDNullableAsync(child.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID())
|
||||
GrandChildEntity.loader(viewerContext).loadByIDNullableAsync(grandchild.getID())
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
privacyPolicyEvaluationRecords.shouldRecord = true;
|
||||
@@ -388,13 +388,13 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
privacyPolicyEvaluationRecords.shouldRecord = false;
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID())
|
||||
ChildEntity.loader(viewerContext).loadByIDNullableAsync(child.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID())
|
||||
GrandChildEntity.loader(viewerContext).loadByIDNullableAsync(grandchild.getID())
|
||||
).resolves.toBeNull();
|
||||
|
||||
// two calls for each trigger, one beforeDelete, one afterDelete
|
||||
@@ -488,13 +488,13 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID())
|
||||
ChildEntity.loader(viewerContext).loadByIDNullableAsync(child.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID())
|
||||
GrandChildEntity.loader(viewerContext).loadByIDNullableAsync(grandchild.getID())
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
privacyPolicyEvaluationRecords.shouldRecord = true;
|
||||
@@ -502,16 +502,16 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
privacyPolicyEvaluationRecords.shouldRecord = false;
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.toBeNull();
|
||||
|
||||
const loadedChild = await ChildEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByIDAsync(child.getID());
|
||||
expect(loadedChild.getField('parent_id')).toBeNull();
|
||||
|
||||
const loadedGrandchild = await GrandChildEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByIDAsync(grandchild.getID());
|
||||
expect(loadedGrandchild.getField('parent_id')).toEqual(loadedChild.getID());
|
||||
|
||||
@@ -595,16 +595,16 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
ChildEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByFieldEqualingAsync('parent_id', parent.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
GrandChildEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByFieldEqualingAsync('parent_id', child.getID())
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
@@ -639,13 +639,13 @@ describe('EntityMutator.processEntityDeletionForInboundEdgesAsync', () => {
|
||||
expect(grandChildCachedAfter.get(child.getID())?.status).toEqual(CacheStatus.MISS);
|
||||
|
||||
await expect(
|
||||
ParentEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parent.getID())
|
||||
ParentEntity.loader(viewerContext).loadByIDNullableAsync(parent.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
ChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(child.getID())
|
||||
ChildEntity.loader(viewerContext).loadByIDNullableAsync(child.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
GrandChildEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(grandchild.getID())
|
||||
GrandChildEntity.loader(viewerContext).loadByIDNullableAsync(grandchild.getID())
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
// two calls for each trigger, one beforeDelete, one afterDelete
|
||||
|
||||
@@ -165,14 +165,14 @@ describe(EntityLoader, () => {
|
||||
|
||||
let capturedThrownThing1: any;
|
||||
try {
|
||||
await entityLoader.loadByIDAsync(ID_SENTINEL_THROW_LITERAL);
|
||||
await entityLoader.resultLoader.loadByIDAsync(ID_SENTINEL_THROW_LITERAL);
|
||||
} catch (e) {
|
||||
capturedThrownThing1 = e;
|
||||
}
|
||||
expect(capturedThrownThing1).not.toBeInstanceOf(Error);
|
||||
expect(capturedThrownThing1).toEqual('hello');
|
||||
|
||||
const result = await entityLoader.loadByIDAsync(ID_SENTINEL_THROW_ERROR);
|
||||
const result = await entityLoader.resultLoader.loadByIDAsync(ID_SENTINEL_THROW_ERROR);
|
||||
expect(result.ok).toBe(false);
|
||||
expect(result.enforceError().message).toEqual('world');
|
||||
});
|
||||
|
||||
@@ -78,36 +78,39 @@ describe(EntityLoader, () => {
|
||||
dataManager,
|
||||
metricsAdapter
|
||||
);
|
||||
const entity = await enforceAsyncResult(entityLoader.loadByIDAsync(id1));
|
||||
const entity = await enforceAsyncResult(entityLoader.resultLoader.loadByIDAsync(id1));
|
||||
expect(entity.getID()).toEqual(id1);
|
||||
expect(entity.getField('dateField')).toEqual(dateToInsert);
|
||||
|
||||
const entities = await enforceResultsAsync(
|
||||
entityLoader.loadManyByFieldEqualingAsync('stringField', 'huh')
|
||||
entityLoader.resultLoader.loadManyByFieldEqualingAsync('stringField', 'huh')
|
||||
);
|
||||
expect(entities.map((m) => m.getID())).toEqual([id1, id2]);
|
||||
|
||||
const entityResultNumber3 = await entityLoader.loadByFieldEqualingAsync('intField', 3);
|
||||
const entityResultNumber3 = await entityLoader.resultLoader.loadByFieldEqualingAsync('intField', 3);
|
||||
expect(entityResultNumber3).not.toBeNull();
|
||||
expect(entityResultNumber3!.enforceValue().getID()).toEqual(id2);
|
||||
|
||||
const entityResultNumber4 = await entityLoader.loadByFieldEqualingAsync('intField', 4);
|
||||
const entityResultNumber4 = await entityLoader.resultLoader.loadByFieldEqualingAsync('intField', 4);
|
||||
expect(entityResultNumber4).toBeNull();
|
||||
|
||||
const entityResultDuplicateValues = await entityLoader
|
||||
.enforcing()
|
||||
.loadManyByFieldEqualingManyAsync('stringField', ['huh', 'huh']);
|
||||
const entityResultDuplicateValues = await entityLoader.loadManyByFieldEqualingManyAsync(
|
||||
'stringField',
|
||||
['huh', 'huh']
|
||||
);
|
||||
expect(entityResultDuplicateValues.size).toBe(1);
|
||||
expect(entityResultDuplicateValues.get('huh')?.map((m) => m.getID())).toEqual([id1, id2]);
|
||||
|
||||
await expect(entityLoader.loadByFieldEqualingAsync('stringField', 'huh')).rejects.toThrowError(
|
||||
await expect(
|
||||
entityLoader.resultLoader.loadByFieldEqualingAsync('stringField', 'huh')
|
||||
).rejects.toThrowError(
|
||||
'loadByFieldEqualing: Multiple entities of type TestEntity found for stringField=huh'
|
||||
);
|
||||
|
||||
await expect(entityLoader.loadByIDNullableAsync(uuidv4())).resolves.toBeNull();
|
||||
await expect(entityLoader.loadByIDNullableAsync(id1)).resolves.not.toBeNull();
|
||||
await expect(entityLoader.resultLoader.loadByIDNullableAsync(uuidv4())).resolves.toBeNull();
|
||||
await expect(entityLoader.resultLoader.loadByIDNullableAsync(id1)).resolves.not.toBeNull();
|
||||
|
||||
await expect(entityLoader.loadByIDAsync('not-a-uuid')).rejects.toThrowError(
|
||||
await expect(entityLoader.resultLoader.loadByIDAsync('not-a-uuid')).rejects.toThrowError(
|
||||
'Entity field not valid: TestEntity (customIdField = not-a-uuid)'
|
||||
);
|
||||
});
|
||||
@@ -181,7 +184,7 @@ describe(EntityLoader, () => {
|
||||
metricsAdapter
|
||||
);
|
||||
const entities = await enforceResultsAsync(
|
||||
entityLoader.loadManyByFieldEqualityConjunctionAsync([
|
||||
entityLoader.resultLoader.loadManyByFieldEqualityConjunctionAsync([
|
||||
{
|
||||
fieldName: 'stringField',
|
||||
fieldValue: 'huh',
|
||||
@@ -204,7 +207,7 @@ describe(EntityLoader, () => {
|
||||
).twice();
|
||||
|
||||
await expect(
|
||||
entityLoader.loadManyByFieldEqualityConjunctionAsync([
|
||||
entityLoader.resultLoader.loadManyByFieldEqualityConjunctionAsync([
|
||||
{ fieldName: 'customIdField', fieldValue: 'not-a-uuid' },
|
||||
])
|
||||
).rejects.toThrowError('Entity field not valid: TestEntity (customIdField = not-a-uuid)');
|
||||
@@ -261,7 +264,7 @@ describe(EntityLoader, () => {
|
||||
dataManager,
|
||||
metricsAdapter
|
||||
);
|
||||
const entity = await enforceAsyncResult(entityLoader.loadByIDAsync(id1));
|
||||
const entity = await enforceAsyncResult(entityLoader.resultLoader.loadByIDAsync(id1));
|
||||
verify(
|
||||
spiedPrivacyPolicy.authorizeReadAsync(
|
||||
viewerContext,
|
||||
@@ -395,7 +398,7 @@ describe(EntityLoader, () => {
|
||||
metricsAdapter
|
||||
);
|
||||
|
||||
const entityResult = await entityLoader.loadByIDAsync(id1);
|
||||
const entityResult = await entityLoader.resultLoader.loadByIDAsync(id1);
|
||||
expect(entityResult.ok).toBe(false);
|
||||
expect(entityResult.reason).toEqual(rejectionError);
|
||||
expect(entityResult.value).toBe(undefined);
|
||||
@@ -430,21 +433,21 @@ describe(EntityLoader, () => {
|
||||
|
||||
const loadByValue = uuidv4();
|
||||
|
||||
await expect(entityLoader.resultLoader.loadByIDAsync(loadByValue)).rejects.toEqual(error);
|
||||
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.resultLoader.loadManyByIDsAsync([loadByValue])).rejects.toEqual(error);
|
||||
await expect(entityLoader.resultLoader.loadManyByIDsAsync([loadByValue])).rejects.toEqual(error);
|
||||
await expect(
|
||||
entityLoader.resultLoader.loadManyByFieldEqualingAsync('customIdField', loadByValue)
|
||||
).rejects.toEqual(error);
|
||||
await expect(
|
||||
entityLoader.loadManyByFieldEqualingAsync('customIdField', loadByValue)
|
||||
).rejects.toEqual(error);
|
||||
await expect(
|
||||
entityLoader.enforcing().loadManyByFieldEqualingAsync('customIdField', loadByValue)
|
||||
entityLoader.resultLoader.loadManyByFieldEqualingManyAsync('customIdField', [loadByValue])
|
||||
).rejects.toEqual(error);
|
||||
await expect(
|
||||
entityLoader.loadManyByFieldEqualingManyAsync('customIdField', [loadByValue])
|
||||
).rejects.toEqual(error);
|
||||
await expect(
|
||||
entityLoader.enforcing().loadManyByFieldEqualingManyAsync('customIdField', [loadByValue])
|
||||
).rejects.toEqual(error);
|
||||
});
|
||||
});
|
||||
@@ -78,7 +78,7 @@ class TestNonTransactionalMutationTrigger extends EntityNonTransactionalMutation
|
||||
): Promise<void> {
|
||||
if (mutationInfo.type === EntityMutationType.DELETE) {
|
||||
const entityLoaded = await BlahEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByIDNullableAsync(entity.getID());
|
||||
if (entityLoaded) {
|
||||
throw new Error(
|
||||
@@ -97,7 +97,7 @@ describe('EntityMutator', () => {
|
||||
// put it in cache
|
||||
const entity = await BlahEntity.creator(viewerContext).enforceCreateAsync();
|
||||
const entityLoaded = await BlahEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByIDAsync(entity.getID());
|
||||
|
||||
await BlahEntity.enforceDeleteAsync(entityLoaded);
|
||||
|
||||
@@ -530,7 +530,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id2)
|
||||
.resultLoader.loadByIDAsync(id2)
|
||||
);
|
||||
|
||||
const updatedEntity = await entityMutatorFactory
|
||||
@@ -545,7 +545,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const reloadedEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id2)
|
||||
.resultLoader.loadByIDAsync(id2)
|
||||
);
|
||||
expect(reloadedEntity.getAllFields()).toMatchObject(updatedEntity.getAllFields());
|
||||
});
|
||||
@@ -582,7 +582,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id2)
|
||||
.resultLoader.loadByIDAsync(id2)
|
||||
);
|
||||
|
||||
await entityMutatorFactory
|
||||
@@ -633,7 +633,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id2)
|
||||
.resultLoader.loadByIDAsync(id2)
|
||||
);
|
||||
|
||||
await entityMutatorFactory
|
||||
@@ -687,7 +687,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id2)
|
||||
.resultLoader.loadByIDAsync(id2)
|
||||
);
|
||||
|
||||
await entityMutatorFactory
|
||||
@@ -723,7 +723,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id1)
|
||||
.resultLoader.loadByIDAsync(id1)
|
||||
);
|
||||
expect(existingEntity).toBeTruthy();
|
||||
|
||||
@@ -735,7 +735,7 @@ describe(EntityMutatorFactory, () => {
|
||||
enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id1)
|
||||
.resultLoader.loadByIDAsync(id1)
|
||||
)
|
||||
).rejects.toBeInstanceOf(Error);
|
||||
});
|
||||
@@ -763,7 +763,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id1)
|
||||
.resultLoader.loadByIDAsync(id1)
|
||||
);
|
||||
|
||||
await entityMutatorFactory
|
||||
@@ -804,7 +804,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id1)
|
||||
.resultLoader.loadByIDAsync(id1)
|
||||
);
|
||||
|
||||
await entityMutatorFactory
|
||||
@@ -849,7 +849,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const existingEntity = await enforceAsyncResult(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadByIDAsync(id1)
|
||||
.resultLoader.loadByIDAsync(id1)
|
||||
);
|
||||
|
||||
await entityMutatorFactory
|
||||
@@ -882,7 +882,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const entites1 = await enforceResultsAsync(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadManyByFieldEqualingAsync('stringField', 'huh')
|
||||
.resultLoader.loadManyByFieldEqualingAsync('stringField', 'huh')
|
||||
);
|
||||
expect(entites1).toHaveLength(1);
|
||||
|
||||
@@ -896,7 +896,7 @@ describe(EntityMutatorFactory, () => {
|
||||
const entities2 = await enforceResultsAsync(
|
||||
entityLoaderFactory
|
||||
.forLoad(viewerContext, queryContext, privacyPolicyEvaluationContext)
|
||||
.loadManyByFieldEqualingAsync('stringField', 'huh')
|
||||
.resultLoader.loadManyByFieldEqualingAsync('stringField', 'huh')
|
||||
);
|
||||
expect(entities2).toHaveLength(2);
|
||||
});
|
||||
|
||||
@@ -98,25 +98,25 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(parentCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subSubCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
await CategoryEntity.enforceDeleteAsync(parentCategory);
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(parentCategory.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subCategory.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subSubCategory.getID())
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
|
||||
@@ -137,10 +137,10 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE', () => {
|
||||
await CategoryEntity.enforceDeleteAsync(categoryA);
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(categoryA.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(categoryA.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(categoryB.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(categoryB.getID())
|
||||
).resolves.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -161,29 +161,29 @@ describe('EntityEdgeDeletionBehavior.SET_NULL', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(parentCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subSubCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
await CategoryEntity.enforceDeleteAsync(parentCategory);
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(parentCategory.getID())
|
||||
).resolves.toBeNull();
|
||||
|
||||
const loadedSubCategory = await CategoryEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(subCategory.getID());
|
||||
const loadedSubCategory = await CategoryEntity.loader(viewerContext).loadByIDAsync(
|
||||
subCategory.getID()
|
||||
);
|
||||
expect(loadedSubCategory.getField('parent_category_id')).toBeNull();
|
||||
|
||||
const loadedSubSubCategory = await CategoryEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(subSubCategory.getID());
|
||||
const loadedSubSubCategory = await CategoryEntity.loader(viewerContext).loadByIDAsync(
|
||||
subSubCategory.getID()
|
||||
);
|
||||
expect(loadedSubSubCategory.getField('parent_category_id')).not.toBeNull();
|
||||
});
|
||||
|
||||
@@ -203,9 +203,9 @@ describe('EntityEdgeDeletionBehavior.SET_NULL', () => {
|
||||
|
||||
await CategoryEntity.enforceDeleteAsync(categoryA);
|
||||
|
||||
const loadedCategoryB = await CategoryEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByIDAsync(categoryB.getID());
|
||||
const loadedCategoryB = await CategoryEntity.loader(viewerContext).loadByIDAsync(
|
||||
categoryB.getID()
|
||||
);
|
||||
expect(loadedCategoryB.getField('parent_category_id')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -228,17 +228,19 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(parentCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('parent_category_id', parentCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByFieldEqualingAsync(
|
||||
'parent_category_id',
|
||||
parentCategory.getID()
|
||||
)
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('parent_category_id', subCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByFieldEqualingAsync(
|
||||
'parent_category_id',
|
||||
subCategory.getID()
|
||||
)
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
const categoryCacheAdapter = viewerContext.getViewerScopedEntityCompanionForClass(
|
||||
@@ -271,13 +273,13 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => {
|
||||
expect(subSubCategoryCachedAfter.get(subCategory.getID())?.status).toEqual(CacheStatus.MISS);
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(parentCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(parentCategory.getID())
|
||||
).resolves.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext).enforcing().loadByIDNullableAsync(subSubCategory.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByIDNullableAsync(subSubCategory.getID())
|
||||
).resolves.not.toBeNull();
|
||||
});
|
||||
|
||||
@@ -298,14 +300,16 @@ describe('EntityEdgeDeletionBehavior.CASCADE_DELETE_INVALIDATE_CACHE', () => {
|
||||
.enforceUpdateAsync();
|
||||
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('parent_category_id', categoryA.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByFieldEqualingAsync(
|
||||
'parent_category_id',
|
||||
categoryA.getID()
|
||||
)
|
||||
).resolves.not.toBeNull();
|
||||
await expect(
|
||||
CategoryEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
.loadByFieldEqualingAsync('parent_category_id', categoryB.getID())
|
||||
CategoryEntity.loader(viewerContext).loadByFieldEqualingAsync(
|
||||
'parent_category_id',
|
||||
categoryB.getID()
|
||||
)
|
||||
).resolves.not.toBeNull();
|
||||
|
||||
const categoryCacheAdapter = viewerContext.getViewerScopedEntityCompanionForClass(
|
||||
|
||||
@@ -28,17 +28,16 @@ describe('Two entities backed by the same table', () => {
|
||||
expect(two).toBeInstanceOf(TwoTestEntity);
|
||||
|
||||
await expect(
|
||||
TwoTestEntity.loader(viewerContext).enforcing().loadByIDAsync(one.getID())
|
||||
TwoTestEntity.loader(viewerContext).loadByIDAsync(one.getID())
|
||||
).rejects.toThrowError('TwoTestEntity must be instantiated with two data');
|
||||
|
||||
await expect(
|
||||
OneTestEntity.loader(viewerContext).enforcing().loadByIDAsync(two.getID())
|
||||
OneTestEntity.loader(viewerContext).loadByIDAsync(two.getID())
|
||||
).rejects.toThrowError('OneTestEntity must be instantiated with one data');
|
||||
|
||||
const manyResults = await OneTestEntity.loader(viewerContext).loadManyByFieldEqualingAsync(
|
||||
'common_other_field',
|
||||
'wat'
|
||||
);
|
||||
const manyResults = await OneTestEntity.loader(
|
||||
viewerContext
|
||||
).resultLoader.loadManyByFieldEqualingAsync('common_other_field', 'wat');
|
||||
const successfulManyResults = successfulResults(manyResults);
|
||||
const failedManyResults = failedResults(manyResults);
|
||||
|
||||
@@ -52,7 +51,7 @@ describe('Two entities backed by the same table', () => {
|
||||
|
||||
const fieldEqualityConjunctionResults = await OneTestEntity.loader(
|
||||
viewerContext
|
||||
).loadManyByFieldEqualityConjunctionAsync([
|
||||
).resultLoader.loadManyByFieldEqualityConjunctionAsync([
|
||||
{
|
||||
fieldName: 'common_other_field',
|
||||
fieldValue: 'wat',
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('Two entities backed by the same table', () => {
|
||||
expect(entity1).toBeInstanceOf(OneTestEntity);
|
||||
|
||||
const entity2 = await TwoTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByIDAsync(entity1.getID());
|
||||
expect(entity2).toBeInstanceOf(TwoTestEntity);
|
||||
|
||||
@@ -33,7 +33,7 @@ describe('Two entities backed by the same table', () => {
|
||||
});
|
||||
|
||||
const loaded1 = await OneTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByIDAsync(entity1.getID());
|
||||
expect(loaded1.getAllFields()).toMatchObject({
|
||||
id: updated2.getID(),
|
||||
@@ -51,7 +51,7 @@ describe('Two entities backed by the same table', () => {
|
||||
.enforceCreateAsync();
|
||||
|
||||
const loadedEntity = await TwoTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByFieldEqualingAsync('other_field', 'huh');
|
||||
expect(loadedEntity?.getAllFields()).toMatchObject({
|
||||
id: entity.getID(),
|
||||
@@ -60,12 +60,12 @@ describe('Two entities backed by the same table', () => {
|
||||
});
|
||||
|
||||
const loaded1 = await OneTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByIDAsync(entity.getID());
|
||||
await OneTestEntity.updater(loaded1).setField('fake_field', 'world').enforceUpdateAsync();
|
||||
|
||||
const loaded2 = await TwoTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByFieldEqualingAsync('other_field', 'huh');
|
||||
expect(loaded2?.getAllFields()).toMatchObject({
|
||||
id: entity.getID(),
|
||||
@@ -74,7 +74,7 @@ describe('Two entities backed by the same table', () => {
|
||||
});
|
||||
|
||||
const loaded22 = await TwoTestEntity.loader(viewerContext)
|
||||
.enforcing()
|
||||
|
||||
.loadByFieldEqualingAsync('fake_field', 'world');
|
||||
expect(loaded22?.getAllFields()).toMatchObject({
|
||||
id: entity.getID(),
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
export { default as ComposedEntityCacheAdapter } from './ComposedEntityCacheAdapter';
|
||||
export { default as ComposedSecondaryEntityCache } from './ComposedSecondaryEntityCache';
|
||||
export { default as GenericSecondaryEntityCache } from './GenericSecondaryEntityCache';
|
||||
export { default as EnforcingEntityLoader } from './EnforcingEntityLoader';
|
||||
export { default as EntityResultLoader } from './EntityResultLoader';
|
||||
export { default as Entity } from './Entity';
|
||||
export * from './Entity';
|
||||
export { default as EntityAssociationLoader } from './EntityAssociationLoader';
|
||||
|
||||
Reference in new issue
Block a user