mongodb 6.10.0 → 6.11.0-dev.20241128.sha.4842cd8a

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 (228) hide show
  1. package/README.md +2 -0
  2. package/lib/admin.js +3 -2
  3. package/lib/admin.js.map +1 -1
  4. package/lib/beta.d.ts +562 -45
  5. package/lib/bulk/common.js +4 -4
  6. package/lib/bulk/common.js.map +1 -1
  7. package/lib/change_stream.js +111 -51
  8. package/lib/change_stream.js.map +1 -1
  9. package/lib/client-side-encryption/auto_encrypter.js +8 -5
  10. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  11. package/lib/client-side-encryption/client_encryption.js +48 -18
  12. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  13. package/lib/client-side-encryption/state_machine.js +43 -29
  14. package/lib/client-side-encryption/state_machine.js.map +1 -1
  15. package/lib/cmap/auth/mongo_credentials.js +5 -2
  16. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  17. package/lib/cmap/auth/mongodb_aws.js +1 -1
  18. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  19. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +38 -0
  20. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -0
  21. package/lib/cmap/auth/mongodb_oidc.js +2 -0
  22. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  23. package/lib/cmap/connect.js +13 -1
  24. package/lib/cmap/connect.js.map +1 -1
  25. package/lib/cmap/connection.js +75 -17
  26. package/lib/cmap/connection.js.map +1 -1
  27. package/lib/cmap/connection_pool.js +14 -12
  28. package/lib/cmap/connection_pool.js.map +1 -1
  29. package/lib/cmap/wire_protocol/on_data.js +5 -1
  30. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  31. package/lib/cmap/wire_protocol/responses.js +30 -0
  32. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  33. package/lib/collection.js +62 -3
  34. package/lib/collection.js.map +1 -1
  35. package/lib/connection_string.js +2 -0
  36. package/lib/connection_string.js.map +1 -1
  37. package/lib/cursor/abstract_cursor.js +221 -38
  38. package/lib/cursor/abstract_cursor.js.map +1 -1
  39. package/lib/cursor/aggregation_cursor.js +29 -7
  40. package/lib/cursor/aggregation_cursor.js.map +1 -1
  41. package/lib/cursor/change_stream_cursor.js +2 -2
  42. package/lib/cursor/change_stream_cursor.js.map +1 -1
  43. package/lib/cursor/client_bulk_write_cursor.js +1 -1
  44. package/lib/cursor/client_bulk_write_cursor.js.map +1 -1
  45. package/lib/cursor/find_cursor.js +18 -8
  46. package/lib/cursor/find_cursor.js.map +1 -1
  47. package/lib/cursor/list_collections_cursor.js +1 -1
  48. package/lib/cursor/list_collections_cursor.js.map +1 -1
  49. package/lib/cursor/list_indexes_cursor.js +1 -1
  50. package/lib/cursor/list_indexes_cursor.js.map +1 -1
  51. package/lib/cursor/run_command_cursor.js +6 -4
  52. package/lib/cursor/run_command_cursor.js.map +1 -1
  53. package/lib/db.js +63 -3
  54. package/lib/db.js.map +1 -1
  55. package/lib/error.js +34 -9
  56. package/lib/error.js.map +1 -1
  57. package/lib/explain.js +57 -1
  58. package/lib/explain.js.map +1 -1
  59. package/lib/gridfs/download.js +31 -3
  60. package/lib/gridfs/download.js.map +1 -1
  61. package/lib/gridfs/index.js +49 -14
  62. package/lib/gridfs/index.js.map +1 -1
  63. package/lib/gridfs/upload.js +80 -22
  64. package/lib/gridfs/upload.js.map +1 -1
  65. package/lib/index.js +9 -5
  66. package/lib/index.js.map +1 -1
  67. package/lib/mongo_client.js +70 -1
  68. package/lib/mongo_client.js.map +1 -1
  69. package/lib/operations/aggregate.js +2 -2
  70. package/lib/operations/aggregate.js.map +1 -1
  71. package/lib/operations/bulk_write.js +7 -2
  72. package/lib/operations/bulk_write.js.map +1 -1
  73. package/lib/operations/client_bulk_write/client_bulk_write.js +3 -3
  74. package/lib/operations/client_bulk_write/client_bulk_write.js.map +1 -1
  75. package/lib/operations/client_bulk_write/executor.js +14 -3
  76. package/lib/operations/client_bulk_write/executor.js.map +1 -1
  77. package/lib/operations/command.js +5 -2
  78. package/lib/operations/command.js.map +1 -1
  79. package/lib/operations/count.js +2 -2
  80. package/lib/operations/count.js.map +1 -1
  81. package/lib/operations/create_collection.js +8 -7
  82. package/lib/operations/create_collection.js.map +1 -1
  83. package/lib/operations/delete.js +6 -6
  84. package/lib/operations/delete.js.map +1 -1
  85. package/lib/operations/distinct.js +2 -2
  86. package/lib/operations/distinct.js.map +1 -1
  87. package/lib/operations/drop.js +8 -8
  88. package/lib/operations/drop.js.map +1 -1
  89. package/lib/operations/estimated_document_count.js +2 -2
  90. package/lib/operations/estimated_document_count.js.map +1 -1
  91. package/lib/operations/execute_operation.js +16 -10
  92. package/lib/operations/execute_operation.js.map +1 -1
  93. package/lib/operations/find.js +6 -3
  94. package/lib/operations/find.js.map +1 -1
  95. package/lib/operations/find_and_modify.js +2 -2
  96. package/lib/operations/find_and_modify.js.map +1 -1
  97. package/lib/operations/get_more.js +2 -1
  98. package/lib/operations/get_more.js.map +1 -1
  99. package/lib/operations/indexes.js +6 -6
  100. package/lib/operations/indexes.js.map +1 -1
  101. package/lib/operations/insert.js +6 -6
  102. package/lib/operations/insert.js.map +1 -1
  103. package/lib/operations/kill_cursors.js +5 -2
  104. package/lib/operations/kill_cursors.js.map +1 -1
  105. package/lib/operations/list_collections.js +2 -2
  106. package/lib/operations/list_collections.js.map +1 -1
  107. package/lib/operations/list_databases.js +2 -2
  108. package/lib/operations/list_databases.js.map +1 -1
  109. package/lib/operations/operation.js.map +1 -1
  110. package/lib/operations/profiling_level.js +2 -2
  111. package/lib/operations/profiling_level.js.map +1 -1
  112. package/lib/operations/remove_user.js +2 -2
  113. package/lib/operations/remove_user.js.map +1 -1
  114. package/lib/operations/rename.js +2 -2
  115. package/lib/operations/rename.js.map +1 -1
  116. package/lib/operations/run_command.js +6 -4
  117. package/lib/operations/run_command.js.map +1 -1
  118. package/lib/operations/search_indexes/create.js +5 -2
  119. package/lib/operations/search_indexes/create.js.map +1 -1
  120. package/lib/operations/search_indexes/drop.js +2 -2
  121. package/lib/operations/search_indexes/drop.js.map +1 -1
  122. package/lib/operations/search_indexes/update.js +2 -2
  123. package/lib/operations/search_indexes/update.js.map +1 -1
  124. package/lib/operations/set_profiling_level.js +2 -2
  125. package/lib/operations/set_profiling_level.js.map +1 -1
  126. package/lib/operations/stats.js +2 -2
  127. package/lib/operations/stats.js.map +1 -1
  128. package/lib/operations/update.js +8 -8
  129. package/lib/operations/update.js.map +1 -1
  130. package/lib/operations/validate_collection.js +2 -2
  131. package/lib/operations/validate_collection.js.map +1 -1
  132. package/lib/read_concern.js +1 -1
  133. package/lib/sdam/common.js +0 -7
  134. package/lib/sdam/common.js.map +1 -1
  135. package/lib/sdam/server.js +4 -1
  136. package/lib/sdam/server.js.map +1 -1
  137. package/lib/sdam/server_description.js +4 -2
  138. package/lib/sdam/server_description.js.map +1 -1
  139. package/lib/sdam/server_selection.js +5 -2
  140. package/lib/sdam/server_selection.js.map +1 -1
  141. package/lib/sdam/topology.js +38 -15
  142. package/lib/sdam/topology.js.map +1 -1
  143. package/lib/sessions.js +157 -98
  144. package/lib/sessions.js.map +1 -1
  145. package/lib/timeout.js +231 -16
  146. package/lib/timeout.js.map +1 -1
  147. package/lib/utils.js +36 -19
  148. package/lib/utils.js.map +1 -1
  149. package/lib/write_concern.js.map +1 -1
  150. package/mongodb.d.ts +562 -45
  151. package/package.json +18 -17
  152. package/src/admin.ts +6 -2
  153. package/src/bulk/common.ts +17 -5
  154. package/src/change_stream.ts +127 -52
  155. package/src/client-side-encryption/auto_encrypter.ts +12 -5
  156. package/src/client-side-encryption/client_encryption.ts +103 -20
  157. package/src/client-side-encryption/state_machine.ts +66 -32
  158. package/src/cmap/auth/mongo_credentials.ts +6 -3
  159. package/src/cmap/auth/mongodb_aws.ts +1 -1
  160. package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +38 -0
  161. package/src/cmap/auth/mongodb_oidc.ts +3 -1
  162. package/src/cmap/connect.ts +18 -1
  163. package/src/cmap/connection.ts +105 -17
  164. package/src/cmap/connection_pool.ts +15 -17
  165. package/src/cmap/handshake/client_metadata.ts +1 -1
  166. package/src/cmap/wire_protocol/on_data.ts +11 -1
  167. package/src/cmap/wire_protocol/responses.ts +35 -1
  168. package/src/collection.ts +81 -9
  169. package/src/connection_string.ts +2 -0
  170. package/src/cursor/abstract_cursor.ts +287 -39
  171. package/src/cursor/aggregation_cursor.ts +54 -8
  172. package/src/cursor/change_stream_cursor.ts +6 -2
  173. package/src/cursor/client_bulk_write_cursor.ts +6 -2
  174. package/src/cursor/find_cursor.ts +40 -9
  175. package/src/cursor/list_collections_cursor.ts +1 -1
  176. package/src/cursor/list_indexes_cursor.ts +1 -1
  177. package/src/cursor/run_command_cursor.ts +50 -5
  178. package/src/db.ts +75 -7
  179. package/src/error.ts +33 -8
  180. package/src/explain.ts +85 -0
  181. package/src/gridfs/download.ts +43 -4
  182. package/src/gridfs/index.ts +64 -16
  183. package/src/gridfs/upload.ts +153 -45
  184. package/src/index.ts +27 -5
  185. package/src/mongo_client.ts +76 -4
  186. package/src/operations/aggregate.ts +10 -2
  187. package/src/operations/bulk_write.ts +9 -2
  188. package/src/operations/client_bulk_write/client_bulk_write.ts +11 -3
  189. package/src/operations/client_bulk_write/executor.ts +15 -3
  190. package/src/operations/command.ts +18 -8
  191. package/src/operations/count.ts +10 -3
  192. package/src/operations/create_collection.ts +14 -7
  193. package/src/operations/delete.ts +15 -6
  194. package/src/operations/distinct.ts +7 -2
  195. package/src/operations/drop.ts +18 -8
  196. package/src/operations/estimated_document_count.ts +7 -2
  197. package/src/operations/execute_operation.ts +22 -13
  198. package/src/operations/find.ts +17 -5
  199. package/src/operations/find_and_modify.ts +7 -2
  200. package/src/operations/get_more.ts +4 -1
  201. package/src/operations/indexes.ts +20 -7
  202. package/src/operations/insert.ts +13 -6
  203. package/src/operations/kill_cursors.ts +10 -2
  204. package/src/operations/list_collections.ts +10 -1
  205. package/src/operations/list_databases.ts +9 -2
  206. package/src/operations/operation.ts +16 -2
  207. package/src/operations/profiling_level.ts +7 -2
  208. package/src/operations/remove_user.ts +7 -2
  209. package/src/operations/rename.ts +7 -2
  210. package/src/operations/run_command.ts +23 -4
  211. package/src/operations/search_indexes/create.ts +10 -2
  212. package/src/operations/search_indexes/drop.ts +7 -2
  213. package/src/operations/search_indexes/update.ts +7 -2
  214. package/src/operations/set_profiling_level.ts +4 -2
  215. package/src/operations/stats.ts +7 -2
  216. package/src/operations/update.ts +16 -8
  217. package/src/operations/validate_collection.ts +7 -2
  218. package/src/read_concern.ts +1 -1
  219. package/src/sdam/common.ts +0 -11
  220. package/src/sdam/server.ts +14 -4
  221. package/src/sdam/server_description.ts +6 -2
  222. package/src/sdam/server_selection.ts +5 -2
  223. package/src/sdam/topology.ts +43 -27
  224. package/src/sessions.ts +206 -120
  225. package/src/timeout.ts +327 -23
  226. package/src/transactions.ts +1 -1
  227. package/src/utils.ts +47 -30
  228. 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,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
420
427
  ...options
421
428
  };
422
429
 
430
+ options.timeoutContext?.addMaxTimeMSToCommand(cmd, options);
431
+
423
432
  const message = this.supportsOpMsg
424
433
  ? new OpMsgRequest(db, cmd, commandOptions)
425
434
  : new OpQueryRequest(db, cmd, commandOptions);
@@ -434,16 +443,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
434
443
  ): AsyncGenerator<MongoDBResponse> {
435
444
  this.throwIfAborted();
436
445
 
437
- if (typeof options.socketTimeoutMS === 'number') {
438
- this.socket.setTimeout(options.socketTimeoutMS);
439
- } else if (this.socketTimeoutMS !== 0) {
440
- this.socket.setTimeout(this.socketTimeoutMS);
441
- }
446
+ const timeout =
447
+ options.socketTimeoutMS ??
448
+ options?.timeoutContext?.getSocketTimeoutMS() ??
449
+ this.socketTimeoutMS;
450
+ this.socket.setTimeout(timeout);
442
451
 
443
452
  try {
444
453
  await this.writeCommand(message, {
445
454
  agreedCompressor: this.description.compressor ?? 'none',
446
- zlibCompressionLevel: this.description.zlibCompressionLevel
455
+ zlibCompressionLevel: this.description.zlibCompressionLevel,
456
+ timeoutContext: options.timeoutContext
447
457
  });
448
458
 
449
459
  if (options.noResponse || message.moreToCome) {
@@ -453,7 +463,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
453
463
 
454
464
  this.throwIfAborted();
455
465
 
456
- for await (const response of this.readMany()) {
466
+ if (
467
+ options.timeoutContext?.csotEnabled() &&
468
+ options.timeoutContext.minRoundTripTime != null &&
469
+ options.timeoutContext.remainingTimeMS < options.timeoutContext.minRoundTripTime
470
+ ) {
471
+ throw new MongoOperationTimeoutError(
472
+ 'Server roundtrip time is greater than the time remaining'
473
+ );
474
+ }
475
+
476
+ for await (const response of this.readMany({ timeoutContext: options.timeoutContext })) {
457
477
  this.socket.setTimeout(0);
458
478
  const bson = response.parse();
459
479
 
@@ -462,11 +482,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
462
482
  yield document;
463
483
  this.throwIfAborted();
464
484
 
465
- if (typeof options.socketTimeoutMS === 'number') {
466
- this.socket.setTimeout(options.socketTimeoutMS);
467
- } else if (this.socketTimeoutMS !== 0) {
468
- this.socket.setTimeout(this.socketTimeoutMS);
469
- }
485
+ this.socket.setTimeout(timeout);
470
486
  }
471
487
  } finally {
472
488
  this.socket.setTimeout(0);
@@ -480,7 +496,6 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
480
496
  responseType?: MongoDBResponseConstructor
481
497
  ) {
482
498
  const message = this.prepareCommand(ns.db, command, options);
483
-
484
499
  let started = 0;
485
500
  if (this.shouldEmitAndLogCommand) {
486
501
  started = now();
@@ -522,6 +537,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
522
537
  }
523
538
 
524
539
  if (document.ok === 0) {
540
+ if (options.timeoutContext?.csotEnabled() && document.isMaxTimeExpiredError) {
541
+ throw new MongoOperationTimeoutError('Server reported a timeout error', {
542
+ cause: new MongoServerError((object ??= document.toObject(bsonOptions)))
543
+ });
544
+ }
525
545
  throw new MongoServerError((object ??= document.toObject(bsonOptions)));
526
546
  }
527
547
 
@@ -595,6 +615,28 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
595
615
  ): Promise<Document> {
596
616
  this.throwIfAborted();
597
617
  for await (const document of this.sendCommand(ns, command, options, responseType)) {
618
+ if (options.timeoutContext?.csotEnabled()) {
619
+ if (MongoDBResponse.is(document)) {
620
+ if (document.isMaxTimeExpiredError) {
621
+ throw new MongoOperationTimeoutError('Server reported a timeout error', {
622
+ cause: new MongoServerError(document.toObject())
623
+ });
624
+ }
625
+ } else {
626
+ if (
627
+ (Array.isArray(document?.writeErrors) &&
628
+ document.writeErrors.some(
629
+ error => error?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired
630
+ )) ||
631
+ document?.writeConcernError?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired
632
+ ) {
633
+ throw new MongoOperationTimeoutError('Server reported a timeout error', {
634
+ cause: new MongoServerError(document)
635
+ });
636
+ }
637
+ }
638
+ }
639
+
598
640
  return document;
599
641
  }
600
642
  throw new MongoUnexpectedServerResponseError('Unable to get response from server');
@@ -630,7 +672,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
630
672
  */
631
673
  private async writeCommand(
632
674
  command: WriteProtocolMessageType,
633
- options: { agreedCompressor?: CompressorName; zlibCompressionLevel?: number }
675
+ options: {
676
+ agreedCompressor?: CompressorName;
677
+ zlibCompressionLevel?: number;
678
+ timeoutContext?: TimeoutContext;
679
+ }
634
680
  ): Promise<void> {
635
681
  const finalCommand =
636
682
  options.agreedCompressor === 'none' || !OpCompressedRequest.canCompress(command)
@@ -642,8 +688,36 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
642
688
 
643
689
  const buffer = Buffer.concat(await finalCommand.toBin());
644
690
 
691
+ if (options.timeoutContext?.csotEnabled()) {
692
+ if (
693
+ options.timeoutContext.minRoundTripTime != null &&
694
+ options.timeoutContext.remainingTimeMS < options.timeoutContext.minRoundTripTime
695
+ ) {
696
+ throw new MongoOperationTimeoutError(
697
+ 'Server roundtrip time is greater than the time remaining'
698
+ );
699
+ }
700
+ }
701
+
645
702
  if (this.socket.write(buffer)) return;
646
- return await once(this.socket, 'drain');
703
+
704
+ const drainEvent = once<void>(this.socket, 'drain');
705
+ 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();
718
+ }
719
+ }
720
+ return await drainEvent;
647
721
  }
648
722
 
649
723
  /**
@@ -655,10 +729,13 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
655
729
  *
656
730
  * Note that `for-await` loops call `return` automatically when the loop is exited.
657
731
  */
658
- private async *readMany(): AsyncGenerator<OpMsgResponse | OpReply> {
732
+ private async *readMany(options: {
733
+ timeoutContext?: TimeoutContext;
734
+ }): AsyncGenerator<OpMsgResponse | OpReply> {
659
735
  try {
660
- this.dataEvents = onData(this.messageStream);
736
+ this.dataEvents = onData(this.messageStream, options);
661
737
  this.messageStream.resume();
738
+
662
739
  for await (const message of this.dataEvents) {
663
740
  const response = await decompressResponse(message);
664
741
  yield response;
@@ -667,6 +744,17 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
667
744
  return;
668
745
  }
669
746
  }
747
+ } catch (readError) {
748
+ const err = readError;
749
+ if (TimeoutError.is(readError)) {
750
+ const error = new MongoOperationTimeoutError(
751
+ `Timed out during socket read (${readError.duration}ms)`
752
+ );
753
+ this.dataEvents = null;
754
+ this.onError(error);
755
+ throw error;
756
+ }
757
+ throw err;
670
758
  } finally {
671
759
  this.dataEvents = null;
672
760
  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
+ timeout?.clear();
402
406
  }
403
407
  }
404
408
 
@@ -681,6 +685,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
681
685
  },
682
686
  error => {
683
687
  this[kPending]--;
688
+ this[kServer].handleError(error);
684
689
  this.emitAndLog(
685
690
  ConnectionPool.CONNECTION_CLOSED,
686
691
  new ConnectionClosedEvent(
@@ -715,9 +720,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
715
720
  // connection permits because that potentially delays the availability of
716
721
  // the connection to a checkout request
717
722
  this.createConnection((err, connection) => {
718
- if (err) {
719
- this[kServer].handleError(err);
720
- }
721
723
  if (!err && connection) {
722
724
  this[kConnections].push(connection);
723
725
  process.nextTick(() => this.processWaitQueue());
@@ -764,7 +766,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
764
766
  ConnectionPool.CONNECTION_CHECK_OUT_FAILED,
765
767
  new ConnectionCheckOutFailedEvent(this, reason, waitQueueMember.checkoutTime, error)
766
768
  );
767
- waitQueueMember.timeout.clear();
768
769
  this[kWaitQueue].shift();
769
770
  waitQueueMember.reject(error);
770
771
  continue;
@@ -785,7 +786,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
785
786
  ConnectionPool.CONNECTION_CHECKED_OUT,
786
787
  new ConnectionCheckedOutEvent(this, connection, waitQueueMember.checkoutTime)
787
788
  );
788
- waitQueueMember.timeout.clear();
789
789
 
790
790
  this[kWaitQueue].shift();
791
791
  waitQueueMember.resolve(connection);
@@ -828,8 +828,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
828
828
  );
829
829
  waitQueueMember.resolve(connection);
830
830
  }
831
-
832
- waitQueueMember.timeout.clear();
833
831
  }
834
832
  process.nextTick(() => this.processWaitQueue());
835
833
  });
@@ -11,7 +11,7 @@ const NODE_DRIVER_VERSION = require('../../../package.json').version;
11
11
 
12
12
  /**
13
13
  * @public
14
- * @see https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.rst#hello-command
14
+ * @see https://github.com/mongodb/specifications/blob/master/source/mongodb-handshake/handshake.md#hello-command
15
15
  */
16
16
  export interface ClientMetadata {
17
17
  driver: {
@@ -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
  },