diff --git a/packages/firestore/exp/src/api/reference.ts b/packages/firestore/exp/src/api/reference.ts index ce059e6c3ff..b7c15689c1f 100644 --- a/packages/firestore/exp/src/api/reference.ts +++ b/packages/firestore/exp/src/api/reference.ts @@ -32,8 +32,6 @@ import { cast } from '../../../lite/src/api/util'; import { DocumentSnapshot, QuerySnapshot } from './snapshot'; import { applyFirestoreDataConverter, - getDocsViaSnapshotListener, - getDocViaSnapshotListener, SnapshotMetadata, validateHasExplicitOrderByForLimitToLast } from '../../../src/api/database'; @@ -65,8 +63,7 @@ export function getDoc( const ref = cast>(reference, DocumentReference); const firestore = cast(ref.firestore, Firestore); return getFirestoreClient(firestore).then(async firestoreClient => { - const viewSnapshot = await getDocViaSnapshotListener( - firestoreClient, + const viewSnapshot = await firestoreClient.getDocumentViaSnapshotListener( ref._key ); return convertToDocSnapshot(firestore, ref, viewSnapshot); @@ -101,8 +98,7 @@ export function getDocFromServer( const ref = cast>(reference, DocumentReference); const firestore = cast(ref.firestore, Firestore); return getFirestoreClient(firestore).then(async firestoreClient => { - const viewSnapshot = await getDocViaSnapshotListener( - firestoreClient, + const viewSnapshot = await firestoreClient.getDocumentViaSnapshotListener( ref._key, { source: 'server' } ); @@ -118,8 +114,7 @@ export function getDocs( validateHasExplicitOrderByForLimitToLast(internalQuery._query); return getFirestoreClient(firestore).then(async firestoreClient => { - const snapshot = await getDocsViaSnapshotListener( - firestoreClient, + const snapshot = await firestoreClient.getDocumentsViaSnapshotListener( internalQuery._query ); return new QuerySnapshot(firestore, internalQuery, snapshot); @@ -145,8 +140,7 @@ export function getDocsFromServer( const internalQuery = cast>(query, Query); const firestore = cast(query.firestore, Firestore); return getFirestoreClient(firestore).then(async firestoreClient => { - const snapshot = await getDocsViaSnapshotListener( - firestoreClient, + const snapshot = await firestoreClient.getDocumentsViaSnapshotListener( internalQuery._query, { source: 'server' } ); diff --git a/packages/firestore/src/api/database.ts b/packages/firestore/src/api/database.ts index e497ac6c3c6..e11368b595b 100644 --- a/packages/firestore/src/api/database.ts +++ b/packages/firestore/src/api/database.ts @@ -83,7 +83,6 @@ import { getLogLevel, logError, LogLevel, setLogLevel } from '../util/log'; import { AutoId } from '../util/misc'; import { Deferred } from '../util/promise'; import { FieldPath as ExternalFieldPath } from './field_path'; - import { CredentialsProvider, CredentialsSettings, @@ -1234,9 +1233,9 @@ export class DocumentReference validateBetweenNumberOfArgs('DocumentReference.get', arguments, 0, 1); validateGetOptions('DocumentReference.get', options); + const firestoreClient = this.firestore.ensureClientConfigured(); if (options && options.source === 'cache') { - return this.firestore - .ensureClientConfigured() + return firestoreClient .getDocumentFromLocalCache(this._key) .then( doc => @@ -1250,11 +1249,9 @@ export class DocumentReference ) ); } else { - return getDocViaSnapshotListener( - this._firestoreClient, - this._key, - options - ).then(snapshot => this._convertToDocSnapshot(snapshot)); + return firestoreClient + .getDocumentViaSnapshotListener(this._key, options) + .then(snapshot => this._convertToDocSnapshot(snapshot)); } } @@ -1286,68 +1283,6 @@ export class DocumentReference } } -/** - * Retrieves a latency-compensated document from the backend via a - * SnapshotListener. - */ -export function getDocViaSnapshotListener( - firestoreClient: FirestoreClient, - key: DocumentKey, - options?: firestore.GetOptions -): Promise { - const result = new Deferred(); - const unlisten = firestoreClient.listen( - newQueryForPath(key.path), - { - includeMetadataChanges: true, - waitForSyncWhenOnline: true - }, - { - next: (snap: ViewSnapshot) => { - // Remove query first before passing event to user to avoid - // user actions affecting the now stale query. - unlisten(); - - const exists = snap.docs.has(key); - if (!exists && snap.fromCache) { - // TODO(dimond): If we're online and the document doesn't - // exist then we resolve with a doc.exists set to false. If - // we're offline however, we reject the Promise in this - // case. Two options: 1) Cache the negative response from - // the server so we can deliver that even when you're - // offline 2) Actually reject the Promise in the online case - // if the document doesn't exist. - result.reject( - new FirestoreError( - Code.UNAVAILABLE, - 'Failed to get document because the client is ' + 'offline.' - ) - ); - } else if ( - exists && - snap.fromCache && - options && - options.source === 'server' - ) { - result.reject( - new FirestoreError( - Code.UNAVAILABLE, - 'Failed to get document from server. (However, this ' + - 'document does exist in the local cache. Run again ' + - 'without setting source to "server" to ' + - 'retrieve the cached document.)' - ) - ); - } else { - result.resolve(snap); - } - }, - error: e => result.reject(e) - } - ); - return result.promise; -} - export class SnapshotMetadata implements firestore.SnapshotMetadata { constructor( readonly hasPendingWrites: boolean, @@ -2170,7 +2105,7 @@ export class Query implements firestore.Query { const firestoreClient = this.firestore.ensureClientConfigured(); return (options && options.source === 'cache' ? firestoreClient.getDocumentsFromLocalCache(this._query) - : getDocsViaSnapshotListener(firestoreClient, this._query, options) + : firestoreClient.getDocumentsViaSnapshotListener(this._query, options) ).then( snap => new QuerySnapshot(this.firestore, this._query, snap, this._converter) @@ -2178,48 +2113,6 @@ export class Query implements firestore.Query { } } -/** - * Retrieves a latency-compensated query snapshot from the backend via a - * SnapshotListener. - */ -export function getDocsViaSnapshotListener( - firestore: FirestoreClient, - query: InternalQuery, - options?: firestore.GetOptions -): Promise { - const result = new Deferred(); - const unlisten = firestore.listen( - query, - { - includeMetadataChanges: true, - waitForSyncWhenOnline: true - }, - { - next: snapshot => { - // Remove query first before passing event to user to avoid - // user actions affecting the now stale query. - unlisten(); - - if (snapshot.fromCache && options && options.source === 'server') { - result.reject( - new FirestoreError( - Code.UNAVAILABLE, - 'Failed to get documents from server. (However, these ' + - 'documents may exist in the local cache. Run again ' + - 'without setting source to "server" to ' + - 'retrieve the cached documents.)' - ) - ); - } else { - result.resolve(snapshot); - } - }, - error: e => result.reject(e) - } - ); - return result.promise; -} - export class QuerySnapshot implements firestore.QuerySnapshot { private _cachedChanges: Array> | null = null; diff --git a/packages/firestore/src/core/firestore_client.ts b/packages/firestore/src/core/firestore_client.ts index 9595b264018..81f160bb322 100644 --- a/packages/firestore/src/core/firestore_client.ts +++ b/packages/firestore/src/core/firestore_client.ts @@ -15,6 +15,8 @@ * limitations under the License. */ +import { GetOptions } from '@firebase/firestore-types'; + import { CredentialsProvider } from '../api/credentials'; import { User } from '../auth/user'; import { LocalStore } from '../local/local_store'; @@ -38,7 +40,7 @@ import { View } from './view'; import { SharedClientState } from '../local/shared_client_state'; import { AutoId } from '../util/misc'; import { DatabaseId, DatabaseInfo } from './database_info'; -import { Query } from './query'; +import { newQueryForPath, Query } from './query'; import { Transaction } from './transaction'; import { ViewSnapshot } from './view_snapshot'; import { @@ -48,6 +50,7 @@ import { } from './component_provider'; import { PartialObserver, Unsubscribe } from '../api/observer'; import { AsyncObserver } from '../util/async_observer'; +import { debugAssert } from '../util/assert'; const LOG_TAG = 'FirestoreClient'; const MAX_CONCURRENT_LIMBO_RESOLUTIONS = 100; @@ -415,6 +418,20 @@ export class FirestoreClient { ); } + async getDocumentViaSnapshotListener( + key: DocumentKey, + options?: GetOptions + ): Promise { + this.verifyNotTerminated(); + await this.initializationDone.promise; + return enqueueReadDocumentViaSnapshotListener( + this.asyncQueue, + this.eventMgr, + key, + options + ); + } + async getDocumentsFromLocalCache(query: Query): Promise { this.verifyNotTerminated(); await this.initializationDone.promise; @@ -425,6 +442,20 @@ export class FirestoreClient { ); } + async getDocumentsViaSnapshotListener( + query: Query, + options?: GetOptions + ): Promise { + this.verifyNotTerminated(); + await this.initializationDone.promise; + return enqueueExecuteQueryViaSnapshotListener( + this.asyncQueue, + this.eventMgr, + query, + options + ); + } + write(mutations: Mutation[]): Promise { this.verifyNotTerminated(); const deferred = new Deferred(); @@ -573,6 +604,75 @@ export async function enqueueReadDocumentFromCache( return deferred.promise; } +/** + * Retrieves a latency-compensated document from the backend via a + * SnapshotListener. + */ +export function enqueueReadDocumentViaSnapshotListener( + asyncQueue: AsyncQueue, + eventManager: EventManager, + key: DocumentKey, + options?: GetOptions +): Promise { + const result = new Deferred(); + const unlisten = enqueueListen( + asyncQueue, + eventManager, + newQueryForPath(key.path), + { + includeMetadataChanges: true, + waitForSyncWhenOnline: true + }, + { + next: (snap: ViewSnapshot) => { + // Remove query first before passing event to user to avoid + // user actions affecting the now stale query. + unlisten(); + + const exists = snap.docs.has(key); + if (!exists && snap.fromCache) { + // TODO(dimond): If we're online and the document doesn't + // exist then we resolve with a doc.exists set to false. If + // we're offline however, we reject the Promise in this + // case. Two options: 1) Cache the negative response from + // the server so we can deliver that even when you're + // offline 2) Actually reject the Promise in the online case + // if the document doesn't exist. + result.reject( + new FirestoreError( + Code.UNAVAILABLE, + 'Failed to get document because the client is ' + 'offline.' + ) + ); + } else if ( + exists && + snap.fromCache && + options && + options.source === 'server' + ) { + result.reject( + new FirestoreError( + Code.UNAVAILABLE, + 'Failed to get document from server. (However, this ' + + 'document does exist in the local cache. Run again ' + + 'without setting source to "server" to ' + + 'retrieve the cached document.)' + ) + ); + } else { + debugAssert( + snap.docs.size <= 1, + 'Expected zero or a single result on a document-only query' + ); + result.resolve(snap); + } + }, + error: e => result.reject(e) + } + ); + return result.promise; +} + export async function enqueueExecuteQueryFromCache( asyncQueue: AsyncQueue, localStore: LocalStore, @@ -602,3 +702,48 @@ export async function enqueueExecuteQueryFromCache( }); return deferred.promise; } + +/** + * Retrieves a latency-compensated query snapshot from the backend via a + * SnapshotListener. + */ +export function enqueueExecuteQueryViaSnapshotListener( + asyncQueue: AsyncQueue, + eventManager: EventManager, + query: Query, + options?: GetOptions +): Promise { + const result = new Deferred(); + const unlisten = enqueueListen( + asyncQueue, + eventManager, + query, + { + includeMetadataChanges: true, + waitForSyncWhenOnline: true + }, + { + next: snapshot => { + // Remove query first before passing event to user to avoid + // user actions affecting the now stale query. + unlisten(); + + if (snapshot.fromCache && options && options.source === 'server') { + result.reject( + new FirestoreError( + Code.UNAVAILABLE, + 'Failed to get documents from server. (However, these ' + + 'documents may exist in the local cache. Run again ' + + 'without setting source to "server" to ' + + 'retrieve the cached documents.)' + ) + ); + } else { + result.resolve(snapshot); + } + }, + error: e => result.reject(e) + } + ); + return result.promise; +}