mirror of
https://github.com/expo/entity.git
synced 2026-11-13 20:28:06 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa4e2484c9 |
No files matched your search
@@ -3,17 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix null handling in load paths (try 2) ([#96](https://github.com/expo/entity/issues/96)) ([d960329](https://github.com/expo/entity/commit/d9603298e0cc5c3c2b0cca959876e9195555fb57))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -4,5 +4,5 @@
|
||||
],
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"version": "0.10.0"
|
||||
"version": "0.9.1"
|
||||
}
|
||||
@@ -3,14 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
|
||||
|
||||
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
|
||||
|
||||
**Note:** Version bump only for package @expo/entity-cache-adapter-redis
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/entity-cache-adapter-redis",
|
||||
"version": "0.10.0",
|
||||
"version": "0.9.1",
|
||||
"description": "Redis cache adapter for @expo/entity",
|
||||
"files": [
|
||||
"build",
|
||||
@@ -32,6 +32,6 @@
|
||||
"ioredis": "^4.17.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/entity": "^0.10.0"
|
||||
"@expo/entity": "^0.9.1"
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
|
||||
|
||||
**Note:** Version bump only for package @expo/entity-database-adapter-knex
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
|
||||
|
||||
**Note:** Version bump only for package @expo/entity-database-adapter-knex
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/entity-database-adapter-knex",
|
||||
"version": "0.10.0",
|
||||
"version": "0.9.1",
|
||||
"description": "Knex database adapter for @expo/entity",
|
||||
"files": [
|
||||
"build",
|
||||
@@ -32,6 +32,6 @@
|
||||
"knex": "^0.21.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/entity": "^0.10.0"
|
||||
"@expo/entity": "^0.9.1"
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
|
||||
|
||||
**Note:** Version bump only for package @expo/entity-example
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
|
||||
|
||||
**Note:** Version bump only for package @expo/entity-example
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@expo/entity-example",
|
||||
"private": true,
|
||||
"version": "0.10.0",
|
||||
"version": "0.9.1",
|
||||
"description": "An example integration of the @expo/entity framework",
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
@@ -22,7 +22,7 @@
|
||||
"author": "Expo",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@expo/entity": "^0.10.0",
|
||||
"@expo/entity": "^0.9.1",
|
||||
"apollo-server-koa": "^2.16.1",
|
||||
"graphql": "^15.3.0",
|
||||
"koa": "^2.13.0",
|
||||
|
||||
@@ -3,14 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
|
||||
|
||||
**Note:** Version bump only for package @expo/entity-full-integration-tests
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@expo/entity-full-integration-tests",
|
||||
"private": true,
|
||||
"version": "0.10.0",
|
||||
"version": "0.9.1",
|
||||
"description": "Full redis and knex integration tests for the entity framework",
|
||||
"scripts": {
|
||||
"tsc": "tsc",
|
||||
@@ -26,8 +26,8 @@
|
||||
"@expo/entity-database-adapter-knex": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/entity": "^0.10.0",
|
||||
"@expo/entity-cache-adapter-redis": "^0.10.0",
|
||||
"@expo/entity-database-adapter-knex": "^0.10.0"
|
||||
"@expo/entity": "^0.9.1",
|
||||
"@expo/entity-cache-adapter-redis": "^0.9.1",
|
||||
"@expo/entity-database-adapter-knex": "^0.9.1"
|
||||
}
|
||||
}
|
||||
@@ -3,17 +3,6 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [0.10.0](https://github.com/expo/entity/compare/v0.9.1...v0.10.0) (2020-11-17)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix null handling in load paths (try 2) ([#96](https://github.com/expo/entity/issues/96)) ([d960329](https://github.com/expo/entity/commit/d9603298e0cc5c3c2b0cca959876e9195555fb57))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.9.1](https://github.com/expo/entity/compare/v0.9.0...v0.9.1) (2020-11-06)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@expo/entity",
|
||||
"version": "0.10.0",
|
||||
"version": "0.9.1",
|
||||
"description": "A privacy-first data model",
|
||||
"files": [
|
||||
"build",
|
||||
|
||||
@@ -79,7 +79,7 @@ export default class EnforcingEntityLoader<
|
||||
uniqueFieldName,
|
||||
fieldValue
|
||||
);
|
||||
return entityResult ? entityResult.enforceValue() : null;
|
||||
return entityResult.enforceValue();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -98,7 +98,7 @@ export default class EnforcingEntityLoader<
|
||||
*/
|
||||
async loadByIDNullableAsync(id: TID): Promise<TEntity | null> {
|
||||
const entityResult = await this.entityLoader.loadByIDNullableAsync(id);
|
||||
return entityResult ? entityResult.enforceValue() : null;
|
||||
return entityResult.enforceValue();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,9 +60,7 @@ export default class EntityAssociationLoader<
|
||||
> {
|
||||
const associatedEntityID = this.entity.getField(fieldIdentifyingAssociatedEntity);
|
||||
if (!associatedEntityID) {
|
||||
return result(null) as Result<
|
||||
null extends TFields[TIdentifyingField] ? TAssociatedEntity | null : TAssociatedEntity
|
||||
>;
|
||||
return result(null) as any;
|
||||
}
|
||||
|
||||
const loader = this.entity
|
||||
@@ -70,10 +68,7 @@ export default class EntityAssociationLoader<
|
||||
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
|
||||
.getLoaderFactory()
|
||||
.forLoad(queryContext);
|
||||
|
||||
return (await loader.loadByIDAsync((associatedEntityID as unknown) as TAssociatedID)) as Result<
|
||||
null extends TFields[TIdentifyingField] ? TAssociatedEntity | null : TAssociatedEntity
|
||||
>;
|
||||
return (await loader.loadByIDAsync((associatedEntityID as unknown) as TAssociatedID)) as any;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,6 +134,7 @@ export default class EntityAssociationLoader<
|
||||
* @param queryContext - query context in which to perform the load
|
||||
*/
|
||||
async loadAssociatedEntityByFieldEqualingAsync<
|
||||
TIdentifyingField extends keyof Pick<TFields, TSelectedFields>,
|
||||
TAssociatedFields,
|
||||
TAssociatedID,
|
||||
TAssociatedEntity extends ReadonlyEntity<
|
||||
@@ -156,7 +152,7 @@ export default class EntityAssociationLoader<
|
||||
>,
|
||||
TAssociatedSelectedFields extends keyof TAssociatedFields = keyof TAssociatedFields
|
||||
>(
|
||||
fieldIdentifyingAssociatedEntity: keyof Pick<TFields, TSelectedFields>,
|
||||
fieldIdentifyingAssociatedEntity: TIdentifyingField,
|
||||
associatedEntityClass: IEntityClass<
|
||||
TAssociatedFields,
|
||||
TAssociatedID,
|
||||
@@ -171,11 +167,12 @@ export default class EntityAssociationLoader<
|
||||
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
|
||||
.getQueryContextProvider()
|
||||
.getQueryContext()
|
||||
): Promise<Result<TAssociatedEntity> | null> {
|
||||
): Promise<Result<TAssociatedEntity | null>> {
|
||||
const associatedFieldValue = this.entity.getField(fieldIdentifyingAssociatedEntity);
|
||||
if (!associatedFieldValue) {
|
||||
return null;
|
||||
return result(null);
|
||||
}
|
||||
|
||||
const loader = this.entity
|
||||
.getViewerContext()
|
||||
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
|
||||
@@ -277,7 +274,7 @@ export default class EntityAssociationLoader<
|
||||
>
|
||||
],
|
||||
queryContext?: EntityQueryContext
|
||||
): Promise<Result<TEntity2> | null>;
|
||||
): Promise<Result<TEntity2 | null>>;
|
||||
|
||||
/**
|
||||
* Load an associated entity by folding a sequence of {@link EntityLoadThroughDirective}. At each
|
||||
@@ -332,7 +329,7 @@ export default class EntityAssociationLoader<
|
||||
>
|
||||
],
|
||||
queryContext?: EntityQueryContext
|
||||
): Promise<Result<TEntity3> | null>;
|
||||
): Promise<Result<TEntity3 | null>>;
|
||||
|
||||
/**
|
||||
* Load an associated entity by folding a sequence of {@link EntityLoadThroughDirective}. At each
|
||||
@@ -408,7 +405,7 @@ export default class EntityAssociationLoader<
|
||||
>
|
||||
],
|
||||
queryContext?: EntityQueryContext
|
||||
): Promise<Result<TEntity4> | null>;
|
||||
): Promise<Result<TEntity4 | null>>;
|
||||
|
||||
/**
|
||||
* Load an associated entity by folding a sequence of {@link EntityLoadThroughDirective}. At each
|
||||
@@ -419,12 +416,12 @@ export default class EntityAssociationLoader<
|
||||
async loadAssociatedEntityThroughAsync(
|
||||
loadDirectives: EntityLoadThroughDirective<TViewerContext, any, any, any, any, any, any, any>[],
|
||||
queryContext?: EntityQueryContext
|
||||
): Promise<Result<ReadonlyEntity<any, any, any, any>> | null>;
|
||||
): Promise<Result<ReadonlyEntity<any, any, any, any> | null>>;
|
||||
|
||||
async loadAssociatedEntityThroughAsync(
|
||||
loadDirectives: EntityLoadThroughDirective<TViewerContext, any, any, any, any, any, any, any>[],
|
||||
queryContext?: EntityQueryContext
|
||||
): Promise<Result<ReadonlyEntity<any, any, any, any>> | null> {
|
||||
): Promise<Result<ReadonlyEntity<any, any, any, any> | null>> {
|
||||
let currentEntity: ReadonlyEntity<any, any, any, any> = this.entity;
|
||||
for (const loadDirective of loadDirectives) {
|
||||
const {
|
||||
@@ -432,7 +429,7 @@ export default class EntityAssociationLoader<
|
||||
fieldIdentifyingAssociatedEntity,
|
||||
associatedEntityLookupByField,
|
||||
} = loadDirective;
|
||||
let associatedEntityResult: Result<ReadonlyEntity<any, any, any, any>> | null;
|
||||
let associatedEntityResult: Result<ReadonlyEntity<any, any, any, any> | null>;
|
||||
if (associatedEntityLookupByField) {
|
||||
associatedEntityResult = await currentEntity
|
||||
.associationLoader()
|
||||
@@ -443,28 +440,23 @@ export default class EntityAssociationLoader<
|
||||
queryContext
|
||||
);
|
||||
} else {
|
||||
const associatedEntityResultLocal = await currentEntity
|
||||
associatedEntityResult = await currentEntity
|
||||
.associationLoader()
|
||||
.loadAssociatedEntityAsync(
|
||||
fieldIdentifyingAssociatedEntity,
|
||||
associatedEntityClass,
|
||||
queryContext
|
||||
);
|
||||
|
||||
if (associatedEntityResultLocal.ok && associatedEntityResultLocal.value === null) {
|
||||
associatedEntityResult = null;
|
||||
} else {
|
||||
associatedEntityResult = associatedEntityResultLocal;
|
||||
}
|
||||
}
|
||||
|
||||
if (!associatedEntityResult) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!associatedEntityResult.ok) {
|
||||
return result(associatedEntityResult.reason);
|
||||
}
|
||||
|
||||
if (!associatedEntityResult.value) {
|
||||
return result(null);
|
||||
}
|
||||
|
||||
currentEntity = associatedEntityResult.value;
|
||||
}
|
||||
return result(currentEntity);
|
||||
|
||||
@@ -132,13 +132,13 @@ export default class EntityLoader<
|
||||
async loadByFieldEqualingAsync<N extends keyof Pick<TFields, TSelectedFields>>(
|
||||
uniqueFieldName: N,
|
||||
fieldValue: NonNullable<TFields[N]>
|
||||
): Promise<Result<TEntity> | null> {
|
||||
): 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;
|
||||
return entityResults[0] ?? result(null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,7 +160,7 @@ export default class EntityLoader<
|
||||
* @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> {
|
||||
async loadByIDNullableAsync(id: TID): Promise<Result<TEntity | null>> {
|
||||
return await this.loadByFieldEqualingAsync(this.idField, id as any);
|
||||
}
|
||||
|
||||
@@ -284,13 +284,6 @@ export default class EntityLoader<
|
||||
await this.dataManager.invalidateObjectFieldsAsync(objectFields);
|
||||
}
|
||||
|
||||
async invalidateFieldsTransactionalAsync(objectFields: Readonly<TFields>): Promise<void> {
|
||||
await this.dataManager.invalidateObjectFieldsTransactionalAsync(
|
||||
this.queryContext,
|
||||
objectFields
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate all caches for an entity. One potential use case would be to keep the entity
|
||||
* framework in sync with changes made to data outside of the framework.
|
||||
|
||||
@@ -205,7 +205,6 @@ export class CreateMutator<
|
||||
queryContext.appendPostCommitCallback(
|
||||
entityLoader.invalidateFieldsAsync.bind(entityLoader, insertResult)
|
||||
);
|
||||
await entityLoader.invalidateFieldsTransactionalAsync(insertResult);
|
||||
|
||||
const unauthorizedEntityAfterInsert = new this.entityClass(this.viewerContext, insertResult);
|
||||
const newEntity = await entityLoader
|
||||
@@ -396,11 +395,6 @@ export class UpdateMutator<
|
||||
entityLoader.invalidateFieldsAsync.bind(entityLoader, this.fieldsForEntity)
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
entityLoader.invalidateFieldsTransactionalAsync(this.originalFieldsForEntity),
|
||||
entityLoader.invalidateFieldsTransactionalAsync(this.fieldsForEntity),
|
||||
]);
|
||||
|
||||
const unauthorizedEntityAfterUpdate = new this.entityClass(this.viewerContext, updateResult);
|
||||
const updatedEntity = await entityLoader
|
||||
.enforcing()
|
||||
@@ -572,7 +566,6 @@ export class DeleteMutator<
|
||||
queryContext.appendPostCommitCallback(
|
||||
entityLoader.invalidateFieldsAsync.bind(entityLoader, this.entity.getAllDatabaseFields())
|
||||
);
|
||||
await entityLoader.invalidateFieldsTransactionalAsync(this.entity.getAllDatabaseFields());
|
||||
|
||||
await this.executeMutationTriggersOrValidatorsAsync(
|
||||
this.mutationTriggers.afterDelete,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import EntityQueryContextProvider from './EntityQueryContextProvider';
|
||||
|
||||
export type PostCommitCallback = (...args: any) => Promise<any>;
|
||||
@@ -14,7 +12,7 @@ export type PostCommitCallback = (...args: any) => Promise<any>;
|
||||
export abstract class EntityQueryContext {
|
||||
constructor(private readonly queryInterface: any) {}
|
||||
|
||||
abstract isInTransaction(): this is EntityTransactionalQueryContext;
|
||||
abstract isInTransaction(): boolean;
|
||||
|
||||
getQueryInterface(): any {
|
||||
return this.queryInterface;
|
||||
@@ -33,7 +31,7 @@ export class EntityNonTransactionalQueryContext extends EntityQueryContext {
|
||||
super(queryInterface);
|
||||
}
|
||||
|
||||
isInTransaction(): this is EntityTransactionalQueryContext {
|
||||
isInTransaction(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -45,8 +43,6 @@ export class EntityNonTransactionalQueryContext extends EntityQueryContext {
|
||||
}
|
||||
|
||||
export class EntityTransactionalQueryContext extends EntityQueryContext {
|
||||
public readonly transactionID = uuidv4();
|
||||
|
||||
private readonly postCommitCallbacks: PostCommitCallback[] = [];
|
||||
|
||||
public appendPostCommitCallback(callback: PostCommitCallback): void {
|
||||
@@ -59,7 +55,7 @@ export class EntityTransactionalQueryContext extends EntityQueryContext {
|
||||
await Promise.all(callbacks.map((callback) => callback()));
|
||||
}
|
||||
|
||||
isInTransaction(): this is EntityTransactionalQueryContext {
|
||||
isInTransaction(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -287,7 +287,6 @@ describe(EnforcingEntityLoader, () => {
|
||||
'enforcing',
|
||||
'invalidateFieldsAsync',
|
||||
'invalidateEntityAsync',
|
||||
'invalidateFieldsTransactionalAsync',
|
||||
'tryConstructEntities',
|
||||
];
|
||||
expect(loaderProperties).toEqual(expect.arrayContaining(knownLoaderOnlyDifferences));
|
||||
|
||||
@@ -67,7 +67,7 @@ describe(EntityAssociationLoader, () => {
|
||||
const loadedOtherResult = await testEntity
|
||||
.associationLoader()
|
||||
.loadAssociatedEntityByFieldEqualingAsync('stringField', TestEntity, 'customIdField');
|
||||
expect(loadedOtherResult?.enforceValue().getID()).toEqual(testOtherEntity.getID());
|
||||
expect(loadedOtherResult.enforceValue()?.getID()).toEqual(testOtherEntity.getID());
|
||||
});
|
||||
|
||||
it('returns null when loading associated entities by field equaling a non existent association', async () => {
|
||||
@@ -79,7 +79,7 @@ describe(EntityAssociationLoader, () => {
|
||||
const loadedOtherResult = await testEntity
|
||||
.associationLoader()
|
||||
.loadAssociatedEntityByFieldEqualingAsync('stringField', TestEntity, 'customIdField');
|
||||
expect(loadedOtherResult).toBeNull();
|
||||
expect(loadedOtherResult.enforceValue()).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when load-by field is null', async () => {
|
||||
@@ -91,7 +91,7 @@ describe(EntityAssociationLoader, () => {
|
||||
const loadedOtherResult = await testEntity
|
||||
.associationLoader()
|
||||
.loadAssociatedEntityByFieldEqualingAsync('nullableField', TestEntity, 'customIdField');
|
||||
expect(loadedOtherResult).toBeNull();
|
||||
expect(loadedOtherResult.enforceValue()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,7 +160,7 @@ describe(EntityAssociationLoader, () => {
|
||||
fieldIdentifyingAssociatedEntity: 'foreignKey',
|
||||
},
|
||||
]);
|
||||
expect(loaded2Result?.enforceValue().getID()).toEqual(testEntity2.getID());
|
||||
expect(loaded2Result.enforceValue()?.getID()).toEqual(testEntity2.getID());
|
||||
|
||||
const loaded3Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([
|
||||
{
|
||||
@@ -172,7 +172,7 @@ describe(EntityAssociationLoader, () => {
|
||||
fieldIdentifyingAssociatedEntity: 'testIndexedField',
|
||||
},
|
||||
]);
|
||||
expect(loaded3Result?.enforceValue().getID()).toEqual(testEntity3.getID());
|
||||
expect(loaded3Result.enforceValue()?.getID()).toEqual(testEntity3.getID());
|
||||
|
||||
const loaded4Result = await testEntity.associationLoader().loadAssociatedEntityThroughAsync([
|
||||
{
|
||||
@@ -188,7 +188,7 @@ describe(EntityAssociationLoader, () => {
|
||||
fieldIdentifyingAssociatedEntity: 'foreignKey',
|
||||
},
|
||||
]);
|
||||
expect(loaded4Result?.enforceValue().getID()).toEqual(testEntity4.getID());
|
||||
expect(loaded4Result.enforceValue()?.getID()).toEqual(testEntity4.getID());
|
||||
});
|
||||
|
||||
it('fails when chain loading associated entity fails', async () => {
|
||||
@@ -226,7 +226,7 @@ describe(EntityAssociationLoader, () => {
|
||||
associatedEntityLookupByField: 'stringField',
|
||||
},
|
||||
]);
|
||||
expect(loaded2Result?.enforceValue().getID()).toEqual(testEntity2.getID());
|
||||
expect(loaded2Result.enforceValue()?.getID()).toEqual(testEntity2.getID());
|
||||
});
|
||||
|
||||
it('returns null when chain loading by field equality returns null', async () => {
|
||||
@@ -244,7 +244,7 @@ describe(EntityAssociationLoader, () => {
|
||||
associatedEntityLookupByField: 'stringField',
|
||||
},
|
||||
]);
|
||||
expect(loaded2Result).toBeNull();
|
||||
expect(loaded2Result.enforceValue()).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when chain loading by field is null', async () => {
|
||||
@@ -261,7 +261,7 @@ describe(EntityAssociationLoader, () => {
|
||||
fieldIdentifyingAssociatedEntity: 'nullableField',
|
||||
},
|
||||
]);
|
||||
expect(loadedResult).toBeNull();
|
||||
expect(loadedResult.enforceValue()).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -81,16 +81,16 @@ describe(EntityLoader, () => {
|
||||
|
||||
const entityResultNumber3 = await entityLoader.loadByFieldEqualingAsync('numberField', 3);
|
||||
expect(entityResultNumber3).not.toBeNull();
|
||||
expect(entityResultNumber3!.enforceValue().getID()).toEqual('world');
|
||||
expect(entityResultNumber3.enforceValue()?.getID()).toEqual('world');
|
||||
|
||||
const entityResultNumber4 = await entityLoader.loadByFieldEqualingAsync('numberField', 4);
|
||||
expect(entityResultNumber4).toBeNull();
|
||||
expect(entityResultNumber4.enforceValue()).toBeNull();
|
||||
|
||||
await expect(entityLoader.loadByFieldEqualingAsync('stringField', 'huh')).rejects.toThrowError(
|
||||
'loadByFieldEqualing: Multiple entities of type TestEntity found for stringField=huh'
|
||||
);
|
||||
|
||||
await expect(entityLoader.loadByIDNullableAsync('fake')).resolves.toBeNull();
|
||||
expect((await entityLoader.loadByIDNullableAsync('fake')).enforceValue()).toBeNull();
|
||||
await expect(entityLoader.loadByIDNullableAsync('hello')).resolves.not.toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import EntityDatabaseAdapter, {
|
||||
FieldEqualityCondition,
|
||||
QuerySelectionModifiers,
|
||||
} from '../EntityDatabaseAdapter';
|
||||
import { EntityQueryContext, EntityTransactionalQueryContext } from '../EntityQueryContext';
|
||||
import { EntityQueryContext } from '../EntityQueryContext';
|
||||
import EntityQueryContextProvider from '../EntityQueryContextProvider';
|
||||
import { partitionErrors } from '../entityUtils';
|
||||
import {
|
||||
@@ -15,13 +15,6 @@ import IEntityMetricsAdapter, { EntityMetricsLoadType } from '../metrics/IEntity
|
||||
import { computeIfAbsent, zipToMap } from '../utils/collections/maps';
|
||||
import ReadThroughEntityCache from './ReadThroughEntityCache';
|
||||
|
||||
type FieldDataLoader<TFields> = DataLoader<
|
||||
NonNullable<TFields[keyof TFields]>,
|
||||
readonly Readonly<TFields>[]
|
||||
>;
|
||||
|
||||
type FieldDataLoadersMap<TFields> = Map<keyof TFields, FieldDataLoader<TFields>>;
|
||||
|
||||
/**
|
||||
* A data manager is responsible for orchestrating multiple sources of entity
|
||||
* data including local caches, {@link EntityCacheAdapter}, and {@link EntityDatabaseAdapter}.
|
||||
@@ -29,11 +22,9 @@ type FieldDataLoadersMap<TFields> = Map<keyof TFields, FieldDataLoader<TFields>>
|
||||
* It is also responsible for invalidating all sources of data when mutated using {@link EntityMutator}.
|
||||
*/
|
||||
export default class EntityDataManager<TFields> {
|
||||
private readonly regularFieldDataLoaders: FieldDataLoadersMap<TFields> = new Map();
|
||||
|
||||
private readonly transactionalFieldDataLoaders: Map<
|
||||
string,
|
||||
FieldDataLoadersMap<TFields>
|
||||
private readonly fieldDataLoaders: Map<
|
||||
keyof TFields,
|
||||
DataLoader<NonNullable<TFields[keyof TFields]>, readonly Readonly<TFields>[]>
|
||||
> = new Map();
|
||||
|
||||
constructor(
|
||||
@@ -44,10 +35,10 @@ export default class EntityDataManager<TFields> {
|
||||
private readonly entityClassName: string
|
||||
) {}
|
||||
|
||||
private getRegularFieldDataLoderForFieldName<N extends keyof TFields>(
|
||||
private getFieldDataLoaderForFieldName<N extends keyof TFields>(
|
||||
fieldName: N
|
||||
): FieldDataLoader<TFields> {
|
||||
return computeIfAbsent(this.regularFieldDataLoaders, fieldName, () => {
|
||||
): DataLoader<NonNullable<TFields[N]>, readonly Readonly<TFields>[]> {
|
||||
return computeIfAbsent(this.fieldDataLoaders, fieldName, () => {
|
||||
return new DataLoader(
|
||||
async (
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
@@ -62,43 +53,6 @@ export default class EntityDataManager<TFields> {
|
||||
});
|
||||
}
|
||||
|
||||
private getTransactionalFieldDataLoaderForFieldName<N extends keyof TFields>(
|
||||
queryContext: EntityTransactionalQueryContext,
|
||||
fieldName: N
|
||||
): FieldDataLoader<TFields> {
|
||||
const fieldDataLoaderMap = computeIfAbsent(
|
||||
this.transactionalFieldDataLoaders,
|
||||
queryContext.transactionID,
|
||||
() => new Map<keyof TFields, FieldDataLoader<TFields>>()
|
||||
);
|
||||
return computeIfAbsent(fieldDataLoaderMap, fieldName, () => {
|
||||
return new DataLoader(
|
||||
async (
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
): Promise<readonly (readonly TFields[])[]> => {
|
||||
const objectMap = await this.loadManyForTransactionalDataLoaderByFieldEqualingAsync(
|
||||
fieldName,
|
||||
fieldValues,
|
||||
queryContext
|
||||
);
|
||||
return fieldValues.map((fv) => objectMap.get(fv) ?? []);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async loadManyForTransactionalDataLoaderByFieldEqualingAsync<N extends keyof TFields>(
|
||||
fieldName: N,
|
||||
fieldValues: readonly NonNullable<TFields[N]>[],
|
||||
queryContext: EntityTransactionalQueryContext
|
||||
): Promise<ReadonlyMap<NonNullable<TFields[N]>, readonly Readonly<TFields>[]>> {
|
||||
this.metricsAdapter.incrementDataManagerDatabaseLoadCount({
|
||||
fieldValueCount: fieldValues.length,
|
||||
entityClassName: this.entityClassName,
|
||||
});
|
||||
return await this.databaseAdapter.fetchManyWhereAsync(queryContext, fieldName, fieldValues);
|
||||
}
|
||||
|
||||
private async loadManyForDataLoaderByFieldEqualingAsync<N extends keyof TFields>(
|
||||
fieldName: N,
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
@@ -107,11 +61,10 @@ export default class EntityDataManager<TFields> {
|
||||
fieldValueCount: fieldValues.length,
|
||||
entityClassName: this.entityClassName,
|
||||
});
|
||||
|
||||
return await this.entityCache.readManyThroughAsync(
|
||||
fieldName,
|
||||
fieldValues,
|
||||
async (fetcherValues: readonly NonNullable<TFields[N]>[]) => {
|
||||
async (fetcherValues) => {
|
||||
this.metricsAdapter.incrementDataManagerDatabaseLoadCount({
|
||||
fieldValueCount: fieldValues.length,
|
||||
entityClassName: this.entityClassName,
|
||||
@@ -159,13 +112,16 @@ export default class EntityDataManager<TFields> {
|
||||
);
|
||||
}
|
||||
|
||||
// don't cache when in transaction, as rollbacks complicate things significantly
|
||||
if (queryContext.isInTransaction()) {
|
||||
return await this.databaseAdapter.fetchManyWhereAsync(queryContext, fieldName, fieldValues);
|
||||
}
|
||||
|
||||
this.metricsAdapter.incrementDataManagerDataloaderLoadCount({
|
||||
fieldValueCount: fieldValues.length,
|
||||
entityClassName: this.entityClassName,
|
||||
});
|
||||
const dataLoader = queryContext.isInTransaction()
|
||||
? this.getTransactionalFieldDataLoaderForFieldName(queryContext, fieldName)
|
||||
: this.getRegularFieldDataLoderForFieldName(fieldName);
|
||||
const dataLoader = this.getFieldDataLoaderForFieldName(fieldName);
|
||||
const results = await dataLoader.loadMany(fieldValues);
|
||||
const [values, errors] = partitionErrors(results);
|
||||
if (errors.length > 0) {
|
||||
@@ -236,20 +192,7 @@ export default class EntityDataManager<TFields> {
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
): Promise<void> {
|
||||
await this.entityCache.invalidateManyAsync(fieldName, fieldValues);
|
||||
const dataLoader = this.getRegularFieldDataLoderForFieldName(fieldName);
|
||||
fieldValues.forEach((fieldValue) => dataLoader.clear(fieldValue));
|
||||
}
|
||||
|
||||
private async invalidateManyByFieldEqualingTransactionalAsync<N extends keyof TFields>(
|
||||
queryContext: EntityQueryContext,
|
||||
fieldName: N,
|
||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||
): Promise<void> {
|
||||
if (!queryContext.isInTransaction()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataLoader = this.getTransactionalFieldDataLoaderForFieldName(queryContext, fieldName);
|
||||
const dataLoader = this.getFieldDataLoaderForFieldName(fieldName);
|
||||
fieldValues.forEach((fieldValue) => dataLoader.clear(fieldValue));
|
||||
}
|
||||
|
||||
@@ -272,21 +215,4 @@ export default class EntityDataManager<TFields> {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async invalidateObjectFieldsTransactionalAsync(
|
||||
queryContext: EntityQueryContext,
|
||||
objectFields: Readonly<TFields>
|
||||
): Promise<void> {
|
||||
const keys = Object.keys(objectFields) as (keyof TFields)[];
|
||||
await Promise.all(
|
||||
keys.map(async (fieldName: keyof TFields) => {
|
||||
const value = objectFields[fieldName];
|
||||
if (value !== undefined) {
|
||||
await this.invalidateManyByFieldEqualingTransactionalAsync(queryContext, fieldName, [
|
||||
value as NonNullable<TFields[keyof TFields]>,
|
||||
]);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -570,7 +570,7 @@ describe(EntityDataManager, () => {
|
||||
verify(metricsAdapterMock.incrementDataManagerDatabaseLoadCount(anyNumber())).never();
|
||||
});
|
||||
|
||||
it('throws when a load-by value is null or undefined', async () => {
|
||||
it('throws when a load by value is null or undefined', async () => {
|
||||
const objects = getObjects();
|
||||
const dataStore = StubDatabaseAdapter.convertFieldObjectsToDataStore(
|
||||
testEntityConfiguration,
|
||||
|
||||
@@ -559,17 +559,17 @@
|
||||
which "^1.3.1"
|
||||
|
||||
"@expo/entity-cache-adapter-redis@file:packages/entity-cache-adapter-redis":
|
||||
version "0.10.0"
|
||||
version "0.6.0"
|
||||
dependencies:
|
||||
ioredis "^4.17.3"
|
||||
|
||||
"@expo/entity-database-adapter-knex@file:packages/entity-database-adapter-knex":
|
||||
version "0.10.0"
|
||||
version "0.6.0"
|
||||
dependencies:
|
||||
knex "^0.21.5"
|
||||
|
||||
"@expo/entity@file:packages/entity":
|
||||
version "0.10.0"
|
||||
version "0.6.0"
|
||||
dependencies:
|
||||
"@expo/results" "^1.0.0"
|
||||
dataloader "^2.0.0"
|
||||
|
||||
Reference in new issue
Block a user