@rebasepro/client-firebase 0.6.1 → 0.8.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/dist/index.es.js +222 -211
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +222 -211
- package/dist/index.umd.js.map +1 -1
- package/package.json +12 -12
- package/src/hooks/useFirestoreDriver.ts +319 -312
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
import type { FieldValue } from "@firebase/firestore";
|
|
32
32
|
import { FirebaseApp } from "@firebase/app";
|
|
33
33
|
import { FirestoreTextSearchController, FirestoreTextSearchControllerBuilder } from "../types/text_search";
|
|
34
|
-
import { useCallback, useEffect, useRef } from "react";
|
|
34
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
35
35
|
import { localSearchControllerBuilder } from "../utils";
|
|
36
36
|
import { getAuth } from "@firebase/auth";
|
|
37
37
|
|
|
@@ -252,46 +252,86 @@ export function useFirestoreDriver({
|
|
|
252
252
|
|
|
253
253
|
}, [firebaseApp, listenEntity]);
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
255
|
+
const initTextSearch = useCallback(async (props: {
|
|
256
|
+
path: string,
|
|
257
|
+
databaseId?: string,
|
|
258
|
+
collection?: EntityCollection
|
|
259
|
+
}) => {
|
|
260
|
+
console.debug("Init text search controller", searchControllerRef.current, props.path);
|
|
261
|
+
if (!searchControllerRef.current) {
|
|
262
|
+
console.warn("You are trying to use text search, but have not provided a text search controller in `useFirestoreDriver`. You can also set the flag `localTextSearchEnabled` to use local search in `useFirestoreDriver`. Local text search can incur in performance issues and higher costs for large datasets.");
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
try {
|
|
266
|
+
return searchControllerRef.current.init(props);
|
|
267
|
+
} catch (e) {
|
|
268
|
+
console.error("Error initializing text search controller", e);
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
}, []);
|
|
257
272
|
|
|
258
|
-
|
|
273
|
+
/**
|
|
274
|
+
* Fetch entities in a Firestore path
|
|
275
|
+
* @param path
|
|
276
|
+
* @param collection
|
|
277
|
+
* @param filter
|
|
278
|
+
* @param limit
|
|
279
|
+
* @param startAfter
|
|
280
|
+
* @param searchString
|
|
281
|
+
* @param orderBy
|
|
282
|
+
* @param order
|
|
283
|
+
* @return Function to cancel subscription
|
|
284
|
+
* @see useCollectionFetch if you need this functionality implemented as a hook
|
|
285
|
+
* @group Firestore
|
|
286
|
+
*/
|
|
287
|
+
const fetchCollection = useCallback(async <M extends Record<string, any>>({
|
|
288
|
+
path,
|
|
289
|
+
filter,
|
|
290
|
+
limit,
|
|
291
|
+
startAfter,
|
|
292
|
+
searchString,
|
|
293
|
+
orderBy,
|
|
294
|
+
order,
|
|
295
|
+
collection
|
|
296
|
+
}: FetchCollectionProps<M>
|
|
297
|
+
): Promise<Entity<M>[]> => {
|
|
259
298
|
|
|
260
|
-
|
|
299
|
+
const databaseId = collection?.databaseId;
|
|
261
300
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
301
|
+
const resolvedPath = path;
|
|
302
|
+
|
|
303
|
+
console.debug("Fetching collection", {
|
|
304
|
+
path,
|
|
305
|
+
limit,
|
|
306
|
+
filter,
|
|
307
|
+
startAfter,
|
|
308
|
+
orderBy,
|
|
309
|
+
order
|
|
310
|
+
});
|
|
311
|
+
const query = buildQuery(resolvedPath, filter, orderBy, order, startAfter as unknown[] | undefined, limit, databaseId);
|
|
312
|
+
|
|
313
|
+
const snapshot = await getDocs(query);
|
|
314
|
+
return snapshot.docs.map((doc) => createEntityFromDocument(doc, databaseId));
|
|
315
|
+
}, [buildQuery]);
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Listen to a entities in a given path
|
|
319
|
+
* @param path
|
|
320
|
+
* @param collection
|
|
321
|
+
* @param onError
|
|
322
|
+
* @param filter
|
|
323
|
+
* @param limit
|
|
324
|
+
* @param startAfter
|
|
325
|
+
* @param searchString
|
|
326
|
+
* @param orderBy
|
|
327
|
+
* @param order
|
|
328
|
+
* @param onUpdate
|
|
329
|
+
* @return Function to cancel subscription
|
|
330
|
+
* @see useCollectionFetch if you need this functionality implemented as a hook
|
|
331
|
+
* @group Firestore
|
|
332
|
+
*/
|
|
333
|
+
const listenCollection = useCallback(<M extends Record<string, any>>(
|
|
334
|
+
{
|
|
295
335
|
path,
|
|
296
336
|
filter,
|
|
297
337
|
limit,
|
|
@@ -299,320 +339,287 @@ export function useFirestoreDriver({
|
|
|
299
339
|
searchString,
|
|
300
340
|
orderBy,
|
|
301
341
|
order,
|
|
342
|
+
onUpdate,
|
|
343
|
+
onError,
|
|
344
|
+
collection
|
|
345
|
+
}: ListenCollectionProps<M>
|
|
346
|
+
): () => void => {
|
|
347
|
+
|
|
348
|
+
console.debug("Listening collection", {
|
|
349
|
+
path,
|
|
350
|
+
searchString,
|
|
351
|
+
limit,
|
|
352
|
+
filter,
|
|
353
|
+
startAfter,
|
|
354
|
+
orderBy,
|
|
355
|
+
order,
|
|
302
356
|
collection
|
|
303
|
-
}
|
|
304
|
-
): Promise<Entity<M>[]> => {
|
|
357
|
+
});
|
|
305
358
|
|
|
306
|
-
|
|
359
|
+
if (!firebaseApp) {
|
|
360
|
+
throw Error("useFirestoreDriver Firebase not initialised");
|
|
361
|
+
}
|
|
307
362
|
|
|
308
|
-
|
|
363
|
+
const databaseId = collection?.databaseId;
|
|
309
364
|
|
|
310
|
-
|
|
365
|
+
if (searchString) {
|
|
366
|
+
return performTextSearch<M>({
|
|
311
367
|
path,
|
|
312
|
-
limit,
|
|
313
|
-
filter,
|
|
314
|
-
startAfter,
|
|
315
|
-
orderBy,
|
|
316
|
-
order
|
|
317
|
-
});
|
|
318
|
-
const query = buildQuery(resolvedPath, filter, orderBy, order, startAfter as unknown[] | undefined, limit, databaseId);
|
|
319
|
-
|
|
320
|
-
const snapshot = await getDocs(query);
|
|
321
|
-
return snapshot.docs.map((doc) => createEntityFromDocument(doc, databaseId));
|
|
322
|
-
}, [buildQuery]),
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Listen to a entities in a given path
|
|
326
|
-
* @param path
|
|
327
|
-
* @param collection
|
|
328
|
-
* @param onError
|
|
329
|
-
* @param filter
|
|
330
|
-
* @param limit
|
|
331
|
-
* @param startAfter
|
|
332
|
-
* @param searchString
|
|
333
|
-
* @param orderBy
|
|
334
|
-
* @param order
|
|
335
|
-
* @param onUpdate
|
|
336
|
-
* @return Function to cancel subscription
|
|
337
|
-
* @see useCollectionFetch if you need this functionality implemented as a hook
|
|
338
|
-
* @group Firestore
|
|
339
|
-
*/
|
|
340
|
-
listenCollection: useCallback(<M extends Record<string, any>>(
|
|
341
|
-
{
|
|
342
|
-
path,
|
|
343
|
-
filter,
|
|
344
|
-
limit,
|
|
345
|
-
startAfter,
|
|
346
368
|
searchString,
|
|
347
|
-
orderBy,
|
|
348
|
-
order,
|
|
349
369
|
onUpdate,
|
|
350
|
-
|
|
351
|
-
collection
|
|
352
|
-
}: ListenCollectionProps<M>
|
|
353
|
-
): () => void => {
|
|
354
|
-
|
|
355
|
-
console.debug("Listening collection", {
|
|
356
|
-
path,
|
|
357
|
-
searchString,
|
|
358
|
-
limit,
|
|
359
|
-
filter,
|
|
360
|
-
startAfter,
|
|
361
|
-
orderBy,
|
|
362
|
-
order,
|
|
363
|
-
collection
|
|
370
|
+
databaseId
|
|
364
371
|
});
|
|
372
|
+
}
|
|
365
373
|
|
|
366
|
-
|
|
367
|
-
|
|
374
|
+
const resolvedPath = path;
|
|
375
|
+
console.debug("Resolved path for listening", {
|
|
376
|
+
path,
|
|
377
|
+
resolvedPath
|
|
378
|
+
});
|
|
379
|
+
const query = buildQuery(resolvedPath, filter, orderBy, order, startAfter as unknown[] | undefined, limit, databaseId);
|
|
380
|
+
return onSnapshot(query,
|
|
381
|
+
{
|
|
382
|
+
next: (snapshot) => {
|
|
383
|
+
if (!searchString)
|
|
384
|
+
onUpdate(snapshot.docs.map((doc) => createEntityFromDocument(doc, databaseId)));
|
|
385
|
+
},
|
|
386
|
+
error: onError
|
|
368
387
|
}
|
|
388
|
+
);
|
|
369
389
|
|
|
370
|
-
|
|
390
|
+
}, [buildQuery, firebaseApp, performTextSearch]);
|
|
371
391
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
392
|
+
/**
|
|
393
|
+
* Retrieve an entity given a path and a collection
|
|
394
|
+
* @param path
|
|
395
|
+
* @param entityId
|
|
396
|
+
* @param collection
|
|
397
|
+
* @group Firestore
|
|
398
|
+
*/
|
|
399
|
+
const fetchEntity = useCallback(<M extends Record<string, any>>({
|
|
400
|
+
path,
|
|
401
|
+
entityId,
|
|
402
|
+
collection
|
|
403
|
+
}: FetchEntityProps<M>
|
|
404
|
+
): Promise<Entity<M> | undefined> => {
|
|
405
|
+
const resolvedPath = path;
|
|
406
|
+
return getAndBuildEntity(resolvedPath, entityId, collection?.databaseId);
|
|
407
|
+
}, [getAndBuildEntity]);
|
|
380
408
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
395
|
-
);
|
|
396
|
-
|
|
397
|
-
}, [buildQuery, firebaseApp, performTextSearch]),
|
|
398
|
-
|
|
399
|
-
/**
|
|
400
|
-
* Retrieve an entity given a path and a collection
|
|
401
|
-
* @param path
|
|
402
|
-
* @param entityId
|
|
403
|
-
* @param collection
|
|
404
|
-
* @group Firestore
|
|
405
|
-
*/
|
|
406
|
-
fetchEntity: useCallback(<M extends Record<string, any>>({
|
|
409
|
+
/**
|
|
410
|
+
* Save entity to the specified path. Note that Firestore does not allow
|
|
411
|
+
* undefined values.
|
|
412
|
+
* @param path
|
|
413
|
+
* @param entityId
|
|
414
|
+
* @param values
|
|
415
|
+
* @param schemaId
|
|
416
|
+
* @param collection
|
|
417
|
+
* @param status
|
|
418
|
+
* @group Firestore
|
|
419
|
+
*/
|
|
420
|
+
const saveEntity = useCallback(<M extends Record<string, any>>(
|
|
421
|
+
{
|
|
407
422
|
path,
|
|
408
423
|
entityId,
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
return getAndBuildEntity(resolvedPath, entityId, collection?.databaseId);
|
|
414
|
-
}, [getAndBuildEntity]),
|
|
415
|
-
|
|
416
|
-
/**
|
|
417
|
-
*
|
|
418
|
-
* @param path
|
|
419
|
-
* @param entityId
|
|
420
|
-
* @param collection
|
|
421
|
-
* @param onUpdate
|
|
422
|
-
* @param onError
|
|
423
|
-
* @return Function to cancel subscription
|
|
424
|
-
* @group Firestore
|
|
425
|
-
*/
|
|
426
|
-
listenEntity,
|
|
424
|
+
values: valuesProp,
|
|
425
|
+
collection,
|
|
426
|
+
status
|
|
427
|
+
}: SaveEntityProps<M>): Promise<Entity<M>> => {
|
|
427
428
|
|
|
428
|
-
|
|
429
|
-
* Save entity to the specified path. Note that Firestore does not allow
|
|
430
|
-
* undefined values.
|
|
431
|
-
* @param path
|
|
432
|
-
* @param entityId
|
|
433
|
-
* @param values
|
|
434
|
-
* @param schemaId
|
|
435
|
-
* @param collection
|
|
436
|
-
* @param status
|
|
437
|
-
* @group Firestore
|
|
438
|
-
*/
|
|
439
|
-
saveEntity: useCallback(<M extends Record<string, any>>(
|
|
440
|
-
{
|
|
441
|
-
path,
|
|
442
|
-
entityId,
|
|
443
|
-
values: valuesProp,
|
|
444
|
-
collection,
|
|
445
|
-
status
|
|
446
|
-
}: SaveEntityProps<M>): Promise<Entity<M>> => {
|
|
429
|
+
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
447
430
|
|
|
448
|
-
|
|
431
|
+
console.debug("1", {
|
|
432
|
+
path,
|
|
433
|
+
entityId,
|
|
434
|
+
values: valuesProp,
|
|
435
|
+
collection
|
|
436
|
+
})
|
|
437
|
+
const values = cmsToFirestoreModel(valuesProp, getFirestore(firebaseApp));
|
|
449
438
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
439
|
+
console.debug("2", {
|
|
440
|
+
path,
|
|
441
|
+
entityId,
|
|
442
|
+
values: valuesProp,
|
|
443
|
+
collection
|
|
444
|
+
})
|
|
445
|
+
const databaseId = collection?.databaseId;
|
|
446
|
+
const firestore = databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp);
|
|
447
|
+
const resolvedPath = path;
|
|
457
448
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
449
|
+
const collectionReference: CollectionReference = collectionClause(firestore, path);
|
|
450
|
+
console.debug("Saving entity", {
|
|
451
|
+
path,
|
|
452
|
+
entityId,
|
|
453
|
+
values,
|
|
454
|
+
databaseId
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
let documentReference: DocumentReference;
|
|
458
|
+
if (entityId) {
|
|
459
|
+
documentReference = doc(collectionReference, String(entityId));
|
|
460
|
+
} else {
|
|
461
|
+
documentReference = doc(collectionReference);
|
|
462
|
+
}
|
|
463
|
+
return setDoc(documentReference, values as Record<string, unknown>, { merge: true })
|
|
464
|
+
.then(() => {
|
|
465
|
+
return {
|
|
466
|
+
id: documentReference.id,
|
|
467
|
+
path,
|
|
468
|
+
values: firestoreToCMSModel(values)
|
|
469
|
+
} as Entity<M>;
|
|
463
470
|
})
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
471
|
+
.catch((error) => {
|
|
472
|
+
console.error("Error saving entity", error);
|
|
473
|
+
throw error;
|
|
467
474
|
|
|
468
|
-
const collectionReference: CollectionReference = collectionClause(firestore, path);
|
|
469
|
-
console.debug("Saving entity", {
|
|
470
|
-
path,
|
|
471
|
-
entityId,
|
|
472
|
-
values,
|
|
473
|
-
databaseId
|
|
474
475
|
});
|
|
476
|
+
}, [firebaseApp]);
|
|
475
477
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
} as Entity<M>;
|
|
489
|
-
})
|
|
490
|
-
.catch((error) => {
|
|
491
|
-
console.error("Error saving entity", error);
|
|
492
|
-
throw error;
|
|
478
|
+
/**
|
|
479
|
+
* Delete an entity
|
|
480
|
+
* @param entity
|
|
481
|
+
* @param collection
|
|
482
|
+
* @group Firestore
|
|
483
|
+
*/
|
|
484
|
+
const deleteEntity = useCallback(<M extends Record<string, any>>(
|
|
485
|
+
{
|
|
486
|
+
entity
|
|
487
|
+
}: DeleteEntityProps<M>
|
|
488
|
+
): Promise<void> => {
|
|
489
|
+
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
493
490
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
/**
|
|
498
|
-
* Delete an entity
|
|
499
|
-
* @param entity
|
|
500
|
-
* @param collection
|
|
501
|
-
* @group Firestore
|
|
502
|
-
*/
|
|
503
|
-
deleteEntity: useCallback(<M extends Record<string, any>>(
|
|
504
|
-
{
|
|
505
|
-
entity
|
|
506
|
-
}: DeleteEntityProps<M>
|
|
507
|
-
): Promise<void> => {
|
|
508
|
-
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
509
|
-
|
|
510
|
-
const databaseId = entity.databaseId;
|
|
511
|
-
const firestore = databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp);
|
|
512
|
-
|
|
513
|
-
return deleteDoc(doc(firestore, entity.path, String(entity.id)));
|
|
514
|
-
}, [firebaseApp]),
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Check if the given property is unique in the given collection
|
|
518
|
-
* @param path Collection path
|
|
519
|
-
* @param name of the property
|
|
520
|
-
* @param value
|
|
521
|
-
* @param property
|
|
522
|
-
* @param entityId
|
|
523
|
-
* @return `true` if there are no other fields besides the given entity
|
|
524
|
-
* @group Firestore
|
|
525
|
-
*/
|
|
526
|
-
checkUniqueField: useCallback(async (
|
|
527
|
-
path: string,
|
|
528
|
-
name: string,
|
|
529
|
-
value: unknown,
|
|
530
|
-
entityId?: string | number,
|
|
531
|
-
collection?: EntityCollection<any>
|
|
532
|
-
): Promise<boolean> => {
|
|
491
|
+
const databaseId = entity.databaseId;
|
|
492
|
+
const firestore = databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp);
|
|
533
493
|
|
|
534
|
-
|
|
494
|
+
return deleteDoc(doc(firestore, entity.path, String(entity.id)));
|
|
495
|
+
}, [firebaseApp]);
|
|
535
496
|
|
|
536
|
-
|
|
537
|
-
|
|
497
|
+
/**
|
|
498
|
+
* Check if the given property is unique in the given collection
|
|
499
|
+
* @param path Collection path
|
|
500
|
+
* @param name of the property
|
|
501
|
+
* @param value
|
|
502
|
+
* @param property
|
|
503
|
+
* @param entityId
|
|
504
|
+
* @return `true` if there are no other fields besides the given entity
|
|
505
|
+
* @group Firestore
|
|
506
|
+
*/
|
|
507
|
+
const checkUniqueField = useCallback(async (
|
|
508
|
+
path: string,
|
|
509
|
+
name: string,
|
|
510
|
+
value: unknown,
|
|
511
|
+
entityId?: string | number,
|
|
512
|
+
collection?: EntityCollection<any>
|
|
513
|
+
): Promise<boolean> => {
|
|
538
514
|
|
|
539
|
-
|
|
540
|
-
return Promise.resolve(true);
|
|
541
|
-
}
|
|
542
|
-
const q = query(collectionClause(firestore, path), whereClause(name, "==", cmsToFirestoreModel(value, firestore)));
|
|
543
|
-
const snapshot = await getDocs(q);
|
|
544
|
-
return snapshot.docs.filter(doc => doc.id !== entityId).length === 0;
|
|
515
|
+
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
545
516
|
|
|
546
|
-
|
|
517
|
+
const databaseId = collection?.databaseId;
|
|
518
|
+
const firestore = databaseId ? getFirestore(firebaseApp, databaseId) : getFirestore(firebaseApp);
|
|
547
519
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const databaseId = collection?.databaseId;
|
|
557
|
-
const resolvedPath = path;
|
|
558
|
-
const query = buildQuery(resolvedPath, filter, orderBy, order, undefined, undefined, databaseId);
|
|
559
|
-
const snapshot = await getCountFromServer(query);
|
|
560
|
-
return snapshot.data().count;
|
|
561
|
-
}, [firebaseApp]),
|
|
562
|
-
|
|
563
|
-
isFilterCombinationValid: useCallback(({
|
|
564
|
-
path,
|
|
565
|
-
collection,
|
|
566
|
-
filterValues,
|
|
567
|
-
sortBy
|
|
568
|
-
}: {
|
|
569
|
-
path: string,
|
|
570
|
-
collection: EntityCollection<any>,
|
|
571
|
-
filterValues: FilterValues<any>,
|
|
572
|
-
sortBy?: [string, "asc" | "desc"],
|
|
573
|
-
}): boolean => {
|
|
520
|
+
if (value === undefined || value === null) {
|
|
521
|
+
return Promise.resolve(true);
|
|
522
|
+
}
|
|
523
|
+
const q = query(collectionClause(firestore, path), whereClause(name, "==", cmsToFirestoreModel(value, firestore)));
|
|
524
|
+
const snapshot = await getDocs(q);
|
|
525
|
+
return snapshot.docs.filter(doc => doc.id !== entityId).length === 0;
|
|
526
|
+
|
|
527
|
+
}, [firebaseApp]);
|
|
574
528
|
|
|
575
|
-
|
|
529
|
+
const countEntities = useCallback(async ({
|
|
530
|
+
path,
|
|
531
|
+
filter,
|
|
532
|
+
order,
|
|
533
|
+
orderBy,
|
|
534
|
+
collection
|
|
535
|
+
}: FetchCollectionProps<any>): Promise<number> => {
|
|
536
|
+
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
537
|
+
const databaseId = collection?.databaseId;
|
|
538
|
+
const resolvedPath = path;
|
|
539
|
+
const query = buildQuery(resolvedPath, filter, orderBy, order, undefined, undefined, databaseId);
|
|
540
|
+
const snapshot = await getCountFromServer(query);
|
|
541
|
+
return snapshot.data().count;
|
|
542
|
+
}, [firebaseApp]);
|
|
576
543
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
544
|
+
const isFilterCombinationValid = useCallback(({
|
|
545
|
+
path,
|
|
546
|
+
collection,
|
|
547
|
+
filterValues,
|
|
548
|
+
sortBy
|
|
549
|
+
}: {
|
|
550
|
+
path: string,
|
|
551
|
+
collection: EntityCollection<any>,
|
|
552
|
+
filterValues: FilterValues<any>,
|
|
553
|
+
sortBy?: [string, "asc" | "desc"],
|
|
554
|
+
}): boolean => {
|
|
581
555
|
|
|
582
|
-
|
|
583
|
-
path: resolvedPath,
|
|
584
|
-
collection
|
|
585
|
-
});
|
|
556
|
+
if (!firebaseApp) throw Error("useFirestoreDriver Firebase not initialised");
|
|
586
557
|
|
|
587
|
-
|
|
588
|
-
|
|
558
|
+
// If no indexes are defined, we assume the query is valid.
|
|
559
|
+
// If there is no index in Firestore, and error message will be shown
|
|
560
|
+
if (firestoreIndexesBuilder === undefined) return true;
|
|
561
|
+
const resolvedPath = path;
|
|
589
562
|
|
|
590
|
-
|
|
591
|
-
|
|
563
|
+
const indexes = firestoreIndexesBuilder?.({
|
|
564
|
+
path: resolvedPath,
|
|
565
|
+
collection
|
|
566
|
+
});
|
|
592
567
|
|
|
593
|
-
|
|
594
|
-
|
|
568
|
+
const sortKey = sortBy ? sortBy[0] : undefined;
|
|
569
|
+
const sortDirection = sortBy ? sortBy[1] : undefined;
|
|
595
570
|
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
571
|
+
// Order by clause cannot contain a field with an equality filter
|
|
572
|
+
const values: [WhereFilterOp, any][] = Object.values(filterValues) as [WhereFilterOp, any][];
|
|
599
573
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
}
|
|
574
|
+
const filterKeys = Object.keys(filterValues);
|
|
575
|
+
const filtersCount = filterKeys.length;
|
|
603
576
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
577
|
+
if (!sortKey && values.every((v) => v[0] === "==")) {
|
|
578
|
+
return true;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (filtersCount === 1 && (!sortKey || sortKey === filterKeys[0])) {
|
|
582
|
+
return true;
|
|
583
|
+
}
|
|
607
584
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
Object.entries(filterValues).every(([key, value]) => compositeIndex[key] !== undefined && (!sortDirection || compositeIndex[key] === sortDirection))
|
|
612
|
-
) !== undefined;
|
|
613
|
-
}, [firebaseApp])
|
|
585
|
+
if (!indexes && filtersCount > 1) {
|
|
586
|
+
return false;
|
|
587
|
+
}
|
|
614
588
|
|
|
615
|
-
|
|
589
|
+
return !!indexes && indexes
|
|
590
|
+
.filter((compositeIndex) => !sortKey || sortKey in compositeIndex)
|
|
591
|
+
.find((compositeIndex) =>
|
|
592
|
+
Object.entries(filterValues).every(([key, value]) => compositeIndex[key] !== undefined && (!sortDirection || compositeIndex[key] === sortDirection))
|
|
593
|
+
) !== undefined;
|
|
594
|
+
}, [firebaseApp]);
|
|
595
|
+
|
|
596
|
+
return useMemo(() => ({
|
|
597
|
+
key: "firestore" as const,
|
|
598
|
+
currentTime,
|
|
599
|
+
initialised: Boolean(firebaseApp),
|
|
600
|
+
initTextSearch,
|
|
601
|
+
fetchCollection,
|
|
602
|
+
listenCollection,
|
|
603
|
+
fetchEntity,
|
|
604
|
+
listenEntity,
|
|
605
|
+
saveEntity,
|
|
606
|
+
deleteEntity,
|
|
607
|
+
checkUniqueField,
|
|
608
|
+
countEntities,
|
|
609
|
+
isFilterCombinationValid
|
|
610
|
+
}), [
|
|
611
|
+
firebaseApp,
|
|
612
|
+
initTextSearch,
|
|
613
|
+
fetchCollection,
|
|
614
|
+
listenCollection,
|
|
615
|
+
fetchEntity,
|
|
616
|
+
listenEntity,
|
|
617
|
+
saveEntity,
|
|
618
|
+
deleteEntity,
|
|
619
|
+
checkUniqueField,
|
|
620
|
+
countEntities,
|
|
621
|
+
isFilterCombinationValid
|
|
622
|
+
]);
|
|
616
623
|
|
|
617
624
|
}
|
|
618
625
|
|