mongodb 6.10.0 → 6.11.0

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 +17 -16
  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
package/src/sessions.ts CHANGED
@@ -29,6 +29,7 @@ import { ReadConcernLevel } from './read_concern';
29
29
  import { ReadPreference } from './read_preference';
30
30
  import { type AsyncDisposable, configureResourceManagement } from './resource_management';
31
31
  import { _advanceClusterTime, type ClusterTime, TopologyType } from './sdam/common';
32
+ import { TimeoutContext } from './timeout';
32
33
  import {
33
34
  isTransactionCommand,
34
35
  Transaction,
@@ -58,8 +59,12 @@ export interface ClientSessionOptions {
58
59
  snapshot?: boolean;
59
60
  /** The default TransactionOptions to use for transactions started on this session. */
60
61
  defaultTransactionOptions?: TransactionOptions;
61
- /** @internal
62
- * The value of timeoutMS used for CSOT. Used to override client timeoutMS */
62
+ /**
63
+ * @public
64
+ * @experimental
65
+ * An overriding timeoutMS value to use for a client-side timeout.
66
+ * If not provided the session uses the timeoutMS specified on the MongoClient.
67
+ */
63
68
  defaultTimeoutMS?: number;
64
69
 
65
70
  /** @internal */
@@ -98,6 +103,9 @@ export interface EndSessionOptions {
98
103
  error?: AnyError;
99
104
  force?: boolean;
100
105
  forceClear?: boolean;
106
+
107
+ /** Specifies the time an operation will run until it throws a timeout error */
108
+ timeoutMS?: number;
101
109
  }
102
110
 
103
111
  /**
@@ -115,7 +123,7 @@ export class ClientSession
115
123
  /** @internal */
116
124
  sessionPool: ServerSessionPool;
117
125
  hasEnded: boolean;
118
- clientOptions?: MongoOptions;
126
+ clientOptions: MongoOptions;
119
127
  supports: { causalConsistency: boolean };
120
128
  clusterTime?: ClusterTime;
121
129
  operationTime?: Timestamp;
@@ -129,7 +137,7 @@ export class ClientSession
129
137
  * initially undefined. Gets set to false when startTransaction is called. When commitTransaction is sent to server, if the commitTransaction succeeds, it is then set to undefined, otherwise, set to true */
130
138
  commitAttempted?: boolean;
131
139
  /** @internal */
132
- [kServerSession]: ServerSession | null;
140
+ private [kServerSession]: ServerSession | null;
133
141
  /** @internal */
134
142
  [kSnapshotTime]?: Timestamp;
135
143
  /** @internal */
@@ -138,9 +146,15 @@ export class ClientSession
138
146
  [kPinnedConnection]?: Connection;
139
147
  /** @internal */
140
148
  [kTxnNumberIncrement]: number;
141
- /** @internal */
149
+ /**
150
+ * @experimental
151
+ * Specifies the time an operation in a given `ClientSession` will run until it throws a timeout error
152
+ */
142
153
  timeoutMS?: number;
143
154
 
155
+ /** @internal */
156
+ public timeoutContext: TimeoutContext | null = null;
157
+
144
158
  /**
145
159
  * Create a client session.
146
160
  * @internal
@@ -153,7 +167,7 @@ export class ClientSession
153
167
  client: MongoClient,
154
168
  sessionPool: ServerSessionPool,
155
169
  options: ClientSessionOptions,
156
- clientOptions?: MongoOptions
170
+ clientOptions: MongoOptions
157
171
  ) {
158
172
  super();
159
173
 
@@ -273,27 +287,25 @@ export class ClientSession
273
287
  async endSession(options?: EndSessionOptions): Promise<void> {
274
288
  try {
275
289
  if (this.inTransaction()) {
276
- await this.abortTransaction();
290
+ await this.abortTransaction({ ...options, throwTimeout: true });
277
291
  }
292
+ } catch (error) {
293
+ // spec indicates that we should ignore all errors for `endSessions`
294
+ if (error.name === 'MongoOperationTimeoutError') throw error;
295
+ squashError(error);
296
+ } finally {
278
297
  if (!this.hasEnded) {
279
298
  const serverSession = this[kServerSession];
280
299
  if (serverSession != null) {
281
300
  // release the server session back to the pool
282
301
  this.sessionPool.release(serverSession);
283
- // Make sure a new serverSession never makes it onto this ClientSession
284
- Object.defineProperty(this, kServerSession, {
285
- value: ServerSession.clone(serverSession),
286
- writable: false
287
- });
302
+ // Store a clone of the server session for reference (debugging)
303
+ this[kServerSession] = new ServerSession(serverSession);
288
304
  }
289
305
  // mark the session as ended, and emit a signal
290
306
  this.hasEnded = true;
291
307
  this.emit('ended', this);
292
308
  }
293
- } catch (error) {
294
- // spec indicates that we should ignore all errors for `endSessions`
295
- squashError(error);
296
- } finally {
297
309
  maybeClearPinnedConnection(this, { force: true, ...options });
298
310
  }
299
311
  }
@@ -446,8 +458,10 @@ export class ClientSession
446
458
 
447
459
  /**
448
460
  * Commits the currently active transaction in this session.
461
+ *
462
+ * @param options - Optional options, can be used to override `defaultTimeoutMS`.
449
463
  */
450
- async commitTransaction(): Promise<void> {
464
+ async commitTransaction(options?: { timeoutMS?: number }): Promise<void> {
451
465
  if (this.transaction.state === TxnState.NO_TRANSACTION) {
452
466
  throw new MongoTransactionError('No transaction started');
453
467
  }
@@ -474,13 +488,31 @@ export class ClientSession
474
488
  maxTimeMS?: number;
475
489
  } = { commitTransaction: 1 };
476
490
 
491
+ const timeoutMS =
492
+ typeof options?.timeoutMS === 'number'
493
+ ? options.timeoutMS
494
+ : typeof this.timeoutMS === 'number'
495
+ ? this.timeoutMS
496
+ : null;
497
+
477
498
  const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
478
499
  if (wc != null) {
479
- WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
500
+ if (timeoutMS == null && this.timeoutContext == null) {
501
+ WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
502
+ } else {
503
+ const wcKeys = Object.keys(wc);
504
+ if (wcKeys.length > 2 || (!wcKeys.includes('wtimeoutMS') && !wcKeys.includes('wTimeoutMS')))
505
+ // if the write concern was specified with wTimeoutMS, then we set both wtimeoutMS and wTimeoutMS, guaranteeing at least two keys, so if we have more than two keys, then we can automatically assume that we should add the write concern to the command. If it has 2 or fewer keys, we need to check that those keys aren't the wtimeoutMS or wTimeoutMS options before we add the write concern to the command
506
+ WriteConcern.apply(command, { ...wc, wtimeoutMS: undefined });
507
+ }
480
508
  }
481
509
 
482
510
  if (this.transaction.state === TxnState.TRANSACTION_COMMITTED || this.commitAttempted) {
483
- WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
511
+ if (timeoutMS == null && this.timeoutContext == null) {
512
+ WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
513
+ } else {
514
+ WriteConcern.apply(command, { w: 'majority', ...wc, wtimeoutMS: undefined });
515
+ }
484
516
  }
485
517
 
486
518
  if (typeof this.transaction.options.maxTimeMS === 'number') {
@@ -497,8 +529,18 @@ export class ClientSession
497
529
  bypassPinningCheck: true
498
530
  });
499
531
 
532
+ const timeoutContext =
533
+ this.timeoutContext ??
534
+ (typeof timeoutMS === 'number'
535
+ ? TimeoutContext.create({
536
+ serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
537
+ socketTimeoutMS: this.clientOptions.socketTimeoutMS,
538
+ timeoutMS
539
+ })
540
+ : null);
541
+
500
542
  try {
501
- await executeOperation(this.client, operation);
543
+ await executeOperation(this.client, operation, timeoutContext);
502
544
  this.commitAttempted = undefined;
503
545
  return;
504
546
  } catch (firstCommitError) {
@@ -516,7 +558,8 @@ export class ClientSession
516
558
  session: this,
517
559
  readPreference: ReadPreference.primary,
518
560
  bypassPinningCheck: true
519
- })
561
+ }),
562
+ timeoutContext
520
563
  );
521
564
  return;
522
565
  } catch (retryCommitError) {
@@ -549,8 +592,13 @@ export class ClientSession
549
592
 
550
593
  /**
551
594
  * Aborts the currently active transaction in this session.
595
+ *
596
+ * @param options - Optional options, can be used to override `defaultTimeoutMS`.
552
597
  */
553
- async abortTransaction(): Promise<void> {
598
+ async abortTransaction(options?: { timeoutMS?: number }): Promise<void>;
599
+ /** @internal */
600
+ async abortTransaction(options?: { timeoutMS?: number; throwTimeout?: true }): Promise<void>;
601
+ async abortTransaction(options?: { timeoutMS?: number; throwTimeout?: true }): Promise<void> {
554
602
  if (this.transaction.state === TxnState.NO_TRANSACTION) {
555
603
  throw new MongoTransactionError('No transaction started');
556
604
  }
@@ -580,8 +628,26 @@ export class ClientSession
580
628
  recoveryToken?: Document;
581
629
  } = { abortTransaction: 1 };
582
630
 
631
+ const timeoutMS =
632
+ typeof options?.timeoutMS === 'number'
633
+ ? options.timeoutMS
634
+ : this.timeoutContext?.csotEnabled()
635
+ ? this.timeoutContext.timeoutMS // refresh timeoutMS for abort operation
636
+ : typeof this.timeoutMS === 'number'
637
+ ? this.timeoutMS
638
+ : null;
639
+
640
+ const timeoutContext =
641
+ timeoutMS != null
642
+ ? TimeoutContext.create({
643
+ timeoutMS,
644
+ serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
645
+ socketTimeoutMS: this.clientOptions.socketTimeoutMS
646
+ })
647
+ : null;
648
+
583
649
  const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
584
- if (wc != null) {
650
+ if (wc != null && timeoutMS == null) {
585
651
  WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
586
652
  }
587
653
 
@@ -596,17 +662,26 @@ export class ClientSession
596
662
  });
597
663
 
598
664
  try {
599
- await executeOperation(this.client, operation);
665
+ await executeOperation(this.client, operation, timeoutContext);
600
666
  this.unpin();
601
667
  return;
602
668
  } catch (firstAbortError) {
603
669
  this.unpin();
604
670
 
671
+ if (firstAbortError.name === 'MongoRuntimeError') throw firstAbortError;
672
+ if (options?.throwTimeout && firstAbortError.name === 'MongoOperationTimeoutError') {
673
+ throw firstAbortError;
674
+ }
675
+
605
676
  if (firstAbortError instanceof MongoError && isRetryableWriteError(firstAbortError)) {
606
677
  try {
607
- await executeOperation(this.client, operation);
678
+ await executeOperation(this.client, operation, timeoutContext);
608
679
  return;
609
- } catch {
680
+ } catch (secondAbortError) {
681
+ if (secondAbortError.name === 'MongoRuntimeError') throw secondAbortError;
682
+ if (options?.throwTimeout && secondAbortError.name === 'MongoOperationTimeoutError') {
683
+ throw secondAbortError;
684
+ }
610
685
  // we do not retry the retry
611
686
  }
612
687
  }
@@ -636,6 +711,9 @@ export class ClientSession
636
711
  * `Promise.allSettled`, `Promise.race`, etc to parallelize operations inside a transaction is
637
712
  * undefined behaviour.
638
713
  *
714
+ * **IMPORTANT:** When running an operation inside a `withTransaction` callback, if it is not
715
+ * provided the explicit session in its options, it will not be part of the transaction and it will not respect timeoutMS.
716
+ *
639
717
  *
640
718
  * @remarks
641
719
  * - If all operations successfully complete and the `commitTransaction` operation is successful, then the provided function will return the result of the provided function.
@@ -661,96 +739,119 @@ export class ClientSession
661
739
  */
662
740
  async withTransaction<T = any>(
663
741
  fn: WithTransactionCallback<T>,
664
- options?: TransactionOptions
742
+ options?: TransactionOptions & {
743
+ /**
744
+ * Configures a timeoutMS expiry for the entire withTransactionCallback.
745
+ *
746
+ * @remarks
747
+ * - The remaining timeout will not be applied to callback operations that do not use the ClientSession.
748
+ * - Overriding timeoutMS for operations executed using the explicit session inside the provided callback will result in a client-side error.
749
+ */
750
+ timeoutMS?: number;
751
+ }
665
752
  ): Promise<T> {
666
753
  const MAX_TIMEOUT = 120000;
667
- const startTime = now();
668
754
 
669
- let committed = false;
670
- let result: any;
755
+ const timeoutMS = options?.timeoutMS ?? this.timeoutMS ?? null;
756
+ this.timeoutContext =
757
+ timeoutMS != null
758
+ ? TimeoutContext.create({
759
+ timeoutMS,
760
+ serverSelectionTimeoutMS: this.clientOptions.serverSelectionTimeoutMS,
761
+ socketTimeoutMS: this.clientOptions.socketTimeoutMS
762
+ })
763
+ : null;
671
764
 
672
- while (!committed) {
673
- this.startTransaction(options); // may throw on error
765
+ const startTime = this.timeoutContext?.csotEnabled() ? this.timeoutContext.start : now();
674
766
 
675
- try {
676
- const promise = fn(this);
677
- if (!isPromiseLike(promise)) {
678
- throw new MongoInvalidArgumentError(
679
- 'Function provided to `withTransaction` must return a Promise'
680
- );
681
- }
682
-
683
- result = await promise;
767
+ let committed = false;
768
+ let result: any;
684
769
 
685
- if (
686
- this.transaction.state === TxnState.NO_TRANSACTION ||
687
- this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
688
- this.transaction.state === TxnState.TRANSACTION_ABORTED
689
- ) {
690
- // Assume callback intentionally ended the transaction
691
- return result;
692
- }
693
- } catch (fnError) {
694
- if (!(fnError instanceof MongoError) || fnError instanceof MongoInvalidArgumentError) {
695
- await this.abortTransaction();
696
- throw fnError;
697
- }
770
+ try {
771
+ while (!committed) {
772
+ this.startTransaction(options); // may throw on error
698
773
 
699
- if (
700
- this.transaction.state === TxnState.STARTING_TRANSACTION ||
701
- this.transaction.state === TxnState.TRANSACTION_IN_PROGRESS
702
- ) {
703
- await this.abortTransaction();
704
- }
774
+ try {
775
+ const promise = fn(this);
776
+ if (!isPromiseLike(promise)) {
777
+ throw new MongoInvalidArgumentError(
778
+ 'Function provided to `withTransaction` must return a Promise'
779
+ );
780
+ }
705
781
 
706
- if (
707
- fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
708
- now() - startTime < MAX_TIMEOUT
709
- ) {
710
- continue;
711
- }
782
+ result = await promise;
712
783
 
713
- throw fnError;
714
- }
784
+ if (
785
+ this.transaction.state === TxnState.NO_TRANSACTION ||
786
+ this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
787
+ this.transaction.state === TxnState.TRANSACTION_ABORTED
788
+ ) {
789
+ // Assume callback intentionally ended the transaction
790
+ return result;
791
+ }
792
+ } catch (fnError) {
793
+ if (!(fnError instanceof MongoError) || fnError instanceof MongoInvalidArgumentError) {
794
+ await this.abortTransaction();
795
+ throw fnError;
796
+ }
715
797
 
716
- while (!committed) {
717
- try {
718
- /*
719
- * We will rely on ClientSession.commitTransaction() to
720
- * apply a majority write concern if commitTransaction is
721
- * being retried (see: DRIVERS-601)
722
- */
723
- await this.commitTransaction();
724
- committed = true;
725
- } catch (commitError) {
726
- /*
727
- * Note: a maxTimeMS error will have the MaxTimeMSExpired
728
- * code (50) and can be reported as a top-level error or
729
- * inside writeConcernError, ex.
730
- * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' }
731
- * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } }
732
- */
733
798
  if (
734
- !isMaxTimeMSExpiredError(commitError) &&
735
- commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) &&
736
- now() - startTime < MAX_TIMEOUT
799
+ this.transaction.state === TxnState.STARTING_TRANSACTION ||
800
+ this.transaction.state === TxnState.TRANSACTION_IN_PROGRESS
737
801
  ) {
738
- continue;
802
+ await this.abortTransaction();
739
803
  }
740
804
 
741
805
  if (
742
- commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
743
- now() - startTime < MAX_TIMEOUT
806
+ fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
807
+ (this.timeoutContext != null || now() - startTime < MAX_TIMEOUT)
744
808
  ) {
745
- break;
809
+ continue;
746
810
  }
747
811
 
748
- throw commitError;
812
+ throw fnError;
813
+ }
814
+
815
+ while (!committed) {
816
+ try {
817
+ /*
818
+ * We will rely on ClientSession.commitTransaction() to
819
+ * apply a majority write concern if commitTransaction is
820
+ * being retried (see: DRIVERS-601)
821
+ */
822
+ await this.commitTransaction();
823
+ committed = true;
824
+ } catch (commitError) {
825
+ /*
826
+ * Note: a maxTimeMS error will have the MaxTimeMSExpired
827
+ * code (50) and can be reported as a top-level error or
828
+ * inside writeConcernError, ex.
829
+ * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' }
830
+ * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } }
831
+ */
832
+ if (
833
+ !isMaxTimeMSExpiredError(commitError) &&
834
+ commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) &&
835
+ (this.timeoutContext != null || now() - startTime < MAX_TIMEOUT)
836
+ ) {
837
+ continue;
838
+ }
839
+
840
+ if (
841
+ commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
842
+ (this.timeoutContext != null || now() - startTime < MAX_TIMEOUT)
843
+ ) {
844
+ break;
845
+ }
846
+
847
+ throw commitError;
848
+ }
749
849
  }
750
850
  }
851
+ return result;
852
+ } finally {
853
+ this.timeoutContext = null;
751
854
  }
752
-
753
- return result;
754
855
  }
755
856
  }
756
857
 
@@ -869,7 +970,16 @@ export class ServerSession {
869
970
  isDirty: boolean;
870
971
 
871
972
  /** @internal */
872
- constructor() {
973
+ constructor(cloned?: ServerSession | null) {
974
+ if (cloned != null) {
975
+ const idBytes = Buffer.allocUnsafe(16);
976
+ idBytes.set(cloned.id.id.buffer);
977
+ this.id = { id: new Binary(idBytes, cloned.id.id.sub_type) };
978
+ this.lastUse = cloned.lastUse;
979
+ this.txnNumber = cloned.txnNumber;
980
+ this.isDirty = cloned.isDirty;
981
+ return;
982
+ }
873
983
  this.id = { id: new Binary(uuidV4(), Binary.SUBTYPE_UUID) };
874
984
  this.lastUse = now();
875
985
  this.txnNumber = 0;
@@ -890,30 +1000,6 @@ export class ServerSession {
890
1000
 
891
1001
  return idleTimeMinutes > sessionTimeoutMinutes - 1;
892
1002
  }
893
-
894
- /**
895
- * @internal
896
- * Cloning meant to keep a readable reference to the server session data
897
- * after ClientSession has ended
898
- */
899
- static clone(serverSession: ServerSession): Readonly<ServerSession> {
900
- const arrayBuffer = new ArrayBuffer(16);
901
- const idBytes = Buffer.from(arrayBuffer);
902
- idBytes.set(serverSession.id.id.buffer);
903
-
904
- const id = new Binary(idBytes, serverSession.id.id.sub_type);
905
-
906
- // Manual prototype construction to avoid modifying the constructor of this class
907
- return Object.setPrototypeOf(
908
- {
909
- id: { id },
910
- lastUse: serverSession.lastUse,
911
- txnNumber: serverSession.txnNumber,
912
- isDirty: serverSession.isDirty
913
- },
914
- ServerSession.prototype
915
- );
916
- }
917
1003
  }
918
1004
 
919
1005
  /**