@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 +16 -0
- package/RNFBFirestore.podspec +1 -1
- package/__tests__/firestore.test.ts +20 -0
- package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreCollectionModule.java +113 -2
- package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreDocumentModule.java +2 -2
- package/android/src/reactnative/java/io/invertase/firebase/firestore/ReactNativeFirebaseFirestoreTransactionModule.java +2 -2
- package/ios/RNFBFirestore/RNFBFirestoreCollectionModule.m +75 -0
- package/lib/FirestoreAggregate.js +43 -3
- package/lib/index.d.ts +7 -3
- package/lib/modular/index.d.ts +70 -0
- package/lib/modular/index.js +93 -0
- package/lib/version.js +1 -1
- package/lib/web/RNFBFirestoreModule.js +42 -0
- package/lib/web/query.js +1 -1
- package/package.json +3 -3
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
|
package/RNFBFirestore.podspec
CHANGED
|
@@ -39,7 +39,7 @@ Pod::Spec.new do |s|
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
# Firebase dependencies
|
|
42
|
-
s.dependency 'Firebase/Firestore',
|
|
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
|
|
43
|
-
super.
|
|
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
|
|
47
|
-
super.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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<
|
|
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<
|
|
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<
|
|
946
|
+
data(): AggregateSpecData<AggregateSpecType>;
|
|
943
947
|
}
|
|
944
948
|
|
|
945
949
|
/**
|
package/lib/modular/index.d.ts
CHANGED
|
@@ -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.
|
package/lib/modular/index.js
CHANGED
|
@@ -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.
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-firebase/firestore",
|
|
3
|
-
"version": "21.
|
|
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.
|
|
30
|
+
"@react-native-firebase/app": "21.5.0"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public"
|
|
34
34
|
},
|
|
35
|
-
"gitHead": "
|
|
35
|
+
"gitHead": "ca6b846408d5f1773adb84b94c6dcb8c6a7c658a"
|
|
36
36
|
}
|