@@ -25,6 +25,15 @@ import {Timestamp} from './timestamp';
25
25
import { DocumentData } from '@google-cloud/firestore' ;
26
26
import api = google . firestore . v1 ;
27
27
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
+
28
37
/**
29
38
* A wrapper around BatchGetDocumentsRequest that retries request upon stream
30
39
* failure and returns ordered results.
@@ -33,40 +42,58 @@ import api = google.firestore.v1;
33
42
* @internal
34
43
*/
35
44
export 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 ;
45
48
46
49
/**
47
50
* Creates a new DocumentReader that fetches the provided documents (via
48
51
* `get()`).
49
52
*
50
53
* @param firestore The Firestore instance to use.
51
54
* @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
52
58
*/
53
59
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
56
69
) {
57
70
for ( const docRef of this . allDocuments ) {
58
71
this . outstandingDocuments . add ( docRef . formattedName ) ;
59
72
}
60
73
}
61
74
62
75
/**
63
- * Invokes the BatchGetDocuments RPC and returns the results.
76
+ * Invokes the BatchGetDocuments RPC and returns the results as an array of
77
+ * documents.
64
78
*
65
79
* @param requestTag A unique client-assigned identifier for this request.
66
80
*/
67
81
async get (
68
82
requestTag : string
69
83
) : 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 > > {
70
97
await this . fetchDocuments ( requestTag ) ;
71
98
72
99
// BatchGetDocuments doesn't preserve document order. We use the request
@@ -92,7 +119,10 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
92
119
}
93
120
}
94
121
95
- return orderedDocuments ;
122
+ return {
123
+ result : orderedDocuments ,
124
+ transaction : this . retrievedTransactionId ,
125
+ } ;
96
126
}
97
127
98
128
private async fetchDocuments ( requestTag : string ) : Promise < void > {
@@ -104,10 +134,12 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
104
134
database : this . firestore . formattedName ,
105
135
documents : Array . from ( this . outstandingDocuments ) ,
106
136
} ;
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 ;
111
143
}
112
144
113
145
if ( this . fieldMask ) {
@@ -129,8 +161,12 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
129
161
stream . resume ( ) ;
130
162
131
163
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
+ }
133
168
169
+ let snapshot : DocumentSnapshot < DocumentData > | undefined ;
134
170
if ( response . found ) {
135
171
logger (
136
172
'DocumentReader.fetchDocuments' ,
@@ -142,28 +178,31 @@ export class DocumentReader<AppModelType, DbModelType extends DocumentData> {
142
178
response . found ,
143
179
response . readTime !
144
180
) ;
145
- } else {
181
+ } else if ( response . missing ) {
146
182
logger (
147
183
'DocumentReader.fetchDocuments' ,
148
184
requestTag ,
149
185
'Document missing: %s' ,
150
- response . missing !
186
+ response . missing
151
187
) ;
152
188
snapshot = this . firestore . snapshot_ (
153
- response . missing ! ,
189
+ response . missing ,
154
190
response . readTime !
155
191
) ;
156
192
}
157
193
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
+ }
162
200
}
163
201
} catch ( error ) {
164
202
const shouldRetry =
165
203
// Transactional reads are retried via the transaction runner.
166
- ! this . transactionId &&
204
+ ! request . transaction &&
205
+ ! request . newTransaction &&
167
206
// Only retry if we made progress.
168
207
resultCount > 0 &&
169
208
// Don't retry permanent errors.
0 commit comments