@@ -16,6 +16,7 @@ import { getSocks, type SocksLib } from '../deps';
16
16
import { MongoOperationTimeoutError } from '../error' ;
17
17
import { type MongoClient , type MongoClientOptions } from '../mongo_client' ;
18
18
import { type Abortable } from '../mongo_types' ;
19
+ import { type CollectionInfo } from '../operations/list_collections' ;
19
20
import { Timeout , type TimeoutContext , TimeoutError } from '../timeout' ;
20
21
import {
21
22
addAbortListener ,
@@ -205,11 +206,19 @@ export class StateMachine {
205
206
const mongocryptdManager = executor . _mongocryptdManager ;
206
207
let result : Uint8Array | null = null ;
207
208
208
- while ( context . state !== MONGOCRYPT_CTX_DONE && context . state !== MONGOCRYPT_CTX_ERROR ) {
209
+ // Typescript treats getters just like properties: Once you've tested it for equality
210
+ // it cannot change. Which is exactly the opposite of what we use state and status for.
211
+ // Every call to at least `addMongoOperationResponse` and `finalize` can change the state.
212
+ // These wrappers let us write code more naturally and not add compiler exceptions
213
+ // to conditions checks inside the state machine.
214
+ const getStatus = ( ) => context . status ;
215
+ const getState = ( ) => context . state ;
216
+
217
+ while ( getState ( ) !== MONGOCRYPT_CTX_DONE && getState ( ) !== MONGOCRYPT_CTX_ERROR ) {
209
218
options . signal ?. throwIfAborted ( ) ;
210
- debug ( `[context#${ context . id } ] ${ stateToString . get ( context . state ) || context . state } ` ) ;
219
+ debug ( `[context#${ context . id } ] ${ stateToString . get ( getState ( ) ) || getState ( ) } ` ) ;
211
220
212
- switch ( context . state ) {
221
+ switch ( getState ( ) ) {
213
222
case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO : {
214
223
const filter = deserialize ( context . nextMongoOperation ( ) ) ;
215
224
if ( ! metaDataClient ) {
@@ -218,22 +227,28 @@ export class StateMachine {
218
227
) ;
219
228
}
220
229
221
- const collInfo = await this . fetchCollectionInfo (
230
+ const collInfoCursor = this . fetchCollectionInfo (
222
231
metaDataClient ,
223
232
context . ns ,
224
233
filter ,
225
234
options
226
235
) ;
227
- if ( collInfo ) {
228
- context . addMongoOperationResponse ( collInfo ) ;
236
+
237
+ for await ( const collInfo of collInfoCursor ) {
238
+ context . addMongoOperationResponse ( serialize ( collInfo ) ) ;
239
+ if ( getState ( ) === MONGOCRYPT_CTX_ERROR ) break ;
229
240
}
230
241
242
+ if ( getState ( ) === MONGOCRYPT_CTX_ERROR ) break ;
243
+
231
244
context . finishMongoOperation ( ) ;
232
245
break ;
233
246
}
234
247
235
248
case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS : {
236
249
const command = context . nextMongoOperation ( ) ;
250
+ if ( getState ( ) === MONGOCRYPT_CTX_ERROR ) break ;
251
+
237
252
if ( ! mongocryptdClient ) {
238
253
throw new MongoCryptError (
239
254
'unreachable state machine state: entered MONGOCRYPT_CTX_NEED_MONGO_MARKINGS but mongocryptdClient is undefined'
@@ -283,22 +298,21 @@ export class StateMachine {
283
298
284
299
case MONGOCRYPT_CTX_READY : {
285
300
const finalizedContext = context . finalize ( ) ;
286
- // @ts -expect-error finalize can change the state, check for error
287
- if ( context . state === MONGOCRYPT_CTX_ERROR ) {
288
- const message = context . status . message || 'Finalization error' ;
301
+ if ( getState ( ) === MONGOCRYPT_CTX_ERROR ) {
302
+ const message = getStatus ( ) . message || 'Finalization error' ;
289
303
throw new MongoCryptError ( message ) ;
290
304
}
291
305
result = finalizedContext ;
292
306
break ;
293
307
}
294
308
295
309
default :
296
- throw new MongoCryptError ( `Unknown state: ${ context . state } ` ) ;
310
+ throw new MongoCryptError ( `Unknown state: ${ getState ( ) } ` ) ;
297
311
}
298
312
}
299
313
300
- if ( context . state === MONGOCRYPT_CTX_ERROR || result == null ) {
301
- const message = context . status . message ;
314
+ if ( getState ( ) === MONGOCRYPT_CTX_ERROR || result == null ) {
315
+ const message = getStatus ( ) . message ;
302
316
if ( ! message ) {
303
317
debug (
304
318
`unidentifiable error in MongoCrypt - received an error status from \`libmongocrypt\` but received no error message.`
@@ -527,29 +541,24 @@ export class StateMachine {
527
541
* @param filter - A filter for the listCollections command
528
542
* @param callback - Invoked with the info of the requested collection, or with an error
529
543
*/
530
- async fetchCollectionInfo (
544
+ fetchCollectionInfo (
531
545
client : MongoClient ,
532
546
ns : string ,
533
547
filter : Document ,
534
548
options ?: { timeoutContext ?: TimeoutContext } & Abortable
535
- ) : Promise < Uint8Array | null > {
549
+ ) : AsyncIterable < CollectionInfo > {
536
550
const { db } = MongoDBCollectionNamespace . fromString ( ns ) ;
537
551
538
552
const cursor = client . db ( db ) . listCollections ( filter , {
539
553
promoteLongs : false ,
540
554
promoteValues : false ,
541
555
timeoutContext :
542
556
options ?. timeoutContext && new CursorTimeoutContext ( options ?. timeoutContext , Symbol ( ) ) ,
543
- signal : options ?. signal
557
+ signal : options ?. signal ,
558
+ nameOnly : false
544
559
} ) ;
545
560
546
- // There is always exactly zero or one matching documents, so this should always exhaust the cursor
547
- // in a single batch. We call `toArray()` just to be safe and ensure that the cursor is always
548
- // exhausted and closed.
549
- const collections = await cursor . toArray ( ) ;
550
-
551
- const info = collections . length > 0 ? serialize ( collections [ 0 ] ) : null ;
552
- return info ;
561
+ return cursor ;
553
562
}
554
563
555
564
/**
0 commit comments