From a39d0f42e4a48e0b1508400b55234ef4567c6ceb Mon Sep 17 00:00:00 2001 From: Marko Zabreznik Date: Thu, 1 Apr 2021 10:32:08 +0200 Subject: [PATCH 1/3] feat: custom query escape hatch --- src/AbstractFirestoreRepository.ts | 4 +++- src/BaseFirestoreRepository.ts | 8 +++++++- src/QueryBuilder.ts | 20 ++++++++++++++++++-- src/types.ts | 11 +++++++++-- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/AbstractFirestoreRepository.ts b/src/AbstractFirestoreRepository.ts index 42d6e629..53999ed2 100644 --- a/src/AbstractFirestoreRepository.ts +++ b/src/AbstractFirestoreRepository.ts @@ -18,6 +18,7 @@ import { PartialBy, IEntityConstructor, ITransactionReferenceStorage, + ICustomQuery, } from './types'; import { isDocumentReference, isGeoPoint, isObject, isTimestamp } from './TypeGuards'; @@ -401,7 +402,8 @@ export abstract class AbstractFirestoreRepository extends Bas queries: IFireOrmQueryLine[], limitVal?: number, orderByObj?: IOrderByParams, - single?: boolean + single?: boolean, + customQuery?: ICustomQuery ): Promise; /** diff --git a/src/BaseFirestoreRepository.ts b/src/BaseFirestoreRepository.ts index 40939aab..89839bc2 100644 --- a/src/BaseFirestoreRepository.ts +++ b/src/BaseFirestoreRepository.ts @@ -9,6 +9,7 @@ import { IEntity, PartialBy, ITransactionRepository, + ICustomQuery, } from './types'; import { getMetadataStorage } from './MetadataUtils'; @@ -91,7 +92,8 @@ export class BaseFirestoreRepository extends AbstractFirestor queries: Array, limitVal?: number, orderByObj?: IOrderByParams, - single?: boolean + single?: boolean, + customQuery?: ICustomQuery ): Promise { let query = queries.reduce((acc, cur) => { const op = cur.operator as WhereFilterOp; @@ -108,6 +110,10 @@ export class BaseFirestoreRepository extends AbstractFirestor query = query.limit(limitVal); } + if (customQuery) { + query = await customQuery(query, this.firestoreColRef); + } + return query.get().then(this.extractTFromColSnap); } } diff --git a/src/QueryBuilder.ts b/src/QueryBuilder.ts index 5040afe0..c9a1f92a 100644 --- a/src/QueryBuilder.ts +++ b/src/QueryBuilder.ts @@ -9,12 +9,14 @@ import { IQueryExecutor, IEntity, IWherePropParam, + ICustomQuery, } from './types'; export default class QueryBuilder implements IQueryBuilder { protected queries: Array = []; protected limitVal: number; protected orderByObj: IOrderByParams; + protected customQueryFunction?: ICustomQuery; constructor(protected executor: IQueryExecutor) {} @@ -172,7 +174,20 @@ export default class QueryBuilder implements IQueryBuilder } find() { - return this.executor.execute(this.queries, this.limitVal, this.orderByObj); + return this.executor.execute( + this.queries, + this.limitVal, + this.orderByObj, + false, + this.customQueryFunction + ); + } + + customQuery(func: ICustomQuery) { + if (this.customQueryFunction) { + throw new Error('Only one custom query can be used per query expression'); + } + this.customQueryFunction = func; } async findOne() { @@ -180,7 +195,8 @@ export default class QueryBuilder implements IQueryBuilder this.queries, this.limitVal, this.orderByObj, - true + true, + this.customQueryFunction ); return queryResult.length ? queryResult[0] : null; diff --git a/src/types.ts b/src/types.ts index 404f1d9b..d400010f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ -import { OrderByDirection, DocumentReference } from '@google-cloud/firestore'; +import { OrderByDirection, DocumentReference, CollectionReference } from '@google-cloud/firestore'; +import { Query } from '@google-cloud/firestore'; export type PartialBy = Omit & Partial>; export type PartialWithRequiredBy = Pick & Partial>; @@ -62,12 +63,18 @@ export interface ILimitable { export type IQueryBuilder = IQueryable & IOrderable & ILimitable; +export type ICustomQuery = ( + query: Query, + firestoreColRef: CollectionReference +) => Promise; + export interface IQueryExecutor { execute( queries: IFireOrmQueryLine[], limitVal?: number, orderByObj?: IOrderByParams, - single?: boolean + single?: boolean, + customQuery?: ICustomQuery ): Promise; } From 961cfc0c0e51de132408cacba9b3e711adcbb0b0 Mon Sep 17 00:00:00 2001 From: Marko Zabreznik Date: Thu, 1 Apr 2021 10:53:39 +0200 Subject: [PATCH 2/3] feat: custom query within abstract repo --- src/AbstractFirestoreRepository.ts | 13 +++++++++++++ src/QueryBuilder.ts | 3 +++ 2 files changed, 16 insertions(+) diff --git a/src/AbstractFirestoreRepository.ts b/src/AbstractFirestoreRepository.ts index 53999ed2..d9a4cf23 100644 --- a/src/AbstractFirestoreRepository.ts +++ b/src/AbstractFirestoreRepository.ts @@ -355,6 +355,19 @@ export abstract class AbstractFirestoreRepository extends Bas return new QueryBuilder(this).findOne(); } + /** + * Returns a new QueryBuilder with an custom query + * specified by @param func. Can only be used once per query. + * + * @param {ICustomQuery} func function to run in a new query + * @returns {QueryBuilder} A new QueryBuilder with the specified + * custom query applied. + * @memberof AbstractFirestoreRepository + */ + customQuery(func: ICustomQuery) { + return new QueryBuilder(this).customQuery(func) + } + /** * Uses class-validator to validate an entity using decorators set in the collection class * diff --git a/src/QueryBuilder.ts b/src/QueryBuilder.ts index c9a1f92a..9c39a870 100644 --- a/src/QueryBuilder.ts +++ b/src/QueryBuilder.ts @@ -187,7 +187,10 @@ export default class QueryBuilder implements IQueryBuilder if (this.customQueryFunction) { throw new Error('Only one custom query can be used per query expression'); } + this.customQueryFunction = func; + + return this; } async findOne() { From 5919ac13fd76729388ab69e218584249391d9643 Mon Sep 17 00:00:00 2001 From: Marko Zabreznik Date: Thu, 1 Apr 2021 11:11:31 +0200 Subject: [PATCH 3/3] feat: custom query interfaces --- src/AbstractFirestoreRepository.ts | 4 ++-- src/types.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AbstractFirestoreRepository.ts b/src/AbstractFirestoreRepository.ts index d9a4cf23..c43904b2 100644 --- a/src/AbstractFirestoreRepository.ts +++ b/src/AbstractFirestoreRepository.ts @@ -364,8 +364,8 @@ export abstract class AbstractFirestoreRepository extends Bas * custom query applied. * @memberof AbstractFirestoreRepository */ - customQuery(func: ICustomQuery) { - return new QueryBuilder(this).customQuery(func) + customQuery(func: ICustomQuery): IQueryBuilder { + return new QueryBuilder(this).customQuery(func); } /** diff --git a/src/types.ts b/src/types.ts index d400010f..6a964aeb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -50,6 +50,7 @@ export interface IQueryable { whereNotIn(prop: IWherePropParam, val: IFirestoreVal[]): IQueryBuilder; find(): Promise; findOne(): Promise; + customQuery(func: ICustomQuery): IQueryBuilder; } export interface IOrderable {