UNPKG

react-native-firebase-compiled

Version:

A well tested, feature rich Firebase implementation for React Native, supporting iOS & Android. Individual module support for Admob, Analytics, Auth, Crash Reporting, Cloud Firestore, Database, Dynamic Links, Functions, Messaging (FCM), Remote Config, Sto

271 lines (224 loc) 9.96 kB
/** * * Query representation wrapper */ import DocumentSnapshot from './DocumentSnapshot'; import FieldPath from './FieldPath'; import QuerySnapshot from './QuerySnapshot'; import { buildNativeArray, buildTypeMap } from './utils/serialize'; import { getAppEventName, SharedEventEmitter } from '../../utils/events'; import { getLogger } from '../../utils/log'; import { firestoreAutoId, isFunction, isObject } from '../../utils'; import { getNativeModule } from '../../utils/native'; const DIRECTIONS = { ASC: 'ASCENDING', asc: 'ASCENDING', DESC: 'DESCENDING', desc: 'DESCENDING' }; const OPERATORS = { '=': 'EQUAL', '==': 'EQUAL', '>': 'GREATER_THAN', '>=': 'GREATER_THAN_OR_EQUAL', '<': 'LESS_THAN', '<=': 'LESS_THAN_OR_EQUAL' }; const buildNativeFieldPath = fieldPath => { if (fieldPath instanceof FieldPath) { return { elements: fieldPath._segments, type: 'fieldpath' }; } return { string: fieldPath, type: 'string' }; }; /** * @class Query */ export default class Query { constructor(firestore, path, fieldFilters, fieldOrders, queryOptions) { this._fieldFilters = fieldFilters || []; this._fieldOrders = fieldOrders || []; this._firestore = firestore; this._queryOptions = queryOptions || {}; this._referencePath = path; } get firestore() { return this._firestore; } endAt(...snapshotOrVarArgs) { const options = { ...this._queryOptions, endAt: this._buildOrderByOption(snapshotOrVarArgs) }; return new Query(this.firestore, this._referencePath, this._fieldFilters, this._fieldOrders, options); } endBefore(...snapshotOrVarArgs) { const options = { ...this._queryOptions, endBefore: this._buildOrderByOption(snapshotOrVarArgs) }; return new Query(this.firestore, this._referencePath, this._fieldFilters, this._fieldOrders, options); } get(options) { if (options) { if (!isObject(options)) { return Promise.reject(new Error('Query.get failed: First argument must be an object.')); } if (options.source && options.source !== 'default' && options.source !== 'server' && options.source !== 'cache') { return Promise.reject(new Error('Query.get failed: GetOptions.source must be one of `default`, `server` or `cache`.')); } } return getNativeModule(this._firestore).collectionGet(this._referencePath.relativeName, this._fieldFilters, this._fieldOrders, this._queryOptions, options).then(nativeData => new QuerySnapshot(this._firestore, this, nativeData)); } limit(limit) { // TODO: Validation // validate.isInteger('n', n); const options = { ...this._queryOptions, limit }; return new Query(this.firestore, this._referencePath, this._fieldFilters, this._fieldOrders, options); } onSnapshot(optionsOrObserverOrOnNext, observerOrOnNextOrOnError, onError) { let observer; let metadataChanges = {}; // Called with: onNext, ?onError if (isFunction(optionsOrObserverOrOnNext)) { if (observerOrOnNextOrOnError && !isFunction(observerOrOnNextOrOnError)) { throw new Error('Query.onSnapshot failed: Second argument must be a valid function.'); } // $FlowExpectedError: Not coping with the overloaded method signature observer = { next: optionsOrObserverOrOnNext, error: observerOrOnNextOrOnError }; } else if (optionsOrObserverOrOnNext && isObject(optionsOrObserverOrOnNext)) { // Called with: Observer if (optionsOrObserverOrOnNext.next) { if (isFunction(optionsOrObserverOrOnNext.next)) { if (optionsOrObserverOrOnNext.error && !isFunction(optionsOrObserverOrOnNext.error)) { throw new Error('Query.onSnapshot failed: Observer.error must be a valid function.'); } // $FlowExpectedError: Not coping with the overloaded method signature observer = { next: optionsOrObserverOrOnNext.next, error: optionsOrObserverOrOnNext.error }; } else { throw new Error('Query.onSnapshot failed: Observer.next must be a valid function.'); } } else if (Object.prototype.hasOwnProperty.call(optionsOrObserverOrOnNext, 'includeMetadataChanges')) { metadataChanges = optionsOrObserverOrOnNext; // Called with: Options, onNext, ?onError if (isFunction(observerOrOnNextOrOnError)) { if (onError && !isFunction(onError)) { throw new Error('Query.onSnapshot failed: Third argument must be a valid function.'); } // $FlowExpectedError: Not coping with the overloaded method signature observer = { next: observerOrOnNextOrOnError, error: onError }; // Called with Options, Observer } else if (observerOrOnNextOrOnError && isObject(observerOrOnNextOrOnError) && observerOrOnNextOrOnError.next) { if (isFunction(observerOrOnNextOrOnError.next)) { if (observerOrOnNextOrOnError.error && !isFunction(observerOrOnNextOrOnError.error)) { throw new Error('Query.onSnapshot failed: Observer.error must be a valid function.'); } observer = { next: observerOrOnNextOrOnError.next, error: observerOrOnNextOrOnError.error }; } else { throw new Error('Query.onSnapshot failed: Observer.next must be a valid function.'); } } else { throw new Error('Query.onSnapshot failed: Second argument must be a function or observer.'); } } else { throw new Error('Query.onSnapshot failed: First argument must be a function, observer or options.'); } } else { throw new Error('Query.onSnapshot failed: Called with invalid arguments.'); } const listenerId = firestoreAutoId(); const listener = nativeQuerySnapshot => { const querySnapshot = new QuerySnapshot(this._firestore, this, nativeQuerySnapshot); observer.next(querySnapshot); }; // Listen to snapshot events SharedEventEmitter.addListener(getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`), listener); // Listen for snapshot error events if (observer.error) { SharedEventEmitter.addListener(getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`), observer.error); } // Add the native listener getNativeModule(this._firestore).collectionOnSnapshot(this._referencePath.relativeName, this._fieldFilters, this._fieldOrders, this._queryOptions, listenerId, metadataChanges); // Return an unsubscribe method return this._offCollectionSnapshot.bind(this, listenerId, listener); } orderBy(fieldPath, directionStr = 'asc') { // TODO: Validation // validate.isFieldPath('fieldPath', fieldPath); // validate.isOptionalFieldOrder('directionStr', directionStr); if (this._queryOptions.startAt || this._queryOptions.startAfter || this._queryOptions.endAt || this._queryOptions.endBefore) { throw new Error('Cannot specify an orderBy() constraint after calling ' + 'startAt(), startAfter(), endBefore() or endAt().'); } const newOrder = { direction: DIRECTIONS[directionStr], fieldPath: buildNativeFieldPath(fieldPath) }; const combinedOrders = this._fieldOrders.concat(newOrder); return new Query(this.firestore, this._referencePath, this._fieldFilters, combinedOrders, this._queryOptions); } startAfter(...snapshotOrVarArgs) { const options = { ...this._queryOptions, startAfter: this._buildOrderByOption(snapshotOrVarArgs) }; return new Query(this.firestore, this._referencePath, this._fieldFilters, this._fieldOrders, options); } startAt(...snapshotOrVarArgs) { const options = { ...this._queryOptions, startAt: this._buildOrderByOption(snapshotOrVarArgs) }; return new Query(this.firestore, this._referencePath, this._fieldFilters, this._fieldOrders, options); } where(fieldPath, opStr, value) { // TODO: Validation // validate.isFieldPath('fieldPath', fieldPath); // validate.isFieldFilter('fieldFilter', opStr, value); const nativeValue = buildTypeMap(value); const newFilter = { fieldPath: buildNativeFieldPath(fieldPath), operator: OPERATORS[opStr], value: nativeValue }; const combinedFilters = this._fieldFilters.concat(newFilter); return new Query(this.firestore, this._referencePath, combinedFilters, this._fieldOrders, this._queryOptions); } /** * INTERNALS */ _buildOrderByOption(snapshotOrVarArgs) { // TODO: Validation let values; if (snapshotOrVarArgs.length === 1 && snapshotOrVarArgs[0] instanceof DocumentSnapshot) { const docSnapshot = snapshotOrVarArgs[0]; values = []; for (let i = 0; i < this._fieldOrders.length; i++) { const fieldOrder = this._fieldOrders[i]; if (fieldOrder.fieldPath.type === 'string' && fieldOrder.fieldPath.string) { values.push(docSnapshot.get(fieldOrder.fieldPath.string)); } else if (fieldOrder.fieldPath.elements) { const fieldPath = new FieldPath(...fieldOrder.fieldPath.elements); values.push(docSnapshot.get(fieldPath)); } } } else { values = snapshotOrVarArgs; } return buildNativeArray(values); } /** * Remove query snapshot listener * @param listener */ _offCollectionSnapshot(listenerId, listener) { getLogger(this._firestore).info('Removing onQuerySnapshot listener'); SharedEventEmitter.removeListener(getAppEventName(this._firestore, `onQuerySnapshot:${listenerId}`), listener); SharedEventEmitter.removeListener(getAppEventName(this._firestore, `onQuerySnapshotError:${listenerId}`), listener); getNativeModule(this._firestore).collectionOffSnapshot(this._referencePath.relativeName, this._fieldFilters, this._fieldOrders, this._queryOptions, listenerId); } }