1515 * limitations under the License.
1616 */
1717
18+ import { deepEqual } from '@firebase/util' ;
19+
1820import { Value } from '../protos/firestore_proto_api' ;
1921import { invokeRunAggregationQueryRpc } from '../remote/datastore' ;
2022import { hardAssert } from '../util/assert' ;
@@ -26,61 +28,87 @@ import { Query, queryEqual } from './reference';
2628import { LiteUserDataWriter } from './reference_impl' ;
2729
2830/**
29- * An `AggregateQuery` computes some aggregation statistics from the result set of
30- * a base `Query`.
31+ * An `AggregateField`that captures input type T.
3132 */
32- export class AggregateQuery {
33- readonly type = 'AggregateQuery' ;
34- /**
35- * The query on which you called `countQuery` in order to get this `AggregateQuery`.
36- */
37- readonly query : Query < unknown > ;
33+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
34+ export class AggregateField < T > {
35+ type = 'AggregateField' ;
36+ }
3837
39- /** @hideconstructor */
40- constructor ( query : Query < unknown > ) {
41- this . query = query ;
42- }
38+ /**
39+ * Creates and returns an aggregation field that counts the documents in the result set.
40+ * @returns An `AggregateField` object with number input type.
41+ */
42+ export function count ( ) : AggregateField < number > {
43+ return new AggregateField < number > ( ) ;
4344}
4445
4546/**
46- * An `AggregateQuerySnapshot` contains results of a `AggregateQuery`.
47+ * The union of all `AggregateField` types that are returned from the factory
48+ * functions.
49+ */
50+ type AggregateFieldType = ReturnType < typeof count > ;
51+
52+ /**
53+ * A type whose values are all `AggregateField` objects.
54+ * This is used as an argument to the "getter" functions, and the snapshot will
55+ * map the same names to the corresponding values.
56+ */
57+ export interface AggregateSpec {
58+ [ field : string ] : AggregateFieldType ;
59+ }
60+
61+ /**
62+ * A type whose keys are taken from an `AggregateSpec` type, and whose values
63+ * are the result of the aggregation performed by the corresponding
64+ * `AggregateField` from the input `AggregateSpec`.
65+ */
66+ export type AggregateSpecData < T extends AggregateSpec > = {
67+ [ P in keyof T ] : T [ P ] extends AggregateField < infer U > ? U : never ;
68+ } ;
69+
70+ /**
71+ * An `AggregateQuerySnapshot` contains the results of running an aggregate query.
4772 */
48- export class AggregateQuerySnapshot {
73+ export class AggregateQuerySnapshot < T extends AggregateSpec > {
4974 readonly type = 'AggregateQuerySnapshot' ;
50- readonly query : AggregateQuery ;
5175
5276 /** @hideconstructor */
53- constructor ( query : AggregateQuery , private readonly _count : number ) {
54- this . query = query ;
55- }
77+ constructor (
78+ readonly query : Query < unknown > ,
79+ private readonly _data : AggregateSpecData < T >
80+ ) { }
5681
5782 /**
58- * @returns The result of a document count aggregation. Returns null if no count aggregation is
59- * available in the result.
83+ * The results of the requested aggregations. The keys of the returned object
84+ * will be the same as those of the `AggregateSpec` object specified to the
85+ * aggregation method, and the values will be the corresponding aggregation
86+ * result.
87+ *
88+ * @returns The aggregation statistics result of running a query.
6089 */
61- getCount ( ) : number | null {
62- return this . _count ;
90+ data ( ) : AggregateSpecData < T > {
91+ return this . _data ;
6392 }
6493}
6594
6695/**
67- * Creates an `AggregateQuery` counting the number of documents matching this query.
96+ * Counts the number of documents in the result set of the given query, ignoring
97+ * any locally-cached data and any locally-pending writes and simply surfacing
98+ * whatever the server returns. If the server cannot be reached then the
99+ * returned promise will be rejected.
100+ *
101+ * @param query - The `Query` to execute.
68102 *
69- * @returns An `AggregateQuery` object that can be used to count the number of documents in
70- * the result set of this query.
103+ * @returns An `AggregateQuerySnapshot` that contains the number of documents.
71104 */
72- export function countQuery ( query : Query < unknown > ) : AggregateQuery {
73- return new AggregateQuery ( query ) ;
74- }
75-
76- export function getAggregate (
77- query : AggregateQuery
78- ) : Promise < AggregateQuerySnapshot > {
79- const firestore = cast ( query . query . firestore , Firestore ) ;
105+ export function getCount (
106+ query : Query < unknown >
107+ ) : Promise < AggregateQuerySnapshot < { count : AggregateField < number > } > > {
108+ const firestore = cast ( query . firestore , Firestore ) ;
80109 const datastore = getDatastore ( firestore ) ;
81110 const userDataWriter = new LiteUserDataWriter ( firestore ) ;
82-
83- return invokeRunAggregationQueryRpc ( datastore , query ) . then ( result => {
111+ return invokeRunAggregationQueryRpc ( datastore , query . _query ) . then ( result => {
84112 hardAssert (
85113 result [ 0 ] !== undefined ,
86114 'Aggregation fields are missing from result.'
@@ -90,29 +118,36 @@ export function getAggregate(
90118 . filter ( ( [ key , value ] ) => key === 'count_alias' )
91119 . map ( ( [ key , value ] ) => userDataWriter . convertValue ( value as Value ) ) ;
92120
93- const count = counts [ 0 ] ;
121+ const countValue = counts [ 0 ] ;
122+
94123 hardAssert (
95- typeof count === 'number' ,
96- 'Count aggeragte field value is not a number: ' + count
124+ typeof countValue === 'number' ,
125+ 'Count aggregate field value is not a number: ' + countValue
97126 ) ;
98127
99- return Promise . resolve ( new AggregateQuerySnapshot ( query , count ) ) ;
128+ return Promise . resolve (
129+ new AggregateQuerySnapshot < { count : AggregateField < number > } > ( query , {
130+ count : countValue
131+ } )
132+ ) ;
100133 } ) ;
101134}
102135
103- export function aggregateQueryEqual (
104- left : AggregateQuery ,
105- right : AggregateQuery
106- ) : boolean {
107- return queryEqual ( left . query , right . query ) ;
108- }
109-
110- export function aggregateQuerySnapshotEqual (
111- left : AggregateQuerySnapshot ,
112- right : AggregateQuerySnapshot
136+ /**
137+ * Compares two `AggregateQuerySnapshot` instances for equality.
138+ * Two `AggregateQuerySnapshot` instances are considered "equal" if they have
139+ * the same underlying query, the same metadata, and the same data.
140+ *
141+ * @param left - The `AggregateQuerySnapshot` to compare.
142+ * @param right - The `AggregateQuerySnapshot` to compare.
143+ *
144+ * @returns true if the AggregateQuerySnapshos are equal.
145+ */
146+ export function aggregateQuerySnapshotEqual < T extends AggregateSpec > (
147+ left : AggregateQuerySnapshot < T > ,
148+ right : AggregateQuerySnapshot < T >
113149) : boolean {
114150 return (
115- aggregateQueryEqual ( left . query , right . query ) &&
116- left . getCount ( ) === right . getCount ( )
151+ queryEqual ( left . query , right . query ) && deepEqual ( left . data ( ) , right . data ( ) )
117152 ) ;
118153}
0 commit comments