@@ -16,6 +16,7 @@ import { getSocks, type SocksLib } from '../deps';
1616import { MongoOperationTimeoutError } from '../error' ;
1717import { type MongoClient , type MongoClientOptions } from '../mongo_client' ;
1818import { type Abortable } from '../mongo_types' ;
19+ import { type CollectionInfo } from '../operations/list_collections' ;
1920import { Timeout , type TimeoutContext , TimeoutError } from '../timeout' ;
2021import {
2122 addAbortListener ,
@@ -205,11 +206,19 @@ export class StateMachine {
205206 const mongocryptdManager = executor . _mongocryptdManager ;
206207 let result : Uint8Array | null = null ;
207208
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 ) {
209218 options . signal ?. throwIfAborted ( ) ;
210- debug ( `[context#${ context . id } ] ${ stateToString . get ( context . state ) || context . state } ` ) ;
219+ debug ( `[context#${ context . id } ] ${ stateToString . get ( getState ( ) ) || getState ( ) } ` ) ;
211220
212- switch ( context . state ) {
221+ switch ( getState ( ) ) {
213222 case MONGOCRYPT_CTX_NEED_MONGO_COLLINFO : {
214223 const filter = deserialize ( context . nextMongoOperation ( ) ) ;
215224 if ( ! metaDataClient ) {
@@ -218,22 +227,28 @@ export class StateMachine {
218227 ) ;
219228 }
220229
221- const collInfo = await this . fetchCollectionInfo (
230+ const collInfoCursor = this . fetchCollectionInfo (
222231 metaDataClient ,
223232 context . ns ,
224233 filter ,
225234 options
226235 ) ;
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 ;
229240 }
230241
242+ if ( getState ( ) === MONGOCRYPT_CTX_ERROR ) break ;
243+
231244 context . finishMongoOperation ( ) ;
232245 break ;
233246 }
234247
235248 case MONGOCRYPT_CTX_NEED_MONGO_MARKINGS : {
236249 const command = context . nextMongoOperation ( ) ;
250+ if ( getState ( ) === MONGOCRYPT_CTX_ERROR ) break ;
251+
237252 if ( ! mongocryptdClient ) {
238253 throw new MongoCryptError (
239254 'unreachable state machine state: entered MONGOCRYPT_CTX_NEED_MONGO_MARKINGS but mongocryptdClient is undefined'
@@ -283,22 +298,21 @@ export class StateMachine {
283298
284299 case MONGOCRYPT_CTX_READY : {
285300 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' ;
289303 throw new MongoCryptError ( message ) ;
290304 }
291305 result = finalizedContext ;
292306 break ;
293307 }
294308
295309 default :
296- throw new MongoCryptError ( `Unknown state: ${ context . state } ` ) ;
310+ throw new MongoCryptError ( `Unknown state: ${ getState ( ) } ` ) ;
297311 }
298312 }
299313
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 ;
302316 if ( ! message ) {
303317 debug (
304318 `unidentifiable error in MongoCrypt - received an error status from \`libmongocrypt\` but received no error message.`
@@ -527,29 +541,24 @@ export class StateMachine {
527541 * @param filter - A filter for the listCollections command
528542 * @param callback - Invoked with the info of the requested collection, or with an error
529543 */
530- async fetchCollectionInfo (
544+ fetchCollectionInfo (
531545 client : MongoClient ,
532546 ns : string ,
533547 filter : Document ,
534548 options ?: { timeoutContext ?: TimeoutContext } & Abortable
535- ) : Promise < Uint8Array | null > {
549+ ) : AsyncIterable < CollectionInfo > {
536550 const { db } = MongoDBCollectionNamespace . fromString ( ns ) ;
537551
538552 const cursor = client . db ( db ) . listCollections ( filter , {
539553 promoteLongs : false ,
540554 promoteValues : false ,
541555 timeoutContext :
542556 options ?. timeoutContext && new CursorTimeoutContext ( options ?. timeoutContext , Symbol ( ) ) ,
543- signal : options ?. signal
557+ signal : options ?. signal ,
558+ nameOnly : false
544559 } ) ;
545560
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 ;
553562 }
554563
555564 /**
0 commit comments