mongodb 6.12.0-dev.20250125.sha.c1bcf0de → 6.12.0-dev.20250128.sha.654069fc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/lib/beta.d.ts +55 -13
  2. package/lib/client-side-encryption/auto_encrypter.js +4 -2
  3. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  4. package/lib/client-side-encryption/client_encryption.js +4 -4
  5. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  6. package/lib/client-side-encryption/state_machine.js +56 -30
  7. package/lib/client-side-encryption/state_machine.js.map +1 -1
  8. package/lib/cmap/connection.js +28 -22
  9. package/lib/cmap/connection.js.map +1 -1
  10. package/lib/cmap/connection_pool.js +5 -0
  11. package/lib/cmap/connection_pool.js.map +1 -1
  12. package/lib/cmap/wire_protocol/on_data.js +6 -1
  13. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  14. package/lib/collection.js.map +1 -1
  15. package/lib/cursor/abstract_cursor.js +47 -18
  16. package/lib/cursor/abstract_cursor.js.map +1 -1
  17. package/lib/cursor/aggregation_cursor.js +2 -1
  18. package/lib/cursor/aggregation_cursor.js.map +1 -1
  19. package/lib/cursor/find_cursor.js +2 -1
  20. package/lib/cursor/find_cursor.js.map +1 -1
  21. package/lib/cursor/list_collections_cursor.js +2 -1
  22. package/lib/cursor/list_collections_cursor.js.map +1 -1
  23. package/lib/db.js +2 -1
  24. package/lib/db.js.map +1 -1
  25. package/lib/mongo_client.js +4 -0
  26. package/lib/mongo_client.js.map +1 -1
  27. package/lib/operations/execute_operation.js +7 -3
  28. package/lib/operations/execute_operation.js.map +1 -1
  29. package/lib/operations/list_collections.js.map +1 -1
  30. package/lib/operations/operation.js.map +1 -1
  31. package/lib/sdam/server.js +26 -16
  32. package/lib/sdam/server.js.map +1 -1
  33. package/lib/sdam/topology.js +5 -0
  34. package/lib/sdam/topology.js.map +1 -1
  35. package/lib/utils.js +64 -7
  36. package/lib/utils.js.map +1 -1
  37. package/mongodb.d.ts +55 -13
  38. package/package.json +1 -1
  39. package/src/client-side-encryption/auto_encrypter.ts +12 -8
  40. package/src/client-side-encryption/client_encryption.ts +6 -4
  41. package/src/client-side-encryption/state_machine.ts +80 -36
  42. package/src/cmap/connection.ts +37 -29
  43. package/src/cmap/connection_pool.ts +17 -3
  44. package/src/cmap/wire_protocol/on_data.ts +9 -2
  45. package/src/collection.ts +15 -8
  46. package/src/cursor/abstract_cursor.ts +71 -23
  47. package/src/cursor/aggregation_cursor.ts +5 -3
  48. package/src/cursor/find_cursor.ts +5 -3
  49. package/src/cursor/list_collections_cursor.ts +5 -3
  50. package/src/db.ts +11 -7
  51. package/src/index.ts +1 -0
  52. package/src/mongo_client.ts +13 -0
  53. package/src/mongo_types.ts +38 -0
  54. package/src/operations/execute_operation.ts +9 -4
  55. package/src/operations/list_collections.ts +4 -1
  56. package/src/operations/operation.ts +3 -2
  57. package/src/sdam/server.ts +31 -18
  58. package/src/sdam/topology.ts +10 -2
  59. package/src/utils.ts +79 -6
@@ -15,8 +15,15 @@ import { CursorTimeoutContext } from '../cursor/abstract_cursor';
15
15
  import { getSocks, type SocksLib } from '../deps';
16
16
  import { MongoOperationTimeoutError } from '../error';
17
17
  import { type MongoClient, type MongoClientOptions } from '../mongo_client';
18
+ import { type Abortable } from '../mongo_types';
18
19
  import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
19
- import { BufferPool, MongoDBCollectionNamespace, promiseWithResolvers } from '../utils';
20
+ import {
21
+ addAbortListener,
22
+ BufferPool,
23
+ kDispose,
24
+ MongoDBCollectionNamespace,
25
+ promiseWithResolvers
26
+ } from '../utils';
20
27
  import { autoSelectSocketOptions, type DataKey } from './client_encryption';
21
28
  import { MongoCryptError } from './errors';
22
29
  import { type MongocryptdManager } from './mongocryptd_manager';
@@ -189,7 +196,7 @@ export class StateMachine {
189
196
  async execute(
190
197
  executor: StateMachineExecutable,
191
198
  context: MongoCryptContext,
192
- timeoutContext?: TimeoutContext
199
+ options: { timeoutContext?: TimeoutContext } & Abortable
193
200
  ): Promise<Uint8Array> {
194
201
  const keyVaultNamespace = executor._keyVaultNamespace;
195
202
  const keyVaultClient = executor._keyVaultClient;
@@ -199,6 +206,7 @@ export class StateMachine {
199
206
  let result: Uint8Array | null = null;
200
207
 
201
208
  while (context.state !== MONGOCRYPT_CTX_DONE && context.state !== MONGOCRYPT_CTX_ERROR) {
209
+ options.signal?.throwIfAborted();
202
210
  debug(`[context#${context.id}] ${stateToString.get(context.state) || context.state}`);
203
211
 
204
212
  switch (context.state) {
@@ -214,7 +222,7 @@ export class StateMachine {
214
222
  metaDataClient,
215
223
  context.ns,
216
224
  filter,
217
- timeoutContext
225
+ options
218
226
  );
219
227
  if (collInfo) {
220
228
  context.addMongoOperationResponse(collInfo);
@@ -235,9 +243,9 @@ export class StateMachine {
235
243
  // When we are using the shared library, we don't have a mongocryptd manager.
236
244
  const markedCommand: Uint8Array = mongocryptdManager
237
245
  ? await mongocryptdManager.withRespawn(
238
- this.markCommand.bind(this, mongocryptdClient, context.ns, command, timeoutContext)
246
+ this.markCommand.bind(this, mongocryptdClient, context.ns, command, options)
239
247
  )
240
- : await this.markCommand(mongocryptdClient, context.ns, command, timeoutContext);
248
+ : await this.markCommand(mongocryptdClient, context.ns, command, options);
241
249
 
242
250
  context.addMongoOperationResponse(markedCommand);
243
251
  context.finishMongoOperation();
@@ -246,12 +254,7 @@ export class StateMachine {
246
254
 
247
255
  case MONGOCRYPT_CTX_NEED_MONGO_KEYS: {
248
256
  const filter = context.nextMongoOperation();
249
- const keys = await this.fetchKeys(
250
- keyVaultClient,
251
- keyVaultNamespace,
252
- filter,
253
- timeoutContext
254
- );
257
+ const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter, options);
255
258
 
256
259
  if (keys.length === 0) {
257
260
  // See docs on EMPTY_V
@@ -273,7 +276,7 @@ export class StateMachine {
273
276
  }
274
277
 
275
278
  case MONGOCRYPT_CTX_NEED_KMS: {
276
- await Promise.all(this.requests(context, timeoutContext));
279
+ await Promise.all(this.requests(context, options));
277
280
  context.finishKMSRequests();
278
281
  break;
279
282
  }
@@ -315,11 +318,13 @@ export class StateMachine {
315
318
  * @param kmsContext - A C++ KMS context returned from the bindings
316
319
  * @returns A promise that resolves when the KMS reply has be fully parsed
317
320
  */
318
- async kmsRequest(request: MongoCryptKMSRequest, timeoutContext?: TimeoutContext): Promise<void> {
321
+ async kmsRequest(
322
+ request: MongoCryptKMSRequest,
323
+ options?: { timeoutContext?: TimeoutContext } & Abortable
324
+ ): Promise<void> {
319
325
  const parsedUrl = request.endpoint.split(':');
320
326
  const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
321
- const socketOptions = autoSelectSocketOptions(this.options.socketOptions || {});
322
- const options: tls.ConnectionOptions & {
327
+ const socketOptions: tls.ConnectionOptions & {
323
328
  host: string;
324
329
  port: number;
325
330
  autoSelectFamily?: boolean;
@@ -328,7 +333,7 @@ export class StateMachine {
328
333
  host: parsedUrl[0],
329
334
  servername: parsedUrl[0],
330
335
  port,
331
- ...socketOptions
336
+ ...autoSelectSocketOptions(this.options.socketOptions || {})
332
337
  };
333
338
  const message = request.message;
334
339
  const buffer = new BufferPool();
@@ -363,7 +368,7 @@ export class StateMachine {
363
368
  throw error;
364
369
  }
365
370
  try {
366
- await this.setTlsOptions(providerTlsOptions, options);
371
+ await this.setTlsOptions(providerTlsOptions, socketOptions);
367
372
  } catch (err) {
368
373
  throw onerror(err);
369
374
  }
@@ -380,23 +385,25 @@ export class StateMachine {
380
385
  .once('close', () => rejectOnNetSocketError(onclose()))
381
386
  .once('connect', () => resolveOnNetSocketConnect());
382
387
 
388
+ let abortListener;
389
+
383
390
  try {
384
391
  if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
385
392
  const netSocketOptions = {
393
+ ...socketOptions,
386
394
  host: this.options.proxyOptions.proxyHost,
387
- port: this.options.proxyOptions.proxyPort || 1080,
388
- ...socketOptions
395
+ port: this.options.proxyOptions.proxyPort || 1080
389
396
  };
390
397
  netSocket.connect(netSocketOptions);
391
398
  await willConnect;
392
399
 
393
400
  try {
394
401
  socks ??= loadSocks();
395
- options.socket = (
402
+ socketOptions.socket = (
396
403
  await socks.SocksClient.createConnection({
397
404
  existing_socket: netSocket,
398
405
  command: 'connect',
399
- destination: { host: options.host, port: options.port },
406
+ destination: { host: socketOptions.host, port: socketOptions.port },
400
407
  proxy: {
401
408
  // host and port are ignored because we pass existing_socket
402
409
  host: 'iLoveJavaScript',
@@ -412,7 +419,7 @@ export class StateMachine {
412
419
  }
413
420
  }
414
421
 
415
- socket = tls.connect(options, () => {
422
+ socket = tls.connect(socketOptions, () => {
416
423
  socket.write(message);
417
424
  });
418
425
 
@@ -422,6 +429,11 @@ export class StateMachine {
422
429
  resolve
423
430
  } = promiseWithResolvers<void>();
424
431
 
432
+ abortListener = addAbortListener(options?.signal, function () {
433
+ destroySockets();
434
+ rejectOnTlsSocketError(this.reason);
435
+ });
436
+
425
437
  socket
426
438
  .once('error', err => rejectOnTlsSocketError(onerror(err)))
427
439
  .once('close', () => rejectOnTlsSocketError(onclose()))
@@ -436,8 +448,11 @@ export class StateMachine {
436
448
  resolve();
437
449
  }
438
450
  });
439
- await (timeoutContext?.csotEnabled()
440
- ? Promise.all([willResolveKmsRequest, Timeout.expires(timeoutContext?.remainingTimeMS)])
451
+ await (options?.timeoutContext?.csotEnabled()
452
+ ? Promise.all([
453
+ willResolveKmsRequest,
454
+ Timeout.expires(options.timeoutContext?.remainingTimeMS)
455
+ ])
441
456
  : willResolveKmsRequest);
442
457
  } catch (error) {
443
458
  if (error instanceof TimeoutError)
@@ -446,16 +461,17 @@ export class StateMachine {
446
461
  } finally {
447
462
  // There's no need for any more activity on this socket at this point.
448
463
  destroySockets();
464
+ abortListener?.[kDispose]();
449
465
  }
450
466
  }
451
467
 
452
- *requests(context: MongoCryptContext, timeoutContext?: TimeoutContext) {
468
+ *requests(context: MongoCryptContext, options?: { timeoutContext?: TimeoutContext } & Abortable) {
453
469
  for (
454
470
  let request = context.nextKMSRequest();
455
471
  request != null;
456
472
  request = context.nextKMSRequest()
457
473
  ) {
458
- yield this.kmsRequest(request, timeoutContext);
474
+ yield this.kmsRequest(request, options);
459
475
  }
460
476
  }
461
477
 
@@ -516,14 +532,16 @@ export class StateMachine {
516
532
  client: MongoClient,
517
533
  ns: string,
518
534
  filter: Document,
519
- timeoutContext?: TimeoutContext
535
+ options?: { timeoutContext?: TimeoutContext } & Abortable
520
536
  ): Promise<Uint8Array | null> {
521
537
  const { db } = MongoDBCollectionNamespace.fromString(ns);
522
538
 
523
539
  const cursor = client.db(db).listCollections(filter, {
524
540
  promoteLongs: false,
525
541
  promoteValues: false,
526
- timeoutContext: timeoutContext && new CursorTimeoutContext(timeoutContext, Symbol())
542
+ timeoutContext:
543
+ options?.timeoutContext && new CursorTimeoutContext(options?.timeoutContext, Symbol()),
544
+ signal: options?.signal
527
545
  });
528
546
 
529
547
  // There is always exactly zero or one matching documents, so this should always exhaust the cursor
@@ -547,17 +565,30 @@ export class StateMachine {
547
565
  client: MongoClient,
548
566
  ns: string,
549
567
  command: Uint8Array,
550
- timeoutContext?: TimeoutContext
568
+ options?: { timeoutContext?: TimeoutContext } & Abortable
551
569
  ): Promise<Uint8Array> {
552
570
  const { db } = MongoDBCollectionNamespace.fromString(ns);
553
571
  const bsonOptions = { promoteLongs: false, promoteValues: false };
554
572
  const rawCommand = deserialize(command, bsonOptions);
555
573
 
574
+ const commandOptions: {
575
+ timeoutMS?: number;
576
+ signal?: AbortSignal;
577
+ } = {
578
+ timeoutMS: undefined,
579
+ signal: undefined
580
+ };
581
+
582
+ if (options?.timeoutContext?.csotEnabled()) {
583
+ commandOptions.timeoutMS = options.timeoutContext.remainingTimeMS;
584
+ }
585
+ if (options?.signal) {
586
+ commandOptions.signal = options.signal;
587
+ }
588
+
556
589
  const response = await client.db(db).command(rawCommand, {
557
590
  ...bsonOptions,
558
- ...(timeoutContext?.csotEnabled()
559
- ? { timeoutMS: timeoutContext?.remainingTimeMS }
560
- : undefined)
591
+ ...commandOptions
561
592
  });
562
593
 
563
594
  return serialize(response, this.bsonOptions);
@@ -575,17 +606,30 @@ export class StateMachine {
575
606
  client: MongoClient,
576
607
  keyVaultNamespace: string,
577
608
  filter: Uint8Array,
578
- timeoutContext?: TimeoutContext
609
+ options?: { timeoutContext?: TimeoutContext } & Abortable
579
610
  ): Promise<Array<DataKey>> {
580
611
  const { db: dbName, collection: collectionName } =
581
612
  MongoDBCollectionNamespace.fromString(keyVaultNamespace);
582
613
 
614
+ const commandOptions: {
615
+ timeoutContext?: CursorTimeoutContext;
616
+ signal?: AbortSignal;
617
+ } = {
618
+ timeoutContext: undefined,
619
+ signal: undefined
620
+ };
621
+
622
+ if (options?.timeoutContext != null) {
623
+ commandOptions.timeoutContext = new CursorTimeoutContext(options.timeoutContext, Symbol());
624
+ }
625
+ if (options?.signal != null) {
626
+ commandOptions.signal = options.signal;
627
+ }
628
+
583
629
  return client
584
630
  .db(dbName)
585
631
  .collection<DataKey>(collectionName, { readConcern: { level: 'majority' } })
586
- .find(deserialize(filter), {
587
- timeoutContext: timeoutContext && new CursorTimeoutContext(timeoutContext, Symbol())
588
- })
632
+ .find(deserialize(filter), commandOptions)
589
633
  .toArray();
590
634
  }
591
635
  }
@@ -33,7 +33,7 @@ import {
33
33
  import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client';
34
34
  import { type MongoClientAuthProviders } from '../mongo_client_auth_providers';
35
35
  import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger';
36
- import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
36
+ import { type Abortable, type CancellationToken, TypedEventEmitter } from '../mongo_types';
37
37
  import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
38
38
  import { ServerType } from '../sdam/common';
39
39
  import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
@@ -438,7 +438,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
438
438
 
439
439
  private async *sendWire(
440
440
  message: WriteProtocolMessageType,
441
- options: CommandOptions,
441
+ options: CommandOptions & Abortable,
442
442
  responseType?: MongoDBResponseConstructor
443
443
  ): AsyncGenerator<MongoDBResponse> {
444
444
  this.throwIfAborted();
@@ -453,7 +453,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
453
453
  await this.writeCommand(message, {
454
454
  agreedCompressor: this.description.compressor ?? 'none',
455
455
  zlibCompressionLevel: this.description.zlibCompressionLevel,
456
- timeoutContext: options.timeoutContext
456
+ timeoutContext: options.timeoutContext,
457
+ signal: options.signal
457
458
  });
458
459
 
459
460
  if (options.noResponse || message.moreToCome) {
@@ -473,7 +474,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
473
474
  );
474
475
  }
475
476
 
476
- for await (const response of this.readMany({ timeoutContext: options.timeoutContext })) {
477
+ for await (const response of this.readMany(options)) {
477
478
  this.socket.setTimeout(0);
478
479
  const bson = response.parse();
479
480
 
@@ -492,9 +493,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
492
493
  private async *sendCommand(
493
494
  ns: MongoDBNamespace,
494
495
  command: Document,
495
- options: CommandOptions,
496
+ options: CommandOptions & Abortable,
496
497
  responseType?: MongoDBResponseConstructor
497
498
  ) {
499
+ options?.signal?.throwIfAborted();
500
+
498
501
  const message = this.prepareCommand(ns.db, command, options);
499
502
  let started = 0;
500
503
  if (this.shouldEmitAndLogCommand) {
@@ -610,10 +613,12 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
610
613
  public async command(
611
614
  ns: MongoDBNamespace,
612
615
  command: Document,
613
- options: CommandOptions = {},
616
+ options: CommandOptions & Abortable = {},
614
617
  responseType?: MongoDBResponseConstructor
615
618
  ): Promise<Document> {
616
619
  this.throwIfAborted();
620
+ options.signal?.throwIfAborted();
621
+
617
622
  for await (const document of this.sendCommand(ns, command, options, responseType)) {
618
623
  if (options.timeoutContext?.csotEnabled()) {
619
624
  if (MongoDBResponse.is(document)) {
@@ -676,7 +681,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
676
681
  agreedCompressor?: CompressorName;
677
682
  zlibCompressionLevel?: number;
678
683
  timeoutContext?: TimeoutContext;
679
- }
684
+ } & Abortable
680
685
  ): Promise<void> {
681
686
  const finalCommand =
682
687
  options.agreedCompressor === 'none' || !OpCompressedRequest.canCompress(command)
@@ -701,23 +706,23 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
701
706
 
702
707
  if (this.socket.write(buffer)) return;
703
708
 
704
- const drainEvent = once<void>(this.socket, 'drain');
709
+ const drainEvent = once<void>(this.socket, 'drain', options);
705
710
  const timeout = options?.timeoutContext?.timeoutForSocketWrite;
706
- if (timeout) {
707
- try {
708
- return await Promise.race([drainEvent, timeout]);
709
- } catch (error) {
710
- let err = error;
711
- if (TimeoutError.is(error)) {
712
- err = new MongoOperationTimeoutError('Timed out at socket write');
713
- this.cleanup(err);
714
- }
715
- throw error;
716
- } finally {
717
- timeout.clear();
711
+ const drained = timeout ? Promise.race([drainEvent, timeout]) : drainEvent;
712
+ try {
713
+ return await drained;
714
+ } catch (writeError) {
715
+ if (TimeoutError.is(writeError)) {
716
+ const timeoutError = new MongoOperationTimeoutError('Timed out at socket write');
717
+ this.onError(timeoutError);
718
+ throw timeoutError;
719
+ } else if (writeError === options.signal?.reason) {
720
+ this.onError(writeError);
718
721
  }
722
+ throw writeError;
723
+ } finally {
724
+ timeout?.clear();
719
725
  }
720
- return await drainEvent;
721
726
  }
722
727
 
723
728
  /**
@@ -729,9 +734,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
729
734
  *
730
735
  * Note that `for-await` loops call `return` automatically when the loop is exited.
731
736
  */
732
- private async *readMany(options: {
733
- timeoutContext?: TimeoutContext;
734
- }): AsyncGenerator<OpMsgResponse | OpReply> {
737
+ private async *readMany(
738
+ options: {
739
+ timeoutContext?: TimeoutContext;
740
+ } & Abortable
741
+ ): AsyncGenerator<OpMsgResponse | OpReply> {
735
742
  try {
736
743
  this.dataEvents = onData(this.messageStream, options);
737
744
  this.messageStream.resume();
@@ -745,16 +752,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
745
752
  }
746
753
  }
747
754
  } catch (readError) {
748
- const err = readError;
749
755
  if (TimeoutError.is(readError)) {
750
- const error = new MongoOperationTimeoutError(
756
+ const timeoutError = new MongoOperationTimeoutError(
751
757
  `Timed out during socket read (${readError.duration}ms)`
752
758
  );
753
759
  this.dataEvents = null;
754
- this.onError(error);
755
- throw error;
760
+ this.onError(timeoutError);
761
+ throw timeoutError;
762
+ } else if (readError === options.signal?.reason) {
763
+ this.onError(readError);
756
764
  }
757
- throw err;
765
+ throw readError;
758
766
  } finally {
759
767
  this.dataEvents = null;
760
768
  this.messageStream.pause();
@@ -25,10 +25,18 @@ import {
25
25
  MongoRuntimeError,
26
26
  MongoServerError
27
27
  } from '../error';
28
- import { CancellationToken, TypedEventEmitter } from '../mongo_types';
28
+ import { type Abortable, CancellationToken, TypedEventEmitter } from '../mongo_types';
29
29
  import type { Server } from '../sdam/server';
30
30
  import { type TimeoutContext, TimeoutError } from '../timeout';
31
- import { type Callback, List, makeCounter, now, promiseWithResolvers } from '../utils';
31
+ import {
32
+ addAbortListener,
33
+ type Callback,
34
+ kDispose,
35
+ List,
36
+ makeCounter,
37
+ now,
38
+ promiseWithResolvers
39
+ } from '../utils';
32
40
  import { connect } from './connect';
33
41
  import { Connection, type ConnectionEvents, type ConnectionOptions } from './connection';
34
42
  import {
@@ -316,7 +324,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
316
324
  * will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
317
325
  * explicitly destroyed by the new owner.
318
326
  */
319
- async checkOut(options: { timeoutContext: TimeoutContext }): Promise<Connection> {
327
+ async checkOut(options: { timeoutContext: TimeoutContext } & Abortable): Promise<Connection> {
320
328
  const checkoutTime = now();
321
329
  this.emitAndLog(
322
330
  ConnectionPool.CONNECTION_CHECK_OUT_STARTED,
@@ -334,6 +342,11 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
334
342
  checkoutTime
335
343
  };
336
344
 
345
+ const abortListener = addAbortListener(options.signal, function () {
346
+ waitQueueMember.cancelled = true;
347
+ reject(this.reason);
348
+ });
349
+
337
350
  this.waitQueue.push(waitQueueMember);
338
351
  process.nextTick(() => this.processWaitQueue());
339
352
 
@@ -364,6 +377,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
364
377
  }
365
378
  throw error;
366
379
  } finally {
380
+ abortListener?.[kDispose]();
367
381
  timeout?.clear();
368
382
  }
369
383
  }
@@ -1,7 +1,8 @@
1
1
  import { type EventEmitter } from 'events';
2
2
 
3
+ import { type Abortable } from '../../mongo_types';
3
4
  import { type TimeoutContext } from '../../timeout';
4
- import { List, promiseWithResolvers } from '../../utils';
5
+ import { addAbortListener, kDispose, List, promiseWithResolvers } from '../../utils';
5
6
 
6
7
  /**
7
8
  * @internal
@@ -21,8 +22,10 @@ type PendingPromises = Omit<
21
22
  */
22
23
  export function onData(
23
24
  emitter: EventEmitter,
24
- { timeoutContext }: { timeoutContext?: TimeoutContext }
25
+ { timeoutContext, signal }: { timeoutContext?: TimeoutContext } & Abortable
25
26
  ) {
27
+ signal?.throwIfAborted();
28
+
26
29
  // Setup pending events and pending promise lists
27
30
  /**
28
31
  * When the caller has not yet called .next(), we store the
@@ -90,6 +93,9 @@ export function onData(
90
93
  // Adding event handlers
91
94
  emitter.on('data', eventHandler);
92
95
  emitter.on('error', errorHandler);
96
+ const abortListener = addAbortListener(signal, function () {
97
+ errorHandler(this.reason);
98
+ });
93
99
 
94
100
  const timeoutForSocketRead = timeoutContext?.timeoutForSocketRead;
95
101
  timeoutForSocketRead?.throwIfExpired();
@@ -115,6 +121,7 @@ export function onData(
115
121
  // Adding event handlers
116
122
  emitter.off('data', eventHandler);
117
123
  emitter.off('error', errorHandler);
124
+ abortListener?.[kDispose]();
118
125
  finished = true;
119
126
  timeoutForSocketRead?.clear();
120
127
  const doneResult = { value: undefined, done: finished } as const;
package/src/collection.ts CHANGED
@@ -14,6 +14,7 @@ import type { Db } from './db';
14
14
  import { MongoInvalidArgumentError, MongoOperationTimeoutError } from './error';
15
15
  import type { MongoClient, PkFactory } from './mongo_client';
16
16
  import type {
17
+ Abortable,
17
18
  Filter,
18
19
  Flatten,
19
20
  OptionalUnlessRequiredId,
@@ -505,7 +506,7 @@ export class Collection<TSchema extends Document = Document> {
505
506
  async findOne(filter: Filter<TSchema>): Promise<WithId<TSchema> | null>;
506
507
  async findOne(
507
508
  filter: Filter<TSchema>,
508
- options: Omit<FindOptions, 'timeoutMode'>
509
+ options: Omit<FindOptions, 'timeoutMode'> & Abortable
509
510
  ): Promise<WithId<TSchema> | null>;
510
511
 
511
512
  // allow an override of the schema.
@@ -513,12 +514,12 @@ export class Collection<TSchema extends Document = Document> {
513
514
  async findOne<T = TSchema>(filter: Filter<TSchema>): Promise<T | null>;
514
515
  async findOne<T = TSchema>(
515
516
  filter: Filter<TSchema>,
516
- options?: Omit<FindOptions, 'timeoutMode'>
517
+ options?: Omit<FindOptions, 'timeoutMode'> & Abortable
517
518
  ): Promise<T | null>;
518
519
 
519
520
  async findOne(
520
521
  filter: Filter<TSchema> = {},
521
- options: FindOptions = {}
522
+ options: FindOptions & Abortable = {}
522
523
  ): Promise<WithId<TSchema> | null> {
523
524
  const cursor = this.find(filter, options).limit(-1).batchSize(1);
524
525
  const res = await cursor.next();
@@ -532,9 +533,15 @@ export class Collection<TSchema extends Document = Document> {
532
533
  * @param filter - The filter predicate. If unspecified, then all documents in the collection will match the predicate
533
534
  */
534
535
  find(): FindCursor<WithId<TSchema>>;
535
- find(filter: Filter<TSchema>, options?: FindOptions): FindCursor<WithId<TSchema>>;
536
- find<T extends Document>(filter: Filter<TSchema>, options?: FindOptions): FindCursor<T>;
537
- find(filter: Filter<TSchema> = {}, options: FindOptions = {}): FindCursor<WithId<TSchema>> {
536
+ find(filter: Filter<TSchema>, options?: FindOptions & Abortable): FindCursor<WithId<TSchema>>;
537
+ find<T extends Document>(
538
+ filter: Filter<TSchema>,
539
+ options?: FindOptions & Abortable
540
+ ): FindCursor<T>;
541
+ find(
542
+ filter: Filter<TSchema> = {},
543
+ options: FindOptions & Abortable = {}
544
+ ): FindCursor<WithId<TSchema>> {
538
545
  return new FindCursor<WithId<TSchema>>(
539
546
  this.client,
540
547
  this.s.namespace,
@@ -792,7 +799,7 @@ export class Collection<TSchema extends Document = Document> {
792
799
  */
793
800
  async countDocuments(
794
801
  filter: Filter<TSchema> = {},
795
- options: CountDocumentsOptions = {}
802
+ options: CountDocumentsOptions & Abortable = {}
796
803
  ): Promise<number> {
797
804
  const pipeline = [];
798
805
  pipeline.push({ $match: filter });
@@ -1006,7 +1013,7 @@ export class Collection<TSchema extends Document = Document> {
1006
1013
  */
1007
1014
  aggregate<T extends Document = Document>(
1008
1015
  pipeline: Document[] = [],
1009
- options?: AggregateOptions
1016
+ options?: AggregateOptions & Abortable
1010
1017
  ): AggregationCursor<T> {
1011
1018
  if (!Array.isArray(pipeline)) {
1012
1019
  throw new MongoInvalidArgumentError(