@react-native-firebase/firestore 14.7.0 → 14.9.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,20 @@
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
+ # [14.9.0](https://github.com/invertase/react-native-firebase/compare/v14.8.1...v14.9.0) (2022-04-27)
7
+
8
+ ### Features
9
+
10
+ - **firestore:** named query and data bundle APIs ([#6199](https://github.com/invertase/react-native-firebase/issues/6199)) ([96591e0](https://github.com/invertase/react-native-firebase/commit/96591e0dac957383c503e94fbf7bf0379d5569f2))
11
+
12
+ ## [14.8.1](https://github.com/invertase/react-native-firebase/compare/v14.8.0...v14.8.1) (2022-04-25)
13
+
14
+ **Note:** Version bump only for package @react-native-firebase/firestore
15
+
16
+ # [14.8.0](https://github.com/invertase/react-native-firebase/compare/v14.7.0...v14.8.0) (2022-04-19)
17
+
18
+ **Note:** Version bump only for package @react-native-firebase/firestore
19
+
6
20
  # [14.7.0](https://github.com/invertase/react-native-firebase/compare/v14.6.0...v14.7.0) (2022-03-23)
7
21
 
8
22
  **Note:** Version bump only for package @react-native-firebase/firestore
@@ -329,4 +329,46 @@ describe('Storage', function () {
329
329
  });
330
330
  });
331
331
  });
332
+
333
+ describe('loadBundle()', function () {
334
+ it('throws if bundle is not a string', async function () {
335
+ try {
336
+ // @ts-ignore the type is incorrect *on purpose* to test type checking in javascript
337
+ firebase.firestore().loadBundle(123);
338
+ return Promise.reject(new Error('Did not throw an Error.'));
339
+ } catch (e: any) {
340
+ return expect(e.message).toContain("'bundle' must be a string value");
341
+ }
342
+ });
343
+
344
+ it('throws if bundle is empty string', async function () {
345
+ try {
346
+ firebase.firestore().loadBundle('');
347
+ return Promise.reject(new Error('Did not throw an Error.'));
348
+ } catch (e: any) {
349
+ return expect(e.message).toContain("'bundle' must be a non-empty string");
350
+ }
351
+ });
352
+ });
353
+
354
+ describe('namedQuery()', function () {
355
+ it('throws if queryName is not a string', async function () {
356
+ try {
357
+ // @ts-ignore the type is incorrect *on purpose* to test type checking in javascript
358
+ firebase.firestore().namedQuery(123);
359
+ return Promise.reject(new Error('Did not throw an Error.'));
360
+ } catch (e: any) {
361
+ return expect(e.message).toContain("'queryName' must be a string value");
362
+ }
363
+ });
364
+
365
+ it('throws if queryName is empty string', async function () {
366
+ try {
367
+ firebase.firestore().namedQuery('');
368
+ return Promise.reject(new Error('Did not throw an Error.'));
369
+ } catch (e: any) {
370
+ return expect(e.message).toContain("'queryName' must be a non-empty string");
371
+ }
372
+ });
373
+ });
332
374
  });
@@ -24,8 +24,10 @@ import android.content.Context;
24
24
  import com.google.android.gms.tasks.Task;
25
25
  import com.google.android.gms.tasks.Tasks;
26
26
  import com.google.firebase.firestore.FirebaseFirestore;
27
+ import com.google.firebase.firestore.LoadBundleTask;
27
28
  import io.invertase.firebase.common.UniversalFirebaseModule;
28
29
  import io.invertase.firebase.common.UniversalFirebasePreferences;
30
+ import java.nio.charset.StandardCharsets;
29
31
  import java.util.Map;
30
32
  import java.util.Objects;
31
33
 
@@ -104,6 +106,11 @@ public class UniversalFirebaseFirestoreModule extends UniversalFirebaseModule {
104
106
  });
105
107
  }
106
108
 
109
+ LoadBundleTask loadBundle(String appName, String bundle) {
110
+ byte[] bundleData = bundle.getBytes(StandardCharsets.UTF_8);
111
+ return getFirestoreForApp(appName).loadBundle(bundleData);
112
+ }
113
+
107
114
  Task<Void> clearPersistence(String appName) {
108
115
  return getFirestoreForApp(appName).clearPersistence();
109
116
  }
@@ -50,6 +50,41 @@ public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFir
50
50
  collectionSnapshotListeners.clear();
51
51
  }
52
52
 
53
+ @ReactMethod
54
+ public void namedQueryOnSnapshot(
55
+ String appName,
56
+ String queryName,
57
+ String type,
58
+ ReadableArray filters,
59
+ ReadableArray orders,
60
+ ReadableMap options,
61
+ int listenerId,
62
+ ReadableMap listenerOptions) {
63
+ if (collectionSnapshotListeners.get(listenerId) != null) {
64
+ return;
65
+ }
66
+
67
+ FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName);
68
+ firebaseFirestore
69
+ .getNamedQuery(queryName)
70
+ .addOnCompleteListener(
71
+ task -> {
72
+ if (task.isSuccessful()) {
73
+ Query query = task.getResult();
74
+ if (query == null) {
75
+ sendOnSnapshotError(appName, listenerId, new NullPointerException());
76
+ } else {
77
+ ReactNativeFirebaseFirestoreQuery firestoreQuery =
78
+ new ReactNativeFirebaseFirestoreQuery(
79
+ appName, query, filters, orders, options);
80
+ handleQueryOnSnapshot(firestoreQuery, appName, listenerId, listenerOptions);
81
+ }
82
+ } else {
83
+ sendOnSnapshotError(appName, listenerId, task.getException());
84
+ }
85
+ });
86
+ }
87
+
53
88
  @ReactMethod
54
89
  public void collectionOnSnapshot(
55
90
  String appName,
@@ -69,34 +104,7 @@ public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFir
69
104
  new ReactNativeFirebaseFirestoreQuery(
70
105
  appName, getQueryForFirestore(firebaseFirestore, path, type), filters, orders, options);
71
106
 
72
- MetadataChanges metadataChanges;
73
-
74
- if (listenerOptions != null
75
- && listenerOptions.hasKey("includeMetadataChanges")
76
- && listenerOptions.getBoolean("includeMetadataChanges")) {
77
- metadataChanges = MetadataChanges.INCLUDE;
78
- } else {
79
- metadataChanges = MetadataChanges.EXCLUDE;
80
- }
81
-
82
- final EventListener<QuerySnapshot> listener =
83
- (querySnapshot, exception) -> {
84
- if (exception != null) {
85
- ListenerRegistration listenerRegistration = collectionSnapshotListeners.get(listenerId);
86
- if (listenerRegistration != null) {
87
- listenerRegistration.remove();
88
- collectionSnapshotListeners.remove(listenerId);
89
- }
90
- sendOnSnapshotError(appName, listenerId, exception);
91
- } else {
92
- sendOnSnapshotEvent(appName, listenerId, querySnapshot, metadataChanges);
93
- }
94
- };
95
-
96
- ListenerRegistration listenerRegistration =
97
- firestoreQuery.query.addSnapshotListener(metadataChanges, listener);
98
-
99
- collectionSnapshotListeners.put(listenerId, listenerRegistration);
107
+ handleQueryOnSnapshot(firestoreQuery, appName, listenerId, listenerOptions);
100
108
  }
101
109
 
102
110
  @ReactMethod
@@ -109,6 +117,37 @@ public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFir
109
117
  }
110
118
  }
111
119
 
120
+ @ReactMethod
121
+ public void namedQueryGet(
122
+ String appName,
123
+ String queryName,
124
+ String type,
125
+ ReadableArray filters,
126
+ ReadableArray orders,
127
+ ReadableMap options,
128
+ ReadableMap getOptions,
129
+ Promise promise) {
130
+ FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName);
131
+ firebaseFirestore
132
+ .getNamedQuery(queryName)
133
+ .addOnCompleteListener(
134
+ task -> {
135
+ if (task.isSuccessful()) {
136
+ Query query = task.getResult();
137
+ if (query == null) {
138
+ rejectPromiseFirestoreException(promise, new NullPointerException());
139
+ } else {
140
+ ReactNativeFirebaseFirestoreQuery firestoreQuery =
141
+ new ReactNativeFirebaseFirestoreQuery(
142
+ appName, query, filters, orders, options);
143
+ handleQueryGet(firestoreQuery, getSource(getOptions), promise);
144
+ }
145
+ } else {
146
+ rejectPromiseFirestoreException(promise, task.getException());
147
+ }
148
+ });
149
+ }
150
+
112
151
  @ReactMethod
113
152
  public void collectionGet(
114
153
  String appName,
@@ -120,26 +159,50 @@ public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFir
120
159
  ReadableMap getOptions,
121
160
  Promise promise) {
122
161
  FirebaseFirestore firebaseFirestore = getFirestoreForApp(appName);
123
- ReactNativeFirebaseFirestoreQuery query =
162
+ ReactNativeFirebaseFirestoreQuery firestoreQuery =
124
163
  new ReactNativeFirebaseFirestoreQuery(
125
164
  appName, getQueryForFirestore(firebaseFirestore, path, type), filters, orders, options);
165
+ handleQueryGet(firestoreQuery, getSource(getOptions), promise);
166
+ }
126
167
 
127
- Source source;
168
+ private void handleQueryOnSnapshot(
169
+ ReactNativeFirebaseFirestoreQuery firestoreQuery,
170
+ String appName,
171
+ int listenerId,
172
+ ReadableMap listenerOptions) {
173
+ MetadataChanges metadataChanges;
128
174
 
129
- if (getOptions != null && getOptions.hasKey("source")) {
130
- String optionsSource = getOptions.getString("source");
131
- if ("server".equals(optionsSource)) {
132
- source = Source.SERVER;
133
- } else if ("cache".equals(optionsSource)) {
134
- source = Source.CACHE;
135
- } else {
136
- source = Source.DEFAULT;
137
- }
175
+ if (listenerOptions != null
176
+ && listenerOptions.hasKey("includeMetadataChanges")
177
+ && listenerOptions.getBoolean("includeMetadataChanges")) {
178
+ metadataChanges = MetadataChanges.INCLUDE;
138
179
  } else {
139
- source = Source.DEFAULT;
180
+ metadataChanges = MetadataChanges.EXCLUDE;
140
181
  }
141
182
 
142
- query
183
+ final EventListener<QuerySnapshot> listener =
184
+ (querySnapshot, exception) -> {
185
+ if (exception != null) {
186
+ ListenerRegistration listenerRegistration = collectionSnapshotListeners.get(listenerId);
187
+ if (listenerRegistration != null) {
188
+ listenerRegistration.remove();
189
+ collectionSnapshotListeners.remove(listenerId);
190
+ }
191
+ sendOnSnapshotError(appName, listenerId, exception);
192
+ } else {
193
+ sendOnSnapshotEvent(appName, listenerId, querySnapshot, metadataChanges);
194
+ }
195
+ };
196
+
197
+ ListenerRegistration listenerRegistration =
198
+ firestoreQuery.query.addSnapshotListener(metadataChanges, listener);
199
+
200
+ collectionSnapshotListeners.put(listenerId, listenerRegistration);
201
+ }
202
+
203
+ private void handleQueryGet(
204
+ ReactNativeFirebaseFirestoreQuery firestoreQuery, Source source, Promise promise) {
205
+ firestoreQuery
143
206
  .get(getExecutor(), source)
144
207
  .addOnCompleteListener(
145
208
  task -> {
@@ -202,4 +265,23 @@ public class ReactNativeFirebaseFirestoreCollectionModule extends ReactNativeFir
202
265
  new ReactNativeFirebaseFirestoreEvent(
203
266
  ReactNativeFirebaseFirestoreEvent.COLLECTION_EVENT_SYNC, body, appName, listenerId));
204
267
  }
268
+
269
+ private Source getSource(ReadableMap getOptions) {
270
+ Source source;
271
+
272
+ if (getOptions != null && getOptions.hasKey("source")) {
273
+ String optionsSource = getOptions.getString("source");
274
+ if ("server".equals(optionsSource)) {
275
+ source = Source.SERVER;
276
+ } else if ("cache".equals(optionsSource)) {
277
+ source = Source.CACHE;
278
+ } else {
279
+ source = Source.DEFAULT;
280
+ }
281
+ } else {
282
+ source = Source.DEFAULT;
283
+ }
284
+
285
+ return source;
286
+ }
205
287
  }
@@ -20,11 +20,14 @@ package io.invertase.firebase.firestore;
20
20
  import static io.invertase.firebase.common.RCTConvertFirebase.toHashMap;
21
21
  import static io.invertase.firebase.firestore.ReactNativeFirebaseFirestoreCommon.rejectPromiseFirestoreException;
22
22
 
23
+ import com.facebook.react.bridge.Arguments;
23
24
  import com.facebook.react.bridge.Promise;
24
25
  import com.facebook.react.bridge.ReactApplicationContext;
25
26
  import com.facebook.react.bridge.ReactMethod;
26
27
  import com.facebook.react.bridge.ReadableMap;
28
+ import com.facebook.react.bridge.WritableMap;
27
29
  import com.google.firebase.firestore.FirebaseFirestore;
30
+ import com.google.firebase.firestore.LoadBundleTaskProgress;
28
31
  import io.invertase.firebase.common.ReactNativeFirebaseModule;
29
32
 
30
33
  public class ReactNativeFirebaseFirestoreModule extends ReactNativeFirebaseModule {
@@ -45,6 +48,21 @@ public class ReactNativeFirebaseFirestoreModule extends ReactNativeFirebaseModul
45
48
  }
46
49
  }
47
50
 
51
+ @ReactMethod
52
+ public void loadBundle(String appName, String bundle, Promise promise) {
53
+ module
54
+ .loadBundle(appName, bundle)
55
+ .addOnCompleteListener(
56
+ task -> {
57
+ if (task.isSuccessful()) {
58
+ LoadBundleTaskProgress progress = task.getResult();
59
+ promise.resolve(taskProgressToWritableMap(progress));
60
+ } else {
61
+ rejectPromiseFirestoreException(promise, task.getException());
62
+ }
63
+ });
64
+ }
65
+
48
66
  @ReactMethod
49
67
  public void clearPersistence(String appName, Promise promise) {
50
68
  module
@@ -142,4 +160,28 @@ public class ReactNativeFirebaseFirestoreModule extends ReactNativeFirebaseModul
142
160
  }
143
161
  });
144
162
  }
163
+
164
+ private WritableMap taskProgressToWritableMap(LoadBundleTaskProgress progress) {
165
+ WritableMap writableMap = Arguments.createMap();
166
+ writableMap.putDouble("bytesLoaded", progress.getBytesLoaded());
167
+ writableMap.putInt("documentsLoaded", progress.getDocumentsLoaded());
168
+ writableMap.putDouble("totalBytes", progress.getTotalBytes());
169
+ writableMap.putInt("totalDocuments", progress.getTotalDocuments());
170
+
171
+ LoadBundleTaskProgress.TaskState taskState = progress.getTaskState();
172
+ String convertedState = "Running";
173
+ switch (taskState) {
174
+ case RUNNING:
175
+ convertedState = "Running";
176
+ break;
177
+ case SUCCESS:
178
+ convertedState = "Success";
179
+ break;
180
+ case ERROR:
181
+ convertedState = "Error";
182
+ break;
183
+ }
184
+ writableMap.putString("taskState", convertedState);
185
+ return writableMap;
186
+ }
145
187
  }
@@ -61,6 +61,42 @@ RCT_EXPORT_MODULE();
61
61
  #pragma mark -
62
62
  #pragma mark Firebase Firestore Methods
63
63
 
64
+ RCT_EXPORT_METHOD(namedQueryOnSnapshot
65
+ : (FIRApp *)firebaseApp
66
+ : (NSString *)name
67
+ : (NSString *)type
68
+ : (NSArray *)filters
69
+ : (NSArray *)orders
70
+ : (NSDictionary *)options
71
+ : (nonnull NSNumber *)listenerId
72
+ : (NSDictionary *)listenerOptions) {
73
+ if (collectionSnapshotListeners[listenerId]) {
74
+ return;
75
+ }
76
+
77
+ FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp];
78
+ [[FIRFirestore firestore] getQueryNamed:name
79
+ completion:^(FIRQuery *query) {
80
+ if (query == nil) {
81
+ [self sendSnapshotError:firebaseApp
82
+ listenerId:listenerId
83
+ error:nil];
84
+ return;
85
+ }
86
+
87
+ RNFBFirestoreQuery *firestoreQuery =
88
+ [[RNFBFirestoreQuery alloc] initWithModifiers:firestore
89
+ query:query
90
+ filters:filters
91
+ orders:orders
92
+ options:options];
93
+ [self handleQueryOnSnapshot:firebaseApp
94
+ firestoreQuery:firestoreQuery
95
+ listenerId:listenerId
96
+ listenerOptions:listenerOptions];
97
+ }];
98
+ }
99
+
64
100
  RCT_EXPORT_METHOD(collectionOnSnapshot
65
101
  : (FIRApp *)firebaseApp
66
102
  : (NSString *)path
@@ -82,33 +118,10 @@ RCT_EXPORT_METHOD(collectionOnSnapshot
82
118
  filters:filters
83
119
  orders:orders
84
120
  options:options];
85
-
86
- BOOL includeMetadataChanges = NO;
87
- if (listenerOptions[KEY_INCLUDE_METADATA_CHANGES] != nil) {
88
- includeMetadataChanges = [listenerOptions[KEY_INCLUDE_METADATA_CHANGES] boolValue];
89
- }
90
-
91
- __weak RNFBFirestoreCollectionModule *weakSelf = self;
92
- id listenerBlock = ^(FIRQuerySnapshot *snapshot, NSError *error) {
93
- if (error) {
94
- id<FIRListenerRegistration> listener = collectionSnapshotListeners[listenerId];
95
- if (listener) {
96
- [listener remove];
97
- [collectionSnapshotListeners removeObjectForKey:listenerId];
98
- }
99
- [weakSelf sendSnapshotError:firebaseApp listenerId:listenerId error:error];
100
- } else {
101
- [weakSelf sendSnapshotEvent:firebaseApp
102
- listenerId:listenerId
103
- snapshot:snapshot
104
- includeMetadataChanges:includeMetadataChanges];
105
- }
106
- };
107
-
108
- id<FIRListenerRegistration> listener = [[firestoreQuery instance]
109
- addSnapshotListenerWithIncludeMetadataChanges:includeMetadataChanges
110
- listener:listenerBlock];
111
- collectionSnapshotListeners[listenerId] = listener;
121
+ [self handleQueryOnSnapshot:firebaseApp
122
+ firestoreQuery:firestoreQuery
123
+ listenerId:listenerId
124
+ listenerOptions:listenerOptions];
112
125
  }
113
126
 
114
127
  RCT_EXPORT_METHOD(collectionOffSnapshot : (FIRApp *)firebaseApp : (nonnull NSNumber *)listenerId) {
@@ -119,6 +132,39 @@ RCT_EXPORT_METHOD(collectionOffSnapshot : (FIRApp *)firebaseApp : (nonnull NSNum
119
132
  }
120
133
  }
121
134
 
135
+ RCT_EXPORT_METHOD(namedQueryGet
136
+ : (FIRApp *)firebaseApp
137
+ : (NSString *)name
138
+ : (NSString *)type
139
+ : (NSArray *)filters
140
+ : (NSArray *)orders
141
+ : (NSDictionary *)options
142
+ : (NSDictionary *)getOptions
143
+ : (RCTPromiseResolveBlock)resolve
144
+ : (RCTPromiseRejectBlock)reject) {
145
+ FIRFirestore *firestore = [RNFBFirestoreCommon getFirestoreForApp:firebaseApp];
146
+ [[FIRFirestore firestore]
147
+ getQueryNamed:name
148
+ completion:^(FIRQuery *query) {
149
+ if (query == nil) {
150
+ return [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:nil];
151
+ }
152
+
153
+ RNFBFirestoreQuery *firestoreQuery =
154
+ [[RNFBFirestoreQuery alloc] initWithModifiers:firestore
155
+ query:query
156
+ filters:filters
157
+ orders:orders
158
+ options:options];
159
+ FIRFirestoreSource source = [self getSource:getOptions];
160
+ [self handleQueryGet:firebaseApp
161
+ firestoreQuery:firestoreQuery
162
+ source:source
163
+ resolve:resolve
164
+ reject:reject];
165
+ }];
166
+ }
167
+
122
168
  RCT_EXPORT_METHOD(collectionGet
123
169
  : (FIRApp *)firebaseApp
124
170
  : (NSString *)path
@@ -137,21 +183,51 @@ RCT_EXPORT_METHOD(collectionGet
137
183
  filters:filters
138
184
  orders:orders
139
185
  options:options];
186
+ FIRFirestoreSource source = [self getSource:getOptions];
187
+ [self handleQueryGet:firebaseApp
188
+ firestoreQuery:firestoreQuery
189
+ source:source
190
+ resolve:resolve
191
+ reject:reject];
192
+ }
140
193
 
141
- FIRFirestoreSource source;
194
+ - (void)handleQueryOnSnapshot:(FIRApp *)firebaseApp
195
+ firestoreQuery:(RNFBFirestoreQuery *)firestoreQuery
196
+ listenerId:(nonnull NSNumber *)listenerId
197
+ listenerOptions:(NSDictionary *)listenerOptions {
198
+ BOOL includeMetadataChanges = NO;
199
+ if (listenerOptions[KEY_INCLUDE_METADATA_CHANGES] != nil) {
200
+ includeMetadataChanges = [listenerOptions[KEY_INCLUDE_METADATA_CHANGES] boolValue];
201
+ }
142
202
 
143
- if (getOptions[@"source"]) {
144
- if ([getOptions[@"source"] isEqualToString:@"server"]) {
145
- source = FIRFirestoreSourceServer;
146
- } else if ([getOptions[@"source"] isEqualToString:@"cache"]) {
147
- source = FIRFirestoreSourceCache;
203
+ __weak RNFBFirestoreCollectionModule *weakSelf = self;
204
+ id listenerBlock = ^(FIRQuerySnapshot *snapshot, NSError *error) {
205
+ if (error) {
206
+ id<FIRListenerRegistration> listener = collectionSnapshotListeners[listenerId];
207
+ if (listener) {
208
+ [listener remove];
209
+ [collectionSnapshotListeners removeObjectForKey:listenerId];
210
+ }
211
+ [weakSelf sendSnapshotError:firebaseApp listenerId:listenerId error:error];
148
212
  } else {
149
- source = FIRFirestoreSourceDefault;
213
+ [weakSelf sendSnapshotEvent:firebaseApp
214
+ listenerId:listenerId
215
+ snapshot:snapshot
216
+ includeMetadataChanges:includeMetadataChanges];
150
217
  }
151
- } else {
152
- source = FIRFirestoreSourceDefault;
153
- }
218
+ };
154
219
 
220
+ id<FIRListenerRegistration> listener = [[firestoreQuery instance]
221
+ addSnapshotListenerWithIncludeMetadataChanges:includeMetadataChanges
222
+ listener:listenerBlock];
223
+ collectionSnapshotListeners[listenerId] = listener;
224
+ }
225
+
226
+ - (void)handleQueryGet:(FIRApp *)firebaseApp
227
+ firestoreQuery:(RNFBFirestoreQuery *)firestoreQuery
228
+ source:(FIRFirestoreSource)source
229
+ resolve:(RCTPromiseResolveBlock)resolve
230
+ reject:(RCTPromiseRejectBlock)reject {
155
231
  [[firestoreQuery instance]
156
232
  getDocumentsWithSource:source
157
233
  completion:^(FIRQuerySnapshot *snapshot, NSError *error) {
@@ -209,4 +285,22 @@ RCT_EXPORT_METHOD(collectionGet
209
285
  }];
210
286
  }
211
287
 
288
+ - (FIRFirestoreSource)getSource:(NSDictionary *)getOptions {
289
+ FIRFirestoreSource source;
290
+
291
+ if (getOptions[@"source"]) {
292
+ if ([getOptions[@"source"] isEqualToString:@"server"]) {
293
+ source = FIRFirestoreSourceServer;
294
+ } else if ([getOptions[@"source"] isEqualToString:@"cache"]) {
295
+ source = FIRFirestoreSourceCache;
296
+ } else {
297
+ source = FIRFirestoreSourceDefault;
298
+ }
299
+ } else {
300
+ source = FIRFirestoreSourceDefault;
301
+ }
302
+
303
+ return source;
304
+ }
305
+
212
306
  @end
@@ -107,6 +107,23 @@ RCT_EXPORT_METHOD(settings
107
107
  resolve([NSNull null]);
108
108
  }
109
109
 
110
+ RCT_EXPORT_METHOD(loadBundle
111
+ : (FIRApp *)firebaseApp
112
+ : (nonnull NSString *)bundle
113
+ : (RCTPromiseResolveBlock)resolve
114
+ : (RCTPromiseRejectBlock)reject) {
115
+ NSData *bundleData = [bundle dataUsingEncoding:NSUTF8StringEncoding];
116
+ [[RNFBFirestoreCommon getFirestoreForApp:firebaseApp]
117
+ loadBundle:bundleData
118
+ completion:^(FIRLoadBundleTaskProgress *progress, NSError *error) {
119
+ if (error) {
120
+ [RNFBFirestoreCommon promiseRejectFirestoreException:reject error:error];
121
+ } else {
122
+ resolve([self taskProgressToDictionary:progress]);
123
+ }
124
+ }];
125
+ }
126
+
110
127
  RCT_EXPORT_METHOD(clearPersistence
111
128
  : (FIRApp *)firebaseApp
112
129
  : (RCTPromiseResolveBlock)resolve
@@ -164,4 +181,27 @@ RCT_EXPORT_METHOD(terminate
164
181
  }];
165
182
  }
166
183
 
184
+ - (NSMutableDictionary *)taskProgressToDictionary:(FIRLoadBundleTaskProgress *)progress {
185
+ NSMutableDictionary *progressMap = [[NSMutableDictionary alloc] init];
186
+ progressMap[@"bytesLoaded"] = @(progress.bytesLoaded);
187
+ progressMap[@"documentsLoaded"] = @(progress.documentsLoaded);
188
+ progressMap[@"totalBytes"] = @(progress.totalBytes);
189
+ progressMap[@"totalDocuments"] = @(progress.totalDocuments);
190
+
191
+ NSString *state;
192
+ switch (progress.state) {
193
+ case FIRLoadBundleTaskStateError:
194
+ state = @"Error";
195
+ break;
196
+ case FIRLoadBundleTaskStateSuccess:
197
+ state = @"Success";
198
+ break;
199
+ case FIRLoadBundleTaskStateInProgress:
200
+ state = @"Running";
201
+ break;
202
+ }
203
+ progressMap[@"taskState"] = state;
204
+ return progressMap;
205
+ }
206
+
167
207
  @end
@@ -31,10 +31,11 @@ import { parseSnapshotArgs } from './utils';
31
31
  let _id = 0;
32
32
 
33
33
  export default class FirestoreQuery {
34
- constructor(firestore, collectionPath, modifiers) {
34
+ constructor(firestore, collectionPath, modifiers, queryName) {
35
35
  this._firestore = firestore;
36
36
  this._collectionPath = collectionPath;
37
37
  this._modifiers = modifiers;
38
+ this._queryName = queryName;
38
39
  }
39
40
 
40
41
  get firestore() {
@@ -134,6 +135,7 @@ export default class FirestoreQuery {
134
135
  this._firestore,
135
136
  this._collectionPath,
136
137
  this._handleQueryCursor('endAt', docOrField, fields),
138
+ this._queryName,
137
139
  );
138
140
  }
139
141
 
@@ -142,6 +144,7 @@ export default class FirestoreQuery {
142
144
  this._firestore,
143
145
  this._collectionPath,
144
146
  this._handleQueryCursor('endBefore', docOrField, fields),
147
+ this._queryName,
145
148
  );
146
149
  }
147
150
 
@@ -164,6 +167,19 @@ export default class FirestoreQuery {
164
167
  );
165
168
  }
166
169
 
170
+ if (!isUndefined(this._queryName)) {
171
+ return this._firestore.native
172
+ .namedQueryGet(
173
+ this._queryName,
174
+ this._modifiers.type,
175
+ this._modifiers.filters,
176
+ this._modifiers.orders,
177
+ this._modifiers.options,
178
+ options,
179
+ )
180
+ .then(data => new FirestoreQuerySnapshot(this._firestore, this, data));
181
+ }
182
+
167
183
  this._modifiers.validatelimitToLast();
168
184
 
169
185
  return this._firestore.native
@@ -219,7 +235,7 @@ export default class FirestoreQuery {
219
235
 
220
236
  const modifiers = this._modifiers._copy().limit(limit);
221
237
 
222
- return new FirestoreQuery(this._firestore, this._collectionPath, modifiers);
238
+ return new FirestoreQuery(this._firestore, this._collectionPath, modifiers, this._queryName);
223
239
  }
224
240
 
225
241
  limitToLast(limitToLast) {
@@ -231,7 +247,7 @@ export default class FirestoreQuery {
231
247
 
232
248
  const modifiers = this._modifiers._copy().limitToLast(limitToLast);
233
249
 
234
- return new FirestoreQuery(this._firestore, this._collectionPath, modifiers);
250
+ return new FirestoreQuery(this._firestore, this._collectionPath, modifiers, this._queryName);
235
251
  }
236
252
 
237
253
  onSnapshot(...args) {
@@ -285,15 +301,27 @@ export default class FirestoreQuery {
285
301
  this._firestore.native.collectionOffSnapshot(listenerId);
286
302
  };
287
303
 
288
- this._firestore.native.collectionOnSnapshot(
289
- this._collectionPath.relativeName,
290
- this._modifiers.type,
291
- this._modifiers.filters,
292
- this._modifiers.orders,
293
- this._modifiers.options,
294
- listenerId,
295
- snapshotListenOptions,
296
- );
304
+ if (!isUndefined(this._queryName)) {
305
+ this._firestore.native.namedQueryOnSnapshot(
306
+ this._queryName,
307
+ this._modifiers.type,
308
+ this._modifiers.filters,
309
+ this._modifiers.orders,
310
+ this._modifiers.options,
311
+ listenerId,
312
+ snapshotListenOptions,
313
+ );
314
+ } else {
315
+ this._firestore.native.collectionOnSnapshot(
316
+ this._collectionPath.relativeName,
317
+ this._modifiers.type,
318
+ this._modifiers.filters,
319
+ this._modifiers.orders,
320
+ this._modifiers.options,
321
+ listenerId,
322
+ snapshotListenOptions,
323
+ );
324
+ }
297
325
 
298
326
  return unsubscribe;
299
327
  }
@@ -343,7 +371,7 @@ export default class FirestoreQuery {
343
371
  throw new Error(`firebase.firestore().collection().orderBy() ${e.message}`);
344
372
  }
345
373
 
346
- return new FirestoreQuery(this._firestore, this._collectionPath, modifiers);
374
+ return new FirestoreQuery(this._firestore, this._collectionPath, modifiers, this._queryName);
347
375
  }
348
376
 
349
377
  startAfter(docOrField, ...fields) {
@@ -351,6 +379,7 @@ export default class FirestoreQuery {
351
379
  this._firestore,
352
380
  this._collectionPath,
353
381
  this._handleQueryCursor('startAfter', docOrField, fields),
382
+ this._queryName,
354
383
  );
355
384
  }
356
385
 
@@ -359,6 +388,7 @@ export default class FirestoreQuery {
359
388
  this._firestore,
360
389
  this._collectionPath,
361
390
  this._handleQueryCursor('startAt', docOrField, fields),
391
+ this._queryName,
362
392
  );
363
393
  }
364
394
 
@@ -421,6 +451,6 @@ export default class FirestoreQuery {
421
451
  throw new Error(`firebase.firestore().collection().where() ${e.message}`);
422
452
  }
423
453
 
424
- return new FirestoreQuery(this._firestore, this._collectionPath, modifiers);
454
+ return new FirestoreQuery(this._firestore, this._collectionPath, modifiers, this._queryName);
425
455
  }
426
456
  }
package/lib/index.d.ts CHANGED
@@ -720,6 +720,10 @@ export namespace FirebaseFirestoreTypes {
720
720
  * });
721
721
  * ```
722
722
  *
723
+ * Please be careful using this operator. It may not be reliable enough for use in strict accounting, as it
724
+ * appears writes may be dropped in situations not fully understood yet, but possibly correlated with increment frequency.
725
+ * See https://github.com/invertase/react-native-firebase/discussions/5914
726
+ *
723
727
  * @param n The value to increment by.
724
728
  */
725
729
  static increment(n: number): FieldValue;
@@ -1829,6 +1833,44 @@ export namespace FirebaseFirestoreTypes {
1829
1833
  ): WriteBatch;
1830
1834
  }
1831
1835
 
1836
+ /**
1837
+ * Represents the state of bundle loading tasks.
1838
+ *
1839
+ * Both 'Error' and 'Success' are sinking state: task will abort or complete and there will be no more
1840
+ * updates after they are reported.
1841
+ */
1842
+ export type TaskState = 'Error' | 'Running' | 'Success';
1843
+
1844
+ /**
1845
+ * Represents a progress update or a final state from loading bundles.
1846
+ */
1847
+ export interface LoadBundleTaskProgress {
1848
+ /**
1849
+ * How many bytes have been loaded.
1850
+ */
1851
+ bytesLoaded: number;
1852
+
1853
+ /**
1854
+ * How many documents have been loaded.
1855
+ */
1856
+ documentsLoaded: number;
1857
+
1858
+ /**
1859
+ * Current task state.
1860
+ */
1861
+ taskState: TaskState;
1862
+
1863
+ /**
1864
+ * How many bytes are in the bundle being loaded.
1865
+ */
1866
+ totalBytes: number;
1867
+
1868
+ /**
1869
+ * How many documents are in the bundle being loaded.
1870
+ */
1871
+ totalDocuments: number;
1872
+ }
1873
+
1832
1874
  /**
1833
1875
  * `firebase.firestore.X`
1834
1876
  */
@@ -2024,6 +2066,29 @@ export namespace FirebaseFirestoreTypes {
2024
2066
  * @param settings A `Settings` object.
2025
2067
  */
2026
2068
  settings(settings: Settings): Promise<void>;
2069
+ /**
2070
+ * Loads a Firestore bundle into the local cache.
2071
+ *
2072
+ * #### Example
2073
+ *
2074
+ * ```js
2075
+ * const resp = await fetch('/createBundle');
2076
+ * const bundleString = await resp.text();
2077
+ * await firestore().loadBundle(bundleString);
2078
+ * ```
2079
+ */
2080
+ loadBundle(bundle: string): Promise<LoadBundleTaskProgress>;
2081
+ /**
2082
+ * Reads a Firestore Query from local cache, identified by the given name.
2083
+ *
2084
+ * #### Example
2085
+ *
2086
+ * ```js
2087
+ * const query = firestore().namedQuery('latest-stories-query');
2088
+ * const storiesSnap = await query.get({ source: 'cache' });
2089
+ * ```
2090
+ */
2091
+ namedQuery<T extends DocumentData = DocumentData>(name: string): Query<T>;
2027
2092
  /**
2028
2093
  * Aimed primarily at clearing up any data cached from running tests. Needs to be executed before any database calls
2029
2094
  * are made.
package/lib/index.js CHANGED
@@ -84,6 +84,30 @@ class FirebaseFirestoreModule extends FirebaseModule {
84
84
  return new FirestoreWriteBatch(this);
85
85
  }
86
86
 
87
+ loadBundle(bundle) {
88
+ if (!isString(bundle)) {
89
+ throw new Error("firebase.firestore().loadBundle(*) 'bundle' must be a string value.");
90
+ }
91
+
92
+ if (bundle === '') {
93
+ throw new Error("firebase.firestore().loadBundle(*) 'bundle' must be a non-empty string.");
94
+ }
95
+
96
+ return this.native.loadBundle(bundle);
97
+ }
98
+
99
+ namedQuery(queryName) {
100
+ if (!isString(queryName)) {
101
+ throw new Error("firebase.firestore().namedQuery(*) 'queryName' must be a string value.");
102
+ }
103
+
104
+ if (queryName === '') {
105
+ throw new Error("firebase.firestore().namedQuery(*) 'queryName' must be a non-empty string.");
106
+ }
107
+
108
+ return new FirestoreQuery(this, this._referencePath, new FirestoreQueryModifiers(), queryName);
109
+ }
110
+
87
111
  async clearPersistence() {
88
112
  await this.native.clearPersistence();
89
113
  }
@@ -164,6 +188,7 @@ class FirebaseFirestoreModule extends FirebaseModule {
164
188
  this,
165
189
  this._referencePath.child(collectionId),
166
190
  new FirestoreQueryModifiers().asCollectionGroupQuery(),
191
+ undefined,
167
192
  );
168
193
  }
169
194
 
package/lib/version.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // generated by genversion
2
- module.exports = '14.7.0';
2
+ module.exports = '14.9.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-native-firebase/firestore",
3
- "version": "14.7.0",
3
+ "version": "14.9.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": "14.7.0"
30
+ "@react-native-firebase/app": "14.9.0"
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public"
34
34
  },
35
- "gitHead": "ef7895cd49eed3a4d103df624e52315250c68ab3"
35
+ "gitHead": "0fd4b4fc65467c4342442b22afb20a7f8cff0ada"
36
36
  }