diff --git a/src/AbstractFirestoreRepository.ts b/src/AbstractFirestoreRepository.ts index 976e3be..166077a 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'; @@ -356,6 +357,19 @@ export abstract class AbstractFirestoreRepository 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): IQueryBuilder { + return new QueryBuilder(this).customQuery(func); + } + /** * Uses class-validator to validate an entity using decorators set in the collection class * @@ -403,7 +417,8 @@ export abstract class AbstractFirestoreRepository queries: IFireOrmQueryLine[], limitVal?: number, orderByObj?: IOrderByParams, - single?: boolean + single?: boolean, + customQuery?: ICustomQuery ): Promise; /** diff --git a/src/BaseFirestoreRepository.ts b/src/BaseFirestoreRepository.ts index 9409696..e2e38c3 100644 --- a/src/BaseFirestoreRepository.ts +++ b/src/BaseFirestoreRepository.ts @@ -9,6 +9,7 @@ import { IEntity, PartialBy, ITransactionRepository, + ICustomQuery, } from './types'; import { getMetadataStorage } from './MetadataUtils'; @@ -93,7 +94,8 @@ export class BaseFirestoreRepository 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; @@ -110,6 +112,10 @@ export class BaseFirestoreRepository 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 02d0e09..5ea60c8 100644 --- a/src/QueryBuilder.ts +++ b/src/QueryBuilder.ts @@ -9,12 +9,14 @@ import { IQueryExecutor, IEntity, IWherePropParam, + ICustomQuery, } from './types'; export class QueryBuilder implements IQueryBuilder { protected queries: Array = []; protected limitVal: number; protected orderByObj: IOrderByParams; + protected customQueryFunction?: ICustomQuery; constructor(protected executor: IQueryExecutor) {} @@ -172,7 +174,23 @@ export 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; + + return this; } async findOne() { @@ -180,7 +198,8 @@ export 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 8a85c67..26dc974 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>; @@ -49,6 +50,7 @@ export interface IQueryable { whereNotIn(prop: IWherePropParam, val: IFirestoreVal[]): IQueryBuilder; find(): Promise; findOne(): Promise; + customQuery(func: ICustomQuery): IQueryBuilder; } export interface IOrderable { @@ -62,12 +64,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; }