/* * Copyright (c) 2022-present Invertase Limited & Contributors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this library except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import type { AggregateSpec, AggregateSpecData, AggregateType, DocumentData, Query as FirestoreQuery, } from './types/firestore'; import { FieldPath, fromDotSeparatedString } from './FieldPath'; import { queryEqual } from './modular/query'; import type FirestorePath from './FirestorePath'; import type { Query as QueryImplementation } from './FirestoreQuery'; import type QueryModifiers from './FirestoreQueryModifiers'; import type { FirestoreInternal } from './types/internal'; export class AggregateQuery { _firestore: FirestoreInternal; _query: FirestoreQuery; _collectionPath: FirestorePath; _modifiers: QueryModifiers; constructor( firestore: FirestoreInternal, query: QueryImplementation, collectionPath: FirestorePath, modifiers: QueryModifiers, ) { this._firestore = firestore; this._query = query; this._collectionPath = collectionPath; this._modifiers = modifiers; } get query(): FirestoreQuery { return this._query; } get(): Promise { return this._firestore.native .collectionCount( this._collectionPath.relativeName, this._modifiers.type, this._modifiers.filters, this._modifiers.orders, this._modifiers.options, ) .then((data: { count?: number }) => new AggregateQuerySnapshot(this._query, data, true)); } } export class AggregateQuerySnapshot< AggregateSpecType extends AggregateSpec = AggregateSpec, AppModelType = DocumentData, DbModelType extends DocumentData = DocumentData, > { readonly type = 'AggregateQuerySnapshot'; _query: FirestoreQuery; _data: { count?: number; [key: string]: unknown }; _isGetCountFromServer: boolean; constructor( query: FirestoreQuery, data: { count?: number; [key: string]: unknown }, isGetCountFromServer: boolean, ) { this._query = query; this._data = data; this._isGetCountFromServer = isGetCountFromServer; } get query(): FirestoreQuery { return this._query; } data(): AggregateSpecData { if (this._isGetCountFromServer) { return { count: this._data.count } as AggregateSpecData; } return { ...this._data } as AggregateSpecData; } _fieldsProto(): Record { return { ...this._data }; } } export class AggregateField { readonly type = 'AggregateField'; readonly aggregateType: AggregateType; readonly _internalFieldPath?: FieldPath; // Phantom property to anchor the type parameter T for AggregateSpecData inference. _phantomType?: T; constructor(aggregateType: AggregateType = 'count', fieldPath?: FieldPath) { this.aggregateType = aggregateType; this._internalFieldPath = fieldPath; } } export function fieldPathFromArgument(path: string | FieldPath): FieldPath { if (path instanceof FieldPath) { return path; } if (typeof path === 'string') { return fromDotSeparatedString(path); } throw new Error('Field path arguments must be of type `string` or `FieldPath`'); } /** * Compares two `AggregateField` instances for equality. */ export function aggregateFieldEqual( left: AggregateField, right: AggregateField, ): boolean { return ( left instanceof AggregateField && right instanceof AggregateField && left.aggregateType === right.aggregateType && left._internalFieldPath?._toPath() === right._internalFieldPath?._toPath() ); } function aggregateSpecDataEqual( left: AggregateSpecData, right: AggregateSpecData, ): boolean { const leftKeys = Object.keys(left); const rightKeys = Object.keys(right); if (leftKeys.length !== rightKeys.length) { return false; } for (const key of leftKeys) { if (!Object.prototype.hasOwnProperty.call(right, key) || left[key] !== right[key]) { return false; } } return true; } /** * Compares two `AggregateQuerySnapshot` instances for equality. */ export function aggregateQuerySnapshotEqual< AggregateSpecType extends AggregateSpec, AppModelType, DbModelType extends DocumentData, >( left: AggregateQuerySnapshot, right: AggregateQuerySnapshot, ): boolean { return ( left instanceof AggregateQuerySnapshot && right instanceof AggregateQuerySnapshot && queryEqual(left.query, right.query) && aggregateSpecDataEqual(left.data(), right.data()) ); }