@@ -25,6 +25,15 @@ import {Timestamp} from './timestamp';
2525import { DocumentData } from '@google-cloud/firestore' ;
2626import api = google . firestore . v1 ;
2727
28+ interface BatchGetResponse < AppModelType , DbModelType extends DocumentData > {
29+ result : Array < DocumentSnapshot < AppModelType , DbModelType > > ;
30+ /**
31+ * The transaction that was started as part of this request. Will only be if
32+ * `DocumentReader.transactionIdOrNewTransaction` was `api.ITransactionOptions`.
33+ */
34+ transaction ?: Uint8Array ;
35+ }
36+
2837/**
2938 * A wrapper around BatchGetDocumentsRequest that retries request upon stream
3039 * failure and returns ordered results.
@@ -33,40 +42,58 @@ import api = google.firestore.v1;
3342 * @internal
3443 */
3544export class DocumentReader < AppModelType , DbModelType extends DocumentData > {
36- /** An optional field mask to apply to this read. */
37- fieldMask ?: FieldPath [ ] ;
38- /** An optional transaction ID to use for this read. */
39- transactionId ?: Uint8Array ;
40- /** An optional readTime to use for this read. */
41- readTime ?: Timestamp ;
42-
43- private outstandingDocuments = new Set < string > ( ) ;
44- private retrievedDocuments = new Map < string , DocumentSnapshot > ( ) ;
45+ private readonly outstandingDocuments = new Set < string > ( ) ;
46+ private readonly retrievedDocuments = new Map < string , DocumentSnapshot > ( ) ;
47+ private retrievedTransactionId ?: Uint8Array ;
4548
4649 /**
4750 * Creates a new DocumentReader that fetches the provided documents (via
4851 * `get()`).
4952 *
5053 * @param firestore The Firestore instance to use.
5154 * @param allDocuments The documents to get.
55+ * @param fieldMask An optional field mask to apply to this read
56+ * @param transactionOrReadTime An optional transaction ID to use for this
57+ * read or options for beginning a new transaction with this read
5258 */
5359 constructor (
54- private firestore : Firestore ,
55- private allDocuments : Array < DocumentReference < AppModelType , DbModelType > >
60+ private readonly firestore : Firestore ,
61+ private readonly allDocuments : ReadonlyArray <
62+ DocumentReference < AppModelType , DbModelType >
63+ > ,
64+ private readonly fieldMask ?: FieldPath [ ] ,
65+ private readonly transactionOrReadTime ?:
66+ | Uint8Array
67+ | api . ITransactionOptions
68+ | Timestamp
5669 ) {
5770 for ( const docRef of this . allDocuments ) {
5871 this . outstandingDocuments . add ( docRef . formattedName ) ;
5972 }
6073 }
6174
6275 /**
63- * Invokes the BatchGetDocuments RPC and returns the results.
76+ * Invokes the BatchGetDocuments RPC and returns the results as an array of
77+ * documents.
6478 *
6579 * @param requestTag A unique client-assigned identifier for this request.
6680 */
6781 async get (
6882 requestTag : string
6983 ) : Promise < Array < DocumentSnapshot < AppModelType , DbModelType > > > {
84+ const { result} = await this . _get ( requestTag ) ;
85+ return result ;
86+ }
87+
88+ /**
89+ * Invokes the BatchGetDocuments RPC and returns the results with transaction
90+ * metadata.
91+ *
92+ * @param requestTag A unique client-assigned identifier for this request.
93+ */
94+ async _get (
95+ requestTag : string
96+ ) : Promise < BatchGetResponse < AppModelType , DbModelType > > {
7097 await this . fetchDocuments ( requestTag ) ;
7198
7299 // BatchGetDocuments doesn't preserve document order. We use the request
@@ -92,7 +119,10 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
92119 }
93120 }
94121
95- return orderedDocuments ;
122+ return {
123+ result : orderedDocuments ,
124+ transaction : this . retrievedTransactionId ,
125+ } ;
96126 }
97127
98128 private async fetchDocuments ( requestTag : string ) : Promise < void > {
@@ -104,10 +134,12 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
104134 database : this . firestore . formattedName ,
105135 documents : Array . from ( this . outstandingDocuments ) ,
106136 } ;
107- if ( this . transactionId ) {
108- request . transaction = this . transactionId ;
109- } else if ( this . readTime ) {
110- request . readTime = this . readTime . toProto ( ) . timestampValue ;
137+ if ( this . transactionOrReadTime instanceof Uint8Array ) {
138+ request . transaction = this . transactionOrReadTime ;
139+ } else if ( this . transactionOrReadTime instanceof Timestamp ) {
140+ request . readTime = this . transactionOrReadTime . toProto ( ) . timestampValue ;
141+ } else if ( this . transactionOrReadTime ) {
142+ request . newTransaction = this . transactionOrReadTime ;
111143 }
112144
113145 if ( this . fieldMask ) {
@@ -129,8 +161,12 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
129161 stream . resume ( ) ;
130162
131163 for await ( const response of stream ) {
132- let snapshot : DocumentSnapshot < DocumentData > ;
164+ // Proto comes with zero-length buffer by default
165+ if ( response . transaction ?. length ) {
166+ this . retrievedTransactionId = response . transaction ;
167+ }
133168
169+ let snapshot : DocumentSnapshot < DocumentData > | undefined ;
134170 if ( response . found ) {
135171 logger (
136172 'DocumentReader.fetchDocuments' ,
@@ -142,28 +178,31 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
142178 response . found ,
143179 response . readTime !
144180 ) ;
145- } else {
181+ } else if ( response . missing ) {
146182 logger (
147183 'DocumentReader.fetchDocuments' ,
148184 requestTag ,
149185 'Document missing: %s' ,
150- response . missing !
186+ response . missing
151187 ) ;
152188 snapshot = this . firestore . snapshot_ (
153- response . missing ! ,
189+ response . missing ,
154190 response . readTime !
155191 ) ;
156192 }
157193
158- const path = snapshot . ref . formattedName ;
159- this . outstandingDocuments . delete ( path ) ;
160- this . retrievedDocuments . set ( path , snapshot ) ;
161- ++ resultCount ;
194+ if ( snapshot ) {
195+ const path = snapshot . ref . formattedName ;
196+ this . outstandingDocuments . delete ( path ) ;
197+ this . retrievedDocuments . set ( path , snapshot ) ;
198+ ++ resultCount ;
199+ }
162200 }
163201 } catch ( error ) {
164202 const shouldRetry =
165203 // Transactional reads are retried via the transaction runner.
166- ! this . transactionId &&
204+ ! request . transaction &&
205+ ! request . newTransaction &&
167206 // Only retry if we made progress.
168207 resultCount > 0 &&
169208 // Don't retry permanent errors.
0 commit comments