mongodb 6.10.0-dev.20241106.sha.dc3fe957 → 6.10.0-dev.20241107.sha.e5582ed7

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 (203) hide show
  1. package/lib/admin.js +3 -2
  2. package/lib/admin.js.map +1 -1
  3. package/lib/beta.d.ts +558 -38
  4. package/lib/bulk/common.js +4 -4
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/change_stream.js +111 -51
  7. package/lib/change_stream.js.map +1 -1
  8. package/lib/client-side-encryption/auto_encrypter.js +8 -5
  9. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  10. package/lib/client-side-encryption/client_encryption.js +48 -18
  11. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  12. package/lib/client-side-encryption/state_machine.js +43 -29
  13. package/lib/client-side-encryption/state_machine.js.map +1 -1
  14. package/lib/cmap/connection.js +78 -6
  15. package/lib/cmap/connection.js.map +1 -1
  16. package/lib/cmap/connection_pool.js +14 -9
  17. package/lib/cmap/connection_pool.js.map +1 -1
  18. package/lib/cmap/wire_protocol/on_data.js +5 -1
  19. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  20. package/lib/cmap/wire_protocol/responses.js +30 -0
  21. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  22. package/lib/collection.js +62 -3
  23. package/lib/collection.js.map +1 -1
  24. package/lib/connection_string.js +2 -0
  25. package/lib/connection_string.js.map +1 -1
  26. package/lib/cursor/abstract_cursor.js +218 -38
  27. package/lib/cursor/abstract_cursor.js.map +1 -1
  28. package/lib/cursor/aggregation_cursor.js +29 -7
  29. package/lib/cursor/aggregation_cursor.js.map +1 -1
  30. package/lib/cursor/change_stream_cursor.js +2 -2
  31. package/lib/cursor/change_stream_cursor.js.map +1 -1
  32. package/lib/cursor/client_bulk_write_cursor.js +1 -1
  33. package/lib/cursor/client_bulk_write_cursor.js.map +1 -1
  34. package/lib/cursor/find_cursor.js +18 -8
  35. package/lib/cursor/find_cursor.js.map +1 -1
  36. package/lib/cursor/list_collections_cursor.js +1 -1
  37. package/lib/cursor/list_collections_cursor.js.map +1 -1
  38. package/lib/cursor/list_indexes_cursor.js +1 -1
  39. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  40. package/lib/cursor/run_command_cursor.js +6 -4
  41. package/lib/cursor/run_command_cursor.js.map +1 -1
  42. package/lib/db.js +63 -3
  43. package/lib/db.js.map +1 -1
  44. package/lib/error.js +27 -2
  45. package/lib/error.js.map +1 -1
  46. package/lib/explain.js +57 -1
  47. package/lib/explain.js.map +1 -1
  48. package/lib/gridfs/download.js +31 -3
  49. package/lib/gridfs/download.js.map +1 -1
  50. package/lib/gridfs/index.js +49 -14
  51. package/lib/gridfs/index.js.map +1 -1
  52. package/lib/gridfs/upload.js +80 -22
  53. package/lib/gridfs/upload.js.map +1 -1
  54. package/lib/index.js +9 -5
  55. package/lib/index.js.map +1 -1
  56. package/lib/mongo_client.js +70 -1
  57. package/lib/mongo_client.js.map +1 -1
  58. package/lib/operations/aggregate.js +2 -2
  59. package/lib/operations/aggregate.js.map +1 -1
  60. package/lib/operations/bulk_write.js +7 -2
  61. package/lib/operations/bulk_write.js.map +1 -1
  62. package/lib/operations/client_bulk_write/client_bulk_write.js +3 -3
  63. package/lib/operations/client_bulk_write/client_bulk_write.js.map +1 -1
  64. package/lib/operations/client_bulk_write/executor.js +14 -3
  65. package/lib/operations/client_bulk_write/executor.js.map +1 -1
  66. package/lib/operations/command.js +5 -2
  67. package/lib/operations/command.js.map +1 -1
  68. package/lib/operations/count.js +2 -2
  69. package/lib/operations/count.js.map +1 -1
  70. package/lib/operations/create_collection.js +8 -7
  71. package/lib/operations/create_collection.js.map +1 -1
  72. package/lib/operations/delete.js +6 -6
  73. package/lib/operations/delete.js.map +1 -1
  74. package/lib/operations/distinct.js +2 -2
  75. package/lib/operations/distinct.js.map +1 -1
  76. package/lib/operations/drop.js +8 -8
  77. package/lib/operations/drop.js.map +1 -1
  78. package/lib/operations/estimated_document_count.js +2 -2
  79. package/lib/operations/estimated_document_count.js.map +1 -1
  80. package/lib/operations/execute_operation.js +16 -10
  81. package/lib/operations/execute_operation.js.map +1 -1
  82. package/lib/operations/find.js +6 -3
  83. package/lib/operations/find.js.map +1 -1
  84. package/lib/operations/find_and_modify.js +2 -2
  85. package/lib/operations/find_and_modify.js.map +1 -1
  86. package/lib/operations/get_more.js +2 -1
  87. package/lib/operations/get_more.js.map +1 -1
  88. package/lib/operations/indexes.js +6 -6
  89. package/lib/operations/indexes.js.map +1 -1
  90. package/lib/operations/insert.js +6 -6
  91. package/lib/operations/insert.js.map +1 -1
  92. package/lib/operations/kill_cursors.js +5 -2
  93. package/lib/operations/kill_cursors.js.map +1 -1
  94. package/lib/operations/list_collections.js +2 -2
  95. package/lib/operations/list_collections.js.map +1 -1
  96. package/lib/operations/list_databases.js +2 -2
  97. package/lib/operations/list_databases.js.map +1 -1
  98. package/lib/operations/operation.js.map +1 -1
  99. package/lib/operations/profiling_level.js +2 -2
  100. package/lib/operations/profiling_level.js.map +1 -1
  101. package/lib/operations/remove_user.js +2 -2
  102. package/lib/operations/remove_user.js.map +1 -1
  103. package/lib/operations/rename.js +2 -2
  104. package/lib/operations/rename.js.map +1 -1
  105. package/lib/operations/run_command.js +6 -4
  106. package/lib/operations/run_command.js.map +1 -1
  107. package/lib/operations/search_indexes/create.js +5 -2
  108. package/lib/operations/search_indexes/create.js.map +1 -1
  109. package/lib/operations/search_indexes/drop.js +2 -2
  110. package/lib/operations/search_indexes/drop.js.map +1 -1
  111. package/lib/operations/search_indexes/update.js +2 -2
  112. package/lib/operations/search_indexes/update.js.map +1 -1
  113. package/lib/operations/set_profiling_level.js +2 -2
  114. package/lib/operations/set_profiling_level.js.map +1 -1
  115. package/lib/operations/stats.js +2 -2
  116. package/lib/operations/stats.js.map +1 -1
  117. package/lib/operations/update.js +8 -8
  118. package/lib/operations/update.js.map +1 -1
  119. package/lib/operations/validate_collection.js +2 -2
  120. package/lib/operations/validate_collection.js.map +1 -1
  121. package/lib/sdam/server.js +4 -1
  122. package/lib/sdam/server.js.map +1 -1
  123. package/lib/sdam/server_description.js +2 -0
  124. package/lib/sdam/server_description.js.map +1 -1
  125. package/lib/sdam/topology.js +38 -11
  126. package/lib/sdam/topology.js.map +1 -1
  127. package/lib/sessions.js +145 -74
  128. package/lib/sessions.js.map +1 -1
  129. package/lib/timeout.js +217 -16
  130. package/lib/timeout.js.map +1 -1
  131. package/lib/utils.js +31 -17
  132. package/lib/utils.js.map +1 -1
  133. package/lib/write_concern.js.map +1 -1
  134. package/mongodb.d.ts +558 -38
  135. package/package.json +2 -2
  136. package/src/admin.ts +6 -2
  137. package/src/bulk/common.ts +17 -5
  138. package/src/change_stream.ts +127 -52
  139. package/src/client-side-encryption/auto_encrypter.ts +12 -5
  140. package/src/client-side-encryption/client_encryption.ts +103 -20
  141. package/src/client-side-encryption/state_machine.ts +66 -32
  142. package/src/cmap/connection.ts +105 -8
  143. package/src/cmap/connection_pool.ts +14 -14
  144. package/src/cmap/wire_protocol/on_data.ts +11 -1
  145. package/src/cmap/wire_protocol/responses.ts +35 -1
  146. package/src/collection.ts +81 -9
  147. package/src/connection_string.ts +2 -0
  148. package/src/cursor/abstract_cursor.ts +286 -39
  149. package/src/cursor/aggregation_cursor.ts +54 -8
  150. package/src/cursor/change_stream_cursor.ts +6 -2
  151. package/src/cursor/client_bulk_write_cursor.ts +6 -2
  152. package/src/cursor/find_cursor.ts +40 -9
  153. package/src/cursor/list_collections_cursor.ts +1 -1
  154. package/src/cursor/list_indexes_cursor.ts +1 -1
  155. package/src/cursor/run_command_cursor.ts +50 -5
  156. package/src/db.ts +75 -7
  157. package/src/error.ts +26 -1
  158. package/src/explain.ts +85 -0
  159. package/src/gridfs/download.ts +43 -4
  160. package/src/gridfs/index.ts +64 -16
  161. package/src/gridfs/upload.ts +152 -45
  162. package/src/index.ts +26 -4
  163. package/src/mongo_client.ts +75 -3
  164. package/src/operations/aggregate.ts +10 -2
  165. package/src/operations/bulk_write.ts +9 -2
  166. package/src/operations/client_bulk_write/client_bulk_write.ts +11 -3
  167. package/src/operations/client_bulk_write/executor.ts +15 -3
  168. package/src/operations/command.ts +18 -8
  169. package/src/operations/count.ts +10 -3
  170. package/src/operations/create_collection.ts +14 -7
  171. package/src/operations/delete.ts +15 -6
  172. package/src/operations/distinct.ts +7 -2
  173. package/src/operations/drop.ts +18 -8
  174. package/src/operations/estimated_document_count.ts +7 -2
  175. package/src/operations/execute_operation.ts +22 -13
  176. package/src/operations/find.ts +17 -5
  177. package/src/operations/find_and_modify.ts +7 -2
  178. package/src/operations/get_more.ts +4 -1
  179. package/src/operations/indexes.ts +20 -7
  180. package/src/operations/insert.ts +13 -6
  181. package/src/operations/kill_cursors.ts +10 -2
  182. package/src/operations/list_collections.ts +10 -1
  183. package/src/operations/list_databases.ts +9 -2
  184. package/src/operations/operation.ts +16 -2
  185. package/src/operations/profiling_level.ts +7 -2
  186. package/src/operations/remove_user.ts +7 -2
  187. package/src/operations/rename.ts +7 -2
  188. package/src/operations/run_command.ts +23 -4
  189. package/src/operations/search_indexes/create.ts +10 -2
  190. package/src/operations/search_indexes/drop.ts +7 -2
  191. package/src/operations/search_indexes/update.ts +7 -2
  192. package/src/operations/set_profiling_level.ts +4 -2
  193. package/src/operations/stats.ts +7 -2
  194. package/src/operations/update.ts +16 -8
  195. package/src/operations/validate_collection.ts +7 -2
  196. package/src/sdam/server.ts +14 -4
  197. package/src/sdam/server_description.ts +4 -0
  198. package/src/sdam/topology.ts +43 -18
  199. package/src/sessions.ts +193 -89
  200. package/src/timeout.ts +310 -23
  201. package/src/transactions.ts +1 -1
  202. package/src/utils.ts +42 -28
  203. package/src/write_concern.ts +6 -3
@@ -21,9 +21,11 @@ import {
21
21
  } from '../constants';
22
22
  import {
23
23
  MongoCompatibilityError,
24
+ MONGODB_ERROR_CODES,
24
25
  MongoMissingDependencyError,
25
26
  MongoNetworkError,
26
27
  MongoNetworkTimeoutError,
28
+ MongoOperationTimeoutError,
27
29
  MongoParseError,
28
30
  MongoServerError,
29
31
  MongoUnexpectedServerResponseError
@@ -35,6 +37,7 @@ import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
35
37
  import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
36
38
  import { ServerType } from '../sdam/common';
37
39
  import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
40
+ import { type TimeoutContext, TimeoutError } from '../timeout';
38
41
  import {
39
42
  BufferPool,
40
43
  calculateDurationInMs,
@@ -88,6 +91,7 @@ export interface CommandOptions extends BSONSerializeOptions {
88
91
  documentsReturnedIn?: string;
89
92
  noResponse?: boolean;
90
93
  omitReadPreference?: boolean;
94
+ omitMaxTimeMS?: boolean;
91
95
 
92
96
  // TODO(NODE-2802): Currently the CommandOptions take a property willRetryWrite which is a hint
93
97
  // from executeOperation that the txnNum should be applied to this command.
@@ -99,6 +103,9 @@ export interface CommandOptions extends BSONSerializeOptions {
99
103
  writeConcern?: WriteConcern;
100
104
 
101
105
  directConnection?: boolean;
106
+
107
+ /** @internal */
108
+ timeoutContext?: TimeoutContext;
102
109
  }
103
110
 
104
111
  /** @public */
@@ -420,6 +427,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
420
427
  ...options
421
428
  };
422
429
 
430
+ if (!options.omitMaxTimeMS) {
431
+ const maxTimeMS = options.timeoutContext?.maxTimeMS;
432
+ if (maxTimeMS && maxTimeMS > 0 && Number.isFinite(maxTimeMS)) cmd.maxTimeMS = maxTimeMS;
433
+ }
434
+
423
435
  const message = this.supportsOpMsg
424
436
  ? new OpMsgRequest(db, cmd, commandOptions)
425
437
  : new OpQueryRequest(db, cmd, commandOptions);
@@ -434,7 +446,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
434
446
  ): AsyncGenerator<MongoDBResponse> {
435
447
  this.throwIfAborted();
436
448
 
437
- if (typeof options.socketTimeoutMS === 'number') {
449
+ if (options.timeoutContext?.csotEnabled()) {
450
+ this.socket.setTimeout(0);
451
+ } else if (typeof options.socketTimeoutMS === 'number') {
438
452
  this.socket.setTimeout(options.socketTimeoutMS);
439
453
  } else if (this.socketTimeoutMS !== 0) {
440
454
  this.socket.setTimeout(this.socketTimeoutMS);
@@ -443,7 +457,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
443
457
  try {
444
458
  await this.writeCommand(message, {
445
459
  agreedCompressor: this.description.compressor ?? 'none',
446
- zlibCompressionLevel: this.description.zlibCompressionLevel
460
+ zlibCompressionLevel: this.description.zlibCompressionLevel,
461
+ timeoutContext: options.timeoutContext
447
462
  });
448
463
 
449
464
  if (options.noResponse || message.moreToCome) {
@@ -453,7 +468,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
453
468
 
454
469
  this.throwIfAborted();
455
470
 
456
- for await (const response of this.readMany()) {
471
+ if (
472
+ options.timeoutContext?.csotEnabled() &&
473
+ options.timeoutContext.minRoundTripTime != null &&
474
+ options.timeoutContext.remainingTimeMS < options.timeoutContext.minRoundTripTime
475
+ ) {
476
+ throw new MongoOperationTimeoutError(
477
+ 'Server roundtrip time is greater than the time remaining'
478
+ );
479
+ }
480
+
481
+ for await (const response of this.readMany({ timeoutContext: options.timeoutContext })) {
457
482
  this.socket.setTimeout(0);
458
483
  const bson = response.parse();
459
484
 
@@ -480,7 +505,6 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
480
505
  responseType?: MongoDBResponseConstructor
481
506
  ) {
482
507
  const message = this.prepareCommand(ns.db, command, options);
483
-
484
508
  let started = 0;
485
509
  if (this.shouldEmitAndLogCommand) {
486
510
  started = now();
@@ -522,6 +546,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
522
546
  }
523
547
 
524
548
  if (document.ok === 0) {
549
+ if (options.timeoutContext?.csotEnabled() && document.isMaxTimeExpiredError) {
550
+ throw new MongoOperationTimeoutError('Server reported a timeout error', {
551
+ cause: new MongoServerError((object ??= document.toObject(bsonOptions)))
552
+ });
553
+ }
525
554
  throw new MongoServerError((object ??= document.toObject(bsonOptions)));
526
555
  }
527
556
 
@@ -595,6 +624,28 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
595
624
  ): Promise<Document> {
596
625
  this.throwIfAborted();
597
626
  for await (const document of this.sendCommand(ns, command, options, responseType)) {
627
+ if (options.timeoutContext?.csotEnabled()) {
628
+ if (MongoDBResponse.is(document)) {
629
+ if (document.isMaxTimeExpiredError) {
630
+ throw new MongoOperationTimeoutError('Server reported a timeout error', {
631
+ cause: new MongoServerError(document.toObject())
632
+ });
633
+ }
634
+ } else {
635
+ if (
636
+ (Array.isArray(document?.writeErrors) &&
637
+ document.writeErrors.some(
638
+ error => error?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired
639
+ )) ||
640
+ document?.writeConcernError?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired
641
+ ) {
642
+ throw new MongoOperationTimeoutError('Server reported a timeout error', {
643
+ cause: new MongoServerError(document)
644
+ });
645
+ }
646
+ }
647
+ }
648
+
598
649
  return document;
599
650
  }
600
651
  throw new MongoUnexpectedServerResponseError('Unable to get response from server');
@@ -630,7 +681,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
630
681
  */
631
682
  private async writeCommand(
632
683
  command: WriteProtocolMessageType,
633
- options: { agreedCompressor?: CompressorName; zlibCompressionLevel?: number }
684
+ options: {
685
+ agreedCompressor?: CompressorName;
686
+ zlibCompressionLevel?: number;
687
+ timeoutContext?: TimeoutContext;
688
+ }
634
689
  ): Promise<void> {
635
690
  const finalCommand =
636
691
  options.agreedCompressor === 'none' || !OpCompressedRequest.canCompress(command)
@@ -642,8 +697,36 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
642
697
 
643
698
  const buffer = Buffer.concat(await finalCommand.toBin());
644
699
 
700
+ if (options.timeoutContext?.csotEnabled()) {
701
+ if (
702
+ options.timeoutContext.minRoundTripTime != null &&
703
+ options.timeoutContext.remainingTimeMS < options.timeoutContext.minRoundTripTime
704
+ ) {
705
+ throw new MongoOperationTimeoutError(
706
+ 'Server roundtrip time is greater than the time remaining'
707
+ );
708
+ }
709
+ }
710
+
645
711
  if (this.socket.write(buffer)) return;
646
- return await once(this.socket, 'drain');
712
+
713
+ const drainEvent = once<void>(this.socket, 'drain');
714
+ const timeout = options?.timeoutContext?.timeoutForSocketWrite;
715
+ if (timeout) {
716
+ try {
717
+ return await Promise.race([drainEvent, timeout]);
718
+ } catch (error) {
719
+ let err = error;
720
+ if (TimeoutError.is(error)) {
721
+ err = new MongoOperationTimeoutError('Timed out at socket write');
722
+ this.cleanup(err);
723
+ }
724
+ throw error;
725
+ } finally {
726
+ timeout.clear();
727
+ }
728
+ }
729
+ return await drainEvent;
647
730
  }
648
731
 
649
732
  /**
@@ -655,10 +738,13 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
655
738
  *
656
739
  * Note that `for-await` loops call `return` automatically when the loop is exited.
657
740
  */
658
- private async *readMany(): AsyncGenerator<OpMsgResponse | OpReply> {
741
+ private async *readMany(options: {
742
+ timeoutContext?: TimeoutContext;
743
+ }): AsyncGenerator<OpMsgResponse | OpReply> {
659
744
  try {
660
- this.dataEvents = onData(this.messageStream);
745
+ this.dataEvents = onData(this.messageStream, options);
661
746
  this.messageStream.resume();
747
+
662
748
  for await (const message of this.dataEvents) {
663
749
  const response = await decompressResponse(message);
664
750
  yield response;
@@ -667,6 +753,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
667
753
  return;
668
754
  }
669
755
  }
756
+ } catch (readError) {
757
+ const err = readError;
758
+ if (TimeoutError.is(readError)) {
759
+ const error = new MongoOperationTimeoutError(
760
+ `Timed out during socket read (${readError.duration}ms)`
761
+ );
762
+ this.dataEvents = null;
763
+ this.onError(error);
764
+ throw error;
765
+ }
766
+ throw err;
670
767
  } finally {
671
768
  this.dataEvents = null;
672
769
  this.messageStream.pause();
@@ -21,12 +21,13 @@ import {
21
21
  MongoInvalidArgumentError,
22
22
  MongoMissingCredentialsError,
23
23
  MongoNetworkError,
24
+ MongoOperationTimeoutError,
24
25
  MongoRuntimeError,
25
26
  MongoServerError
26
27
  } from '../error';
27
28
  import { CancellationToken, TypedEventEmitter } from '../mongo_types';
28
29
  import type { Server } from '../sdam/server';
29
- import { Timeout, TimeoutError } from '../timeout';
30
+ import { type TimeoutContext, TimeoutError } from '../timeout';
30
31
  import { type Callback, List, makeCounter, now, promiseWithResolvers } from '../utils';
31
32
  import { connect } from './connect';
32
33
  import { Connection, type ConnectionEvents, type ConnectionOptions } from './connection';
@@ -102,7 +103,6 @@ export interface ConnectionPoolOptions extends Omit<ConnectionOptions, 'id' | 'g
102
103
  export interface WaitQueueMember {
103
104
  resolve: (conn: Connection) => void;
104
105
  reject: (err: AnyError) => void;
105
- timeout: Timeout;
106
106
  [kCancelled]?: boolean;
107
107
  checkoutTime: number;
108
108
  }
@@ -355,23 +355,20 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
355
355
  * will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
356
356
  * explicitly destroyed by the new owner.
357
357
  */
358
- async checkOut(): Promise<Connection> {
358
+ async checkOut(options: { timeoutContext: TimeoutContext }): Promise<Connection> {
359
359
  const checkoutTime = now();
360
360
  this.emitAndLog(
361
361
  ConnectionPool.CONNECTION_CHECK_OUT_STARTED,
362
362
  new ConnectionCheckOutStartedEvent(this)
363
363
  );
364
364
 
365
- const waitQueueTimeoutMS = this.options.waitQueueTimeoutMS;
366
-
367
365
  const { promise, resolve, reject } = promiseWithResolvers<Connection>();
368
366
 
369
- const timeout = Timeout.expires(waitQueueTimeoutMS);
367
+ const timeout = options.timeoutContext.connectionCheckoutTimeout;
370
368
 
371
369
  const waitQueueMember: WaitQueueMember = {
372
370
  resolve,
373
371
  reject,
374
- timeout,
375
372
  checkoutTime
376
373
  };
377
374
 
@@ -379,13 +376,13 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
379
376
  process.nextTick(() => this.processWaitQueue());
380
377
 
381
378
  try {
382
- return await Promise.race([promise, waitQueueMember.timeout]);
379
+ timeout?.throwIfExpired();
380
+ return await (timeout ? Promise.race([promise, timeout]) : promise);
383
381
  } catch (error) {
384
382
  if (TimeoutError.is(error)) {
383
+ timeout?.clear();
385
384
  waitQueueMember[kCancelled] = true;
386
385
 
387
- waitQueueMember.timeout.clear();
388
-
389
386
  this.emitAndLog(
390
387
  ConnectionPool.CONNECTION_CHECK_OUT_FAILED,
391
388
  new ConnectionCheckOutFailedEvent(this, 'timeout', waitQueueMember.checkoutTime)
@@ -396,9 +393,16 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
396
393
  : 'Timed out while checking out a connection from connection pool',
397
394
  this.address
398
395
  );
396
+ if (options.timeoutContext.csotEnabled()) {
397
+ throw new MongoOperationTimeoutError('Timed out during connection checkout', {
398
+ cause: timeoutError
399
+ });
400
+ }
399
401
  throw timeoutError;
400
402
  }
401
403
  throw error;
404
+ } finally {
405
+ if (options.timeoutContext.clearConnectionCheckoutTimeout) timeout?.clear();
402
406
  }
403
407
  }
404
408
 
@@ -764,7 +768,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
764
768
  ConnectionPool.CONNECTION_CHECK_OUT_FAILED,
765
769
  new ConnectionCheckOutFailedEvent(this, reason, waitQueueMember.checkoutTime, error)
766
770
  );
767
- waitQueueMember.timeout.clear();
768
771
  this[kWaitQueue].shift();
769
772
  waitQueueMember.reject(error);
770
773
  continue;
@@ -785,7 +788,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
785
788
  ConnectionPool.CONNECTION_CHECKED_OUT,
786
789
  new ConnectionCheckedOutEvent(this, connection, waitQueueMember.checkoutTime)
787
790
  );
788
- waitQueueMember.timeout.clear();
789
791
 
790
792
  this[kWaitQueue].shift();
791
793
  waitQueueMember.resolve(connection);
@@ -828,8 +830,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
828
830
  );
829
831
  waitQueueMember.resolve(connection);
830
832
  }
831
-
832
- waitQueueMember.timeout.clear();
833
833
  }
834
834
  process.nextTick(() => this.processWaitQueue());
835
835
  });
@@ -1,5 +1,6 @@
1
1
  import { type EventEmitter } from 'events';
2
2
 
3
+ import { type TimeoutContext } from '../../timeout';
3
4
  import { List, promiseWithResolvers } from '../../utils';
4
5
 
5
6
  /**
@@ -18,7 +19,10 @@ type PendingPromises = Omit<
18
19
  * Returns an AsyncIterator that iterates each 'data' event emitted from emitter.
19
20
  * It will reject upon an error event.
20
21
  */
21
- export function onData(emitter: EventEmitter) {
22
+ export function onData(
23
+ emitter: EventEmitter,
24
+ { timeoutContext }: { timeoutContext?: TimeoutContext }
25
+ ) {
22
26
  // Setup pending events and pending promise lists
23
27
  /**
24
28
  * When the caller has not yet called .next(), we store the
@@ -87,6 +91,10 @@ export function onData(emitter: EventEmitter) {
87
91
  emitter.on('data', eventHandler);
88
92
  emitter.on('error', errorHandler);
89
93
 
94
+ const timeoutForSocketRead = timeoutContext?.timeoutForSocketRead;
95
+ timeoutForSocketRead?.throwIfExpired();
96
+ timeoutForSocketRead?.then(undefined, errorHandler);
97
+
90
98
  return iterator;
91
99
 
92
100
  function eventHandler(value: Buffer) {
@@ -97,6 +105,7 @@ export function onData(emitter: EventEmitter) {
97
105
 
98
106
  function errorHandler(err: Error) {
99
107
  const promise = unconsumedPromises.shift();
108
+
100
109
  if (promise != null) promise.reject(err);
101
110
  else error = err;
102
111
  void closeHandler();
@@ -107,6 +116,7 @@ export function onData(emitter: EventEmitter) {
107
116
  emitter.off('data', eventHandler);
108
117
  emitter.off('error', errorHandler);
109
118
  finished = true;
119
+ timeoutForSocketRead?.clear();
110
120
  const doneResult = { value: undefined, done: finished } as const;
111
121
 
112
122
  for (const promise of unconsumedPromises) {
@@ -10,7 +10,7 @@ import {
10
10
  pluckBSONSerializeOptions,
11
11
  type Timestamp
12
12
  } from '../../bson';
13
- import { MongoUnexpectedServerResponseError } from '../../error';
13
+ import { MONGODB_ERROR_CODES, MongoUnexpectedServerResponseError } from '../../error';
14
14
  import { type ClusterTime } from '../../sdam/common';
15
15
  import { decorateDecryptionResult, ns } from '../../utils';
16
16
  import {
@@ -110,6 +110,40 @@ export class MongoDBResponse extends OnDemandDocument {
110
110
  // {ok:1}
111
111
  static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
112
112
 
113
+ /**
114
+ * Returns true iff:
115
+ * - ok is 0 and the top-level code === 50
116
+ * - ok is 1 and the writeErrors array contains a code === 50
117
+ * - ok is 1 and the writeConcern object contains a code === 50
118
+ */
119
+ get isMaxTimeExpiredError() {
120
+ // {ok: 0, code: 50 ... }
121
+ const isTopLevel = this.ok === 0 && this.code === MONGODB_ERROR_CODES.MaxTimeMSExpired;
122
+ if (isTopLevel) return true;
123
+
124
+ if (this.ok === 0) return false;
125
+
126
+ // {ok: 1, writeConcernError: {code: 50 ... }}
127
+ const isWriteConcern =
128
+ this.get('writeConcernError', BSONType.object)?.getNumber('code') ===
129
+ MONGODB_ERROR_CODES.MaxTimeMSExpired;
130
+ if (isWriteConcern) return true;
131
+
132
+ const writeErrors = this.get('writeErrors', BSONType.array);
133
+ if (writeErrors?.size()) {
134
+ for (let i = 0; i < writeErrors.size(); i++) {
135
+ const isWriteError =
136
+ writeErrors.get(i, BSONType.object)?.getNumber('code') ===
137
+ MONGODB_ERROR_CODES.MaxTimeMSExpired;
138
+
139
+ // {ok: 1, writeErrors: [{code: 50 ... }]}
140
+ if (isWriteError) return true;
141
+ }
142
+ }
143
+
144
+ return false;
145
+ }
146
+
113
147
  /**
114
148
  * Drivers can safely assume that the `recoveryToken` field is always a BSON document but drivers MUST NOT modify the
115
149
  * contents of the document.
package/src/collection.ts CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  type ListSearchIndexesOptions
12
12
  } from './cursor/list_search_indexes_cursor';
13
13
  import type { Db } from './db';
14
- import { MongoInvalidArgumentError } from './error';
14
+ import { MongoInvalidArgumentError, MongoOperationTimeoutError } from './error';
15
15
  import type { MongoClient, PkFactory } from './mongo_client';
16
16
  import type {
17
17
  Filter,
@@ -115,7 +115,10 @@ export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOpt
115
115
  readConcern?: ReadConcernLike;
116
116
  /** The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). */
117
117
  readPreference?: ReadPreferenceLike;
118
- /** @internal TODO(NODE-5688): make this public */
118
+ /**
119
+ * @experimental
120
+ * Specifies the time an operation will run until it throws a timeout error
121
+ */
119
122
  timeoutMS?: number;
120
123
  }
121
124
 
@@ -262,6 +265,10 @@ export class Collection<TSchema extends Document = Document> {
262
265
  this.s.collectionHint = normalizeHintField(v);
263
266
  }
264
267
 
268
+ public get timeoutMS(): number | undefined {
269
+ return this.s.options.timeoutMS;
270
+ }
271
+
265
272
  /**
266
273
  * Inserts a single document into MongoDB. If documents passed in do not contain the **_id** field,
267
274
  * one will be added to each of the documents missing it by the driver, mutating the document. This behavior
@@ -465,10 +472,14 @@ export class Collection<TSchema extends Document = Document> {
465
472
  // Intentionally, we do not inherit options from parent for this operation.
466
473
  return await executeOperation(
467
474
  this.client,
468
- new RenameOperation(this as TODO_NODE_3286, newName, {
469
- ...options,
470
- readPreference: ReadPreference.PRIMARY
471
- }) as TODO_NODE_3286
475
+ new RenameOperation(
476
+ this as TODO_NODE_3286,
477
+ newName,
478
+ resolveOptions(undefined, {
479
+ ...options,
480
+ readPreference: ReadPreference.PRIMARY
481
+ })
482
+ ) as TODO_NODE_3286
472
483
  );
473
484
  }
474
485
 
@@ -492,12 +503,18 @@ export class Collection<TSchema extends Document = Document> {
492
503
  */
493
504
  async findOne(): Promise<WithId<TSchema> | null>;
494
505
  async findOne(filter: Filter<TSchema>): Promise<WithId<TSchema> | null>;
495
- async findOne(filter: Filter<TSchema>, options: FindOptions): Promise<WithId<TSchema> | null>;
506
+ async findOne(
507
+ filter: Filter<TSchema>,
508
+ options: Omit<FindOptions, 'timeoutMode'>
509
+ ): Promise<WithId<TSchema> | null>;
496
510
 
497
511
  // allow an override of the schema.
498
512
  async findOne<T = TSchema>(): Promise<T | null>;
499
513
  async findOne<T = TSchema>(filter: Filter<TSchema>): Promise<T | null>;
500
- async findOne<T = TSchema>(filter: Filter<TSchema>, options?: FindOptions): Promise<T | null>;
514
+ async findOne<T = TSchema>(
515
+ filter: Filter<TSchema>,
516
+ options?: Omit<FindOptions, 'timeoutMode'>
517
+ ): Promise<T | null>;
501
518
 
502
519
  async findOne(
503
520
  filter: Filter<TSchema> = {},
@@ -669,7 +686,9 @@ export class Collection<TSchema extends Document = Document> {
669
686
  new DropIndexOperation(this as TODO_NODE_3286, '*', resolveOptions(this, options))
670
687
  );
671
688
  return true;
672
- } catch {
689
+ } catch (error) {
690
+ // TODO(NODE-6517): Driver should only filter for namespace not found error. Other errors should be thrown.
691
+ if (error instanceof MongoOperationTimeoutError) throw error;
673
692
  return false;
674
693
  }
675
694
  }
@@ -1033,6 +1052,59 @@ export class Collection<TSchema extends Document = Document> {
1033
1052
  * });
1034
1053
  * ```
1035
1054
  *
1055
+ * @remarks
1056
+ * When `timeoutMS` is configured for a change stream, it will have different behaviour depending
1057
+ * on whether the change stream is in iterator mode or emitter mode. In both cases, a change
1058
+ * stream will time out if it does not receive a change event within `timeoutMS` of the last change
1059
+ * event.
1060
+ *
1061
+ * Note that if a change stream is consistently timing out when watching a collection, database or
1062
+ * client that is being changed, then this may be due to the server timing out before it can finish
1063
+ * processing the existing oplog. To address this, restart the change stream with a higher
1064
+ * `timeoutMS`.
1065
+ *
1066
+ * If the change stream times out the initial aggregate operation to establish the change stream on
1067
+ * the server, then the client will close the change stream. If the getMore calls to the server
1068
+ * time out, then the change stream will be left open, but will throw a MongoOperationTimeoutError
1069
+ * when in iterator mode and emit an error event that returns a MongoOperationTimeoutError in
1070
+ * emitter mode.
1071
+ *
1072
+ * To determine whether or not the change stream is still open following a timeout, check the
1073
+ * {@link ChangeStream.closed} getter.
1074
+ *
1075
+ * @example
1076
+ * In iterator mode, if a next() call throws a timeout error, it will attempt to resume the change stream.
1077
+ * The next call can just be retried after this succeeds.
1078
+ * ```ts
1079
+ * const changeStream = collection.watch([], { timeoutMS: 100 });
1080
+ * try {
1081
+ * await changeStream.next();
1082
+ * } catch (e) {
1083
+ * if (e instanceof MongoOperationTimeoutError && !changeStream.closed) {
1084
+ * await changeStream.next();
1085
+ * }
1086
+ * throw e;
1087
+ * }
1088
+ * ```
1089
+ *
1090
+ * @example
1091
+ * In emitter mode, if the change stream goes `timeoutMS` without emitting a change event, it will
1092
+ * emit an error event that returns a MongoOperationTimeoutError, but will not close the change
1093
+ * stream unless the resume attempt fails. There is no need to re-establish change listeners as
1094
+ * this will automatically continue emitting change events once the resume attempt completes.
1095
+ *
1096
+ * ```ts
1097
+ * const changeStream = collection.watch([], { timeoutMS: 100 });
1098
+ * changeStream.on('change', console.log);
1099
+ * changeStream.on('error', e => {
1100
+ * if (e instanceof MongoOperationTimeoutError && !changeStream.closed) {
1101
+ * // do nothing
1102
+ * } else {
1103
+ * changeStream.close();
1104
+ * }
1105
+ * });
1106
+ * ```
1107
+ *
1036
1108
  * @param pipeline - An array of {@link https://www.mongodb.com/docs/manual/reference/operator/aggregation-pipeline/|aggregation pipeline stages} through which to pass change stream documents. This allows for filtering (using $match) and manipulating the change stream documents.
1037
1109
  * @param options - Optional settings for the command
1038
1110
  * @typeParam TLocal - Type of the data being detected by the change stream
@@ -1092,6 +1092,7 @@ export const OPTIONS = {
1092
1092
  type: 'string'
1093
1093
  },
1094
1094
  socketTimeoutMS: {
1095
+ // TODO(NODE-6491): deprecated: 'Please use timeoutMS instead',
1095
1096
  default: 0,
1096
1097
  type: 'uint'
1097
1098
  },
@@ -1162,6 +1163,7 @@ export const OPTIONS = {
1162
1163
  }
1163
1164
  },
1164
1165
  waitQueueTimeoutMS: {
1166
+ // TODO(NODE-6491): deprecated: 'Please use timeoutMS instead',
1165
1167
  default: 0,
1166
1168
  type: 'uint'
1167
1169
  },