@react-native-firebase/firestore 21.4.0 → 21.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [21.5.0](https://github.com/invertase/react-native-firebase/compare/v21.4.1...v21.5.0) (2024-11-16)
7
+
8
+ ### Features
9
+
10
+ - **firestore:** implement `sum()` & `average()` aggregate queries ([#8115](https://github.com/invertase/react-native-firebase/issues/8115)) ([e4c57fa](https://github.com/invertase/react-native-firebase/commit/e4c57fae5558d47af06fc82412ff61dc5ae54698))
11
+
12
+ ### Bug Fixes
13
+
14
+ - **android:** rn74 forward-port onCatalystInstanceDestroy -> invalidate ([83696ea](https://github.com/invertase/react-native-firebase/commit/83696ea4c944b2be0b8fd9f2fc1db212800cbcf8))
15
+
16
+ ## [21.4.1](https://github.com/invertase/react-native-firebase/compare/v21.4.0...v21.4.1) (2024-11-13)
17
+
18
+ ### Reverts
19
+
20
+ - Revert "fix(ios, sdk): constrain transitive dependencies more tightly" ([1ff247c](https://github.com/invertase/react-native-firebase/commit/1ff247cd73804efbd52eb9490f68087685de814c))
21
+
6
22
  ## [21.4.0](https://github.com/invertase/react-native-firebase/compare/v21.3.0...v21.4.0) (2024-11-07)
7
23
 
8
24
  ### Features
@@ -39,7 +39,7 @@ Pod::Spec.new do |s|
39
39
  end
40
40
 
41
41
  # Firebase dependencies
42
- s.dependency 'Firebase/Firestore', "<= #{firebase_sdk_version}"
42
+ s.dependency 'Firebase/Firestore', firebase_sdk_version
43
43
 
44
44
  if defined?($RNFirebaseAsStaticFramework)
45
45
  Pod::UI.puts "#{s.name}: Using overridden static_framework value of '#{$RNFirebaseAsStaticFramework}'"
@@ -4,6 +4,10 @@ import firestore, {
4
4
  firebase,
5
5
  Filter,
6
6
  getFirestore,
7
+ getAggregateFromServer,
8
+ count,
9
+ average,
10
+ sum,
7
11
  addDoc,
8
12
  doc,
9
13
  collection,
@@ -651,6 +655,22 @@ describe('Firestore', function () {
651
655
  it('`enablePersistentCacheIndexAutoCreation` is properly exposed to end user', function () {
652
656
  expect(enablePersistentCacheIndexAutoCreation).toBeDefined();
653
657
  });
658
+
659
+ it('`getAggregateFromServer` is properly exposed to end user', function () {
660
+ expect(getAggregateFromServer).toBeDefined();
661
+ });
662
+
663
+ it('`count` is properly exposed to end user', function () {
664
+ expect(count).toBeDefined();
665
+ });
666
+
667
+ it('`average` is properly exposed to end user', function () {
668
+ expect(average).toBeDefined();
669
+ });
670
+
671
+ it('`sum` is properly exposed to end user', function () {
672
+ expect(sum).toBeDefined();
673
+ });
654
674
  });
655
675
 
656
676
  describe('FirestorePersistentCacheIndexManager', function () {
@@ -17,6 +17,8 @@ package io.invertase.firebase.firestore;
17
17
  *
18
18
  */
19
19
 
20
+ import static com.google.firebase.firestore.AggregateField.average;
21
+ import static com.google.firebase.firestore.AggregateField.sum;
20
22
  import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreCommon.rejectPromiseFirestoreException;
21
23
  import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreSerialize.snapshotToWritableMap;
22
24
  import static io.invertase.firebase.firestore.UniversalFirebaseFirestoreCommon.getFirestoreForApp;
@@ -28,6 +30,7 @@ import com.google.android.gms.tasks.Tasks;
28
30
  import com.google.firebase.firestore.*;
29
31
  import io.invertase.firebase.common.ReactNativeFirebaseEventEmitter;
30
32
  import io.invertase.firebase.common.ReactNativeFirebaseModule;
33
+ import java.util.ArrayList;
31
34
 
32
35
  public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFirebaseModule {
33
36
  private static final String SERVICE_NAME = "FirestoreCollection";
@@ -39,8 +42,8 @@ public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFir
39
42
  }
40
43
 
41
44
  @Override
42
- public void onCatalystInstanceDestroy() {
43
- super.onCatalystInstanceDestroy();
45
+ public void invalidate() {
46
+ super.invalidate();
44
47
 
45
48
  for (int i = 0, size = collectionSnapshotListeners.size(); i < size; i++) {
46
49
  int key = collectionSnapshotListeners.keyAt(i);
@@ -193,6 +196,114 @@ public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFir
193
196
  });
194
197
  }
195
198
 
199
+ @ReactMethod
200
+ public void aggregateQuery(
201
+ String appName,
202
+ String databaseId,
203
+ String path,
204
+ String type,
205
+ ReadableArray filters,
206
+ ReadableArray orders,
207
+ ReadableMap options,
208
+ ReadableArray aggregateQueries,
209
+ Promise promise) {
210
+ FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName, databaseId);
211
+ ReactNativeFirebaseFirestoreQuery firestoreQuery =
212
+ new ReactNativeFirebaseFirestoreQuery(
213
+ appName,
214
+ databaseId,
215
+ getQueryForFirestore(firebaseFirestore, path, type),
216
+ filters,
217
+ orders,
218
+ options);
219
+
220
+ ArrayList<AggregateField> aggregateFields = new ArrayList<>();
221
+
222
+ for (int i = 0; i < aggregateQueries.size(); i++) {
223
+ ReadableMap aggregateQuery = aggregateQueries.getMap(i);
224
+ String aggregateType = aggregateQuery.getString("aggregateType");
225
+ if (aggregateType == null) aggregateType = "";
226
+ String fieldPath = aggregateQuery.getString("field");
227
+
228
+ switch (aggregateType) {
229
+ case "count":
230
+ aggregateFields.add(AggregateField.count());
231
+ break;
232
+ case "sum":
233
+ aggregateFields.add(AggregateField.sum(fieldPath));
234
+ break;
235
+ case "average":
236
+ aggregateFields.add(AggregateField.average(fieldPath));
237
+ break;
238
+ default:
239
+ rejectPromiseWithCodeAndMessage(
240
+ promise, "firestore/invalid-argument", "Invalid AggregateType: " + aggregateType);
241
+ return;
242
+ }
243
+ }
244
+ AggregateQuery firestoreAggregateQuery =
245
+ firestoreQuery.query.aggregate(
246
+ aggregateFields.get(0),
247
+ aggregateFields.subList(1, aggregateFields.size()).toArray(new AggregateField[0]));
248
+
249
+ firestoreAggregateQuery
250
+ .get(AggregateSource.SERVER)
251
+ .addOnCompleteListener(
252
+ task -> {
253
+ if (task.isSuccessful()) {
254
+ WritableMap result = Arguments.createMap();
255
+ AggregateQuerySnapshot snapshot = task.getResult();
256
+
257
+ for (int k = 0; k < aggregateQueries.size(); k++) {
258
+ ReadableMap aggQuery = aggregateQueries.getMap(k);
259
+ String aggType = aggQuery.getString("aggregateType");
260
+ if (aggType == null) aggType = "";
261
+ String field = aggQuery.getString("field");
262
+ String key = aggQuery.getString("key");
263
+
264
+ if (key == null) {
265
+ rejectPromiseWithCodeAndMessage(
266
+ promise, "firestore/invalid-argument", "key may not be null");
267
+ return;
268
+ }
269
+
270
+ switch (aggType) {
271
+ case "count":
272
+ result.putDouble(key, Long.valueOf(snapshot.getCount()).doubleValue());
273
+ break;
274
+ case "sum":
275
+ Number sum = (Number) snapshot.get(sum(field));
276
+ if (sum == null) {
277
+ rejectPromiseWithCodeAndMessage(
278
+ promise, "firestore/unknown", "sum unexpectedly null");
279
+ return;
280
+ }
281
+ result.putDouble(key, sum.doubleValue());
282
+ break;
283
+ case "average":
284
+ Number average = snapshot.get(average(field));
285
+ if (average == null) {
286
+ result.putNull(key);
287
+ } else {
288
+ result.putDouble(key, average.doubleValue());
289
+ }
290
+ break;
291
+ default:
292
+ rejectPromiseWithCodeAndMessage(
293
+ promise,
294
+ "firestore/invalid-argument",
295
+ "Invalid AggregateType: " + aggType);
296
+ return;
297
+ }
298
+ }
299
+
300
+ promise.resolve(result);
301
+ } else {
302
+ rejectPromiseFirestoreException(promise, task.getException());
303
+ }
304
+ });
305
+ }
306
+
196
307
  @ReactMethod
197
308
  public void collectionGet(
198
309
  String appName,
@@ -43,8 +43,8 @@ public class ReactNativeFirebaseFirestoreDocumentModule extends ReactNativeFireb
43
43
  }
44
44
 
45
45
  @Override
46
- public void onCatalystInstanceDestroy() {
47
- super.onCatalystInstanceDestroy();
46
+ public void invalidate() {
47
+ super.invalidate();
48
48
 
49
49
  for (int i = 0, size = documentSnapshotListeners.size(); i < size; i++) {
50
50
  int key = documentSnapshotListeners.keyAt(i);
@@ -45,7 +45,7 @@ public class ReactNativeFirebaseFirestoreTransactionModule extends ReactNativeFi
45
45
  }
46
46
 
47
47
  @Override
48
- public void onCatalystInstanceDestroy() {
48
+ public void invalidate() {
49
49
  for (int i = 0, size = transactionHandlers.size(); i < size; i++) {
50
50
  int key = transactionHandlers.keyAt(i);
51
51
  ReactNativeFirebaseFirestoreTransactionHandler transactionHandler =
@@ -57,7 +57,7 @@ public class ReactNativeFirebaseFirestoreTransactionModule extends ReactNativeFi
57
57
  }
58
58
 
59
59
  transactionHandlers.clear();
60
- super.onCatalystInstanceDestroy();
60
+ super.invalidate();
61
61
  }
62
62
 
63
63
  @ReactMethod
@@ -216,6 +216,81 @@ RCT_EXPORT_METHOD(collectionCount
216
216
  }];
217
217
  }
218
218
 
219
+ RCT_EXPORT_METHOD(aggregateQuery
220
+ : (FIRApp *)firebaseApp
221
+ : (NSString *)databaseId
222
+ : (NSString *)path
223
+ : (NSString *)type
224
+ : (NSArray *)filters
225
+ : (NSArray *)orders
226
+ : (NSDictionary *)options
227
+ : (NSArray *)aggregateQueries
228
+ : (RCTPromiseResolveBlock)resolve
229
+ : (RCTPromiseRejectBlock)reject) {
230
+ FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp
231
+ databaseId:databaseId];
232
+ FIRQuery *query = [RNFBFirestoreCommon getQueryForFirestore:firestore path:path type:type];
233
+
234
+ NSMutableArray<FIRAggregateField *> *aggregateFields =
235
+ [[NSMutableArray<FIRAggregateField *> alloc] init];
236
+
237
+ for (NSDictionary *aggregateQuery in aggregateQueries) {
238
+ NSString *aggregateType = aggregateQuery[@"aggregateType"];
239
+ NSString *fieldPath = aggregateQuery[@"field"];
240
+
241
+ if ([aggregateType isEqualToString:@"count"]) {
242
+ [aggregateFields addObject:[FIRAggregateField aggregateFieldForCount]];
243
+ } else if ([aggregateType isEqualToString:@"sum"]) {
244
+ [aggregateFields addObject:[FIRAggregateField aggregateFieldForSumOfField:fieldPath]];
245
+ } else if ([aggregateType isEqualToString:@"average"]) {
246
+ [aggregateFields addObject:[FIRAggregateField aggregateFieldForAverageOfField:fieldPath]];
247
+ } else {
248
+ NSString *reason = [@"Invalid Aggregate Type: " stringByAppendingString:aggregateType];
249
+ [RNFBFirestoreCommon
250
+ promiseRejectFirestoreException:reject
251
+ error:[NSException exceptionWithName:
252
+ @"RNFB Firestore: Invalid Aggregate Type"
253
+ reason:reason
254
+ userInfo:nil]];
255
+ return;
256
+ }
257
+ }
258
+
259
+ FIRAggregateQuery *aggregateQuery = [query aggregate:aggregateFields];
260
+
261
+ [aggregateQuery
262
+ aggregationWithSource:FIRAggregateSourceServer
263
+ completion:^(FIRAggregateQuerySnapshot *_Nullable snapshot,
264
+ NSError *_Nullable error) {
265
+ if (error) {
266
+ [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error];
267
+ } else {
268
+ NSMutableDictionary *snapshotMap = [NSMutableDictionary dictionary];
269
+
270
+ for (NSDictionary *aggregateQuery in aggregateQueries) {
271
+ NSString *aggregateType = aggregateQuery[@"aggregateType"];
272
+ NSString *fieldPath = aggregateQuery[@"field"];
273
+ NSString *key = aggregateQuery[@"key"];
274
+
275
+ if ([aggregateType isEqualToString:@"count"]) {
276
+ snapshotMap[key] = snapshot.count;
277
+ } else if ([aggregateType isEqualToString:@"sum"]) {
278
+ NSNumber *sum = [snapshot
279
+ valueForAggregateField:[FIRAggregateField
280
+ aggregateFieldForSumOfField:fieldPath]];
281
+ snapshotMap[key] = sum;
282
+ } else if ([aggregateType isEqualToString:@"average"]) {
283
+ NSNumber *average = [snapshot
284
+ valueForAggregateField:[FIRAggregateField
285
+ aggregateFieldForAverageOfField:fieldPath]];
286
+ snapshotMap[key] = (average == nil ? [NSNull null] : average);
287
+ }
288
+ }
289
+ resolve(snapshotMap);
290
+ }
291
+ }];
292
+ }
293
+
219
294
  RCT_EXPORT_METHOD(collectionGet
220
295
  : (FIRApp *)firebaseApp
221
296
  : (NSString *)databaseId
@@ -15,6 +15,8 @@
15
15
  *
16
16
  */
17
17
 
18
+ import FirestoreFieldPath, { fromDotSeparatedString } from './FirestoreFieldPath';
19
+
18
20
  export class FirestoreAggregateQuery {
19
21
  constructor(firestore, query, collectionPath, modifiers) {
20
22
  this._firestore = firestore;
@@ -36,17 +38,55 @@ export class FirestoreAggregateQuery {
36
38
  this._modifiers.orders,
37
39
  this._modifiers.options,
38
40
  )
39
- .then(data => new FirestoreAggregateQuerySnapshot(this._query, data));
41
+ .then(data => new FirestoreAggregateQuerySnapshot(this._query, data, true));
40
42
  }
41
43
  }
42
44
 
43
45
  export class FirestoreAggregateQuerySnapshot {
44
- constructor(query, data) {
46
+ constructor(query, data, isGetCountFromServer) {
45
47
  this._query = query;
46
48
  this._data = data;
49
+ this._isGetCountFromServer = isGetCountFromServer;
47
50
  }
48
51
 
49
52
  data() {
50
- return { count: this._data.count };
53
+ if (this._isGetCountFromServer) {
54
+ return { count: this._data.count };
55
+ } else {
56
+ return { ...this._data };
57
+ }
58
+ }
59
+ }
60
+
61
+ export const AggregateType = {
62
+ SUM: 'sum',
63
+ AVG: 'average',
64
+ COUNT: 'count',
65
+ };
66
+
67
+ export class AggregateField {
68
+ /** Indicates the aggregation operation of this AggregateField. */
69
+ aggregateType;
70
+ _fieldPath;
71
+
72
+ /**
73
+ * Create a new AggregateField<T>
74
+ * @param aggregateType Specifies the type of aggregation operation to perform.
75
+ * @param _fieldPath Optionally specifies the field that is aggregated.
76
+ * @internal
77
+ */
78
+ constructor(aggregateType, fieldPath) {
79
+ this.aggregateType = aggregateType;
80
+ this._fieldPath = fieldPath;
81
+ }
82
+ }
83
+
84
+ export function fieldPathFromArgument(path) {
85
+ if (path instanceof FirestoreFieldPath) {
86
+ return path;
87
+ } else if (typeof path === 'string') {
88
+ return fromDotSeparatedString(path);
89
+ } else {
90
+ throw new Error('Field path arguments must be of type `string` or `FieldPath`');
51
91
  }
52
92
  }
package/lib/index.d.ts CHANGED
@@ -921,12 +921,16 @@ export namespace FirebaseFirestoreTypes {
921
921
  /**
922
922
  * The results of executing an aggregation query.
923
923
  */
924
- export interface AggregateQuerySnapshot<T extends AggregateSpec> {
924
+ export interface AggregateQuerySnapshot<
925
+ AggregateSpecType extends AggregateSpec,
926
+ AppModelType = DocumentData,
927
+ DbModelType extends DocumentData = DocumentData,
928
+ > {
925
929
  /**
926
930
  * The underlying query over which the aggregations recorded in this
927
931
  * `AggregateQuerySnapshot` were performed.
928
932
  */
929
- get query(): Query<unknown>;
933
+ get query(): Query<AppModelType, DbModelType>;
930
934
 
931
935
  /**
932
936
  * Returns the results of the aggregations performed over the underlying
@@ -939,7 +943,7 @@ export namespace FirebaseFirestoreTypes {
939
943
  * @returns The results of the aggregations performed over the underlying
940
944
  * query.
941
945
  */
942
- data(): AggregateSpecData<T>;
946
+ data(): AggregateSpecData<AggregateSpecType>;
943
947
  }
944
948
 
945
949
  /**
@@ -10,6 +10,7 @@ import Query = FirebaseFirestoreTypes.Query;
10
10
  import FieldValue = FirebaseFirestoreTypes.FieldValue;
11
11
  import FieldPath = FirebaseFirestoreTypes.FieldPath;
12
12
  import PersistentCacheIndexManager = FirebaseFirestoreTypes.PersistentCacheIndexManager;
13
+ import AggregateQuerySnapshot = FirebaseFirestoreTypes.AggregateQuerySnapshot;
13
14
 
14
15
  /** Primitive types. */
15
16
  export type Primitive = string | number | boolean | undefined | null;
@@ -495,6 +496,75 @@ export function getCountFromServer<AppModelType, DbModelType extends DocumentDat
495
496
  >
496
497
  >;
497
498
 
499
+ /**
500
+ * Specifies a set of aggregations and their aliases.
501
+ */
502
+ interface AggregateSpec {
503
+ [field: string]: AggregateFieldType;
504
+ }
505
+
506
+ /**
507
+ * The union of all `AggregateField` types that are supported by Firestore.
508
+ */
509
+ export type AggregateFieldType =
510
+ | ReturnType<typeof sum>
511
+ | ReturnType<typeof average>
512
+ | ReturnType<typeof count>;
513
+
514
+ export function getAggregateFromServer<
515
+ AggregateSpecType extends AggregateSpec,
516
+ AppModelType,
517
+ DbModelType extends DocumentData,
518
+ >(
519
+ query: Query<AppModelType, DbModelType>,
520
+ aggregateSpec: AggregateSpecType,
521
+ ): Promise<AggregateQuerySnapshot<AggregateSpecType, AppModelType, DbModelType>>;
522
+
523
+ /**
524
+ * Create an AggregateField object that can be used to compute the sum of
525
+ * a specified field over a range of documents in the result set of a query.
526
+ * @param field Specifies the field to sum across the result set.
527
+ */
528
+ export function sum(field: string | FieldPath): AggregateField<number>;
529
+
530
+ /**
531
+ * Create an AggregateField object that can be used to compute the average of
532
+ * a specified field over a range of documents in the result set of a query.
533
+ * @param field Specifies the field to average across the result set.
534
+ */
535
+ export function average(field: string | FieldPath): AggregateField<number | null>;
536
+
537
+ /**
538
+ * Create an AggregateField object that can be used to compute the count of
539
+ * documents in the result set of a query.
540
+ */
541
+ export function count(): AggregateField<number>;
542
+
543
+ /**
544
+ * Represents an aggregation that can be performed by Firestore.
545
+ */
546
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
547
+ export class AggregateField<T> {
548
+ /** A type string to uniquely identify instances of this class. */
549
+ readonly type = 'AggregateField';
550
+
551
+ /** Indicates the aggregation operation of this AggregateField. */
552
+ readonly aggregateType: AggregateType;
553
+
554
+ /**
555
+ * Create a new AggregateField<T>
556
+ * @param aggregateType Specifies the type of aggregation operation to perform.
557
+ * @param _internalFieldPath Optionally specifies the field that is aggregated.
558
+ * @internal
559
+ */
560
+ constructor(
561
+ aggregateType: AggregateType = 'count',
562
+ readonly _internalFieldPath?: InternalFieldPath,
563
+ ) {
564
+ this.aggregateType = aggregateType;
565
+ }
566
+ }
567
+
498
568
  /**
499
569
  * Represents the task of loading a Firestore bundle.
500
570
  * It provides progress of bundle loading, as well as task completion and error events.
@@ -13,6 +13,14 @@
13
13
  */
14
14
 
15
15
  import { firebase } from '../index';
16
+ import { isObject } from '@react-native-firebase/app/lib/common';
17
+ import {
18
+ FirestoreAggregateQuerySnapshot,
19
+ AggregateField,
20
+ AggregateType,
21
+ fieldPathFromArgument,
22
+ } from '../FirestoreAggregate';
23
+ import FirestoreQuery from '../FirestoreQuery';
16
24
 
17
25
  /**
18
26
  * @param {FirebaseApp?} app
@@ -192,6 +200,91 @@ export function getCountFromServer(query) {
192
200
  return query.count().get();
193
201
  }
194
202
 
203
+ export function getAggregateFromServer(query, aggregateSpec) {
204
+ if (!(query instanceof FirestoreQuery)) {
205
+ throw new Error(
206
+ '`getAggregateFromServer(*, aggregateSpec)` `query` must be an instance of `FirestoreQuery`',
207
+ );
208
+ }
209
+
210
+ if (!isObject(aggregateSpec)) {
211
+ throw new Error('`getAggregateFromServer(query, *)` `aggregateSpec` must be an object');
212
+ } else {
213
+ const containsOneAggregateField = Object.values(aggregateSpec).find(
214
+ value => value instanceof AggregateField,
215
+ );
216
+
217
+ if (!containsOneAggregateField) {
218
+ throw new Error(
219
+ '`getAggregateFromServer(query, *)` `aggregateSpec` must contain at least one `AggregateField`',
220
+ );
221
+ }
222
+ }
223
+ const aggregateQueries = [];
224
+ for (const key in aggregateSpec) {
225
+ if (aggregateSpec.hasOwnProperty(key)) {
226
+ const aggregateField = aggregateSpec[key];
227
+ // we ignore any fields that are not `AggregateField`
228
+ if (aggregateField instanceof AggregateField) {
229
+ switch (aggregateField.aggregateType) {
230
+ case AggregateType.AVG:
231
+ case AggregateType.SUM:
232
+ case AggregateType.COUNT:
233
+ const aggregateQuery = {
234
+ aggregateType: aggregateField.aggregateType,
235
+ field:
236
+ aggregateField._fieldPath === null ? null : aggregateField._fieldPath._toPath(),
237
+ key,
238
+ };
239
+ aggregateQueries.push(aggregateQuery);
240
+ break;
241
+ default:
242
+ throw new Error(
243
+ `'AggregateField' has an an unknown 'AggregateType' : ${aggregateField.aggregateType}`,
244
+ );
245
+ }
246
+ }
247
+ }
248
+ }
249
+
250
+ return query._firestore.native
251
+ .aggregateQuery(
252
+ query._collectionPath.relativeName,
253
+ query._modifiers.type,
254
+ query._modifiers.filters,
255
+ query._modifiers.orders,
256
+ query._modifiers.options,
257
+ aggregateQueries,
258
+ )
259
+ .then(data => new FirestoreAggregateQuerySnapshot(query, data, false));
260
+ }
261
+
262
+ /**
263
+ * Create an AggregateField object that can be used to compute the sum of
264
+ * a specified field over a range of documents in the result set of a query.
265
+ * @param field Specifies the field to sum across the result set.
266
+ */
267
+ export function sum(field) {
268
+ return new AggregateField(AggregateType.SUM, fieldPathFromArgument(field));
269
+ }
270
+
271
+ /**
272
+ * Create an AggregateField object that can be used to compute the average of
273
+ * a specified field over a range of documents in the result set of a query.
274
+ * @param field Specifies the field to average across the result set.
275
+ */
276
+ export function average(field) {
277
+ return new AggregateField(AggregateType.AVG, fieldPathFromArgument(field));
278
+ }
279
+
280
+ /**
281
+ * Create an AggregateField object that can be used to compute the count of
282
+ * documents in the result set of a query.
283
+ */
284
+ export function count() {
285
+ return new AggregateField(AggregateType.COUNT, null);
286
+ }
287
+
195
288
  /**
196
289
  * @param {Firestore} firestore
197
290
  * @param {ReadableStream<Uint8Array> | ArrayBuffer | string} bundleData
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- module.exports = '21.4.0';
2
+ module.exports = '21.5.0';
@@ -11,6 +11,10 @@ import {
11
11
  getDoc,
12
12
  getDocs,
13
13
  getCount,
14
+ getAggregate,
15
+ count,
16
+ average,
17
+ sum,
14
18
  deleteDoc,
15
19
  setDoc,
16
20
  updateDoc,
@@ -215,6 +219,44 @@ export default {
215
219
  });
216
220
  },
217
221
 
222
+ aggregateQuery(appName, databaseId, path, type, filters, orders, options, aggregateQueries) {
223
+ return guard(async () => {
224
+ const firestore = getCachedFirestoreInstance(appName, databaseId);
225
+ const queryRef =
226
+ type === 'collectionGroup' ? collectionGroup(firestore, path) : collection(firestore, path);
227
+ const query = buildQuery(queryRef, filters, orders, options);
228
+ const aggregateSpec = {};
229
+
230
+ for (let i = 0; i < aggregateQueries.length; i++) {
231
+ const aggregateQuery = aggregateQueries[i];
232
+ const { aggregateType, field, key } = aggregateQuery;
233
+
234
+ switch (aggregateType) {
235
+ case 'count':
236
+ aggregateSpec[key] = count();
237
+ break;
238
+ case 'average':
239
+ aggregateSpec[key] = average(field);
240
+ break;
241
+ case 'sum':
242
+ aggregateSpec[key] = sum(field);
243
+ break;
244
+ }
245
+ }
246
+ const result = await getAggregate(query, aggregateSpec);
247
+
248
+ const data = result.data();
249
+ const response = {};
250
+ for (let i = 0; i < aggregateQueries.length; i++) {
251
+ const aggregateQuery = aggregateQueries[i];
252
+ const { key } = aggregateQuery;
253
+ response[key] = data[key];
254
+ }
255
+
256
+ return response;
257
+ });
258
+ },
259
+
218
260
  /**
219
261
  * Get a collection from Firestore.
220
262
  * @param {string} appName - The app name.
package/lib/web/query.js CHANGED
@@ -108,5 +108,5 @@ function getFilterConstraint(filter) {
108
108
  throw new Error('Invalid filter operator');
109
109
  }
110
110
 
111
- throw new Error('Invaldi filter.');
111
+ throw new Error('Invalid filter.');
112
112
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/firestore",
3
- "version": "21.4.0",
3
+ "version": "21.5.0",
4
4
  "author": "Invertase <oss@invertase.io> (http://invertase.io)",
5
5
  "description": "React Native Firebase - Cloud Firestore is a NoSQL cloud database to store and sync data between your React Native application and Firebase's database. The API matches the Firebase Web SDK whilst taking advantage of the native SDKs performance and offline capabilities.",
6
6
  "main": "lib/index.js",
@@ -27,10 +27,10 @@
27
27
  "firestore"
28
28
  ],
29
29
  "peerDependencies": {
30
- "@react-native-firebase/app": "21.4.0"
30
+ "@react-native-firebase/app": "21.5.0"
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  },
35
- "gitHead": "227e1ed54cf668cbc701c0ed2542b8ec66957e1e"
35
+ "gitHead": "ca6b846408d5f1773adb84b94c6dcb8c6a7c658a"
36
36
  }