mongodb 6.8.2 → 6.9.0-dev.20240913.sha.8b0f3541

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 (242) hide show
  1. package/README.md +14 -1
  2. package/lib/beta.d.ts +7905 -0
  3. package/lib/beta.js +21 -0
  4. package/lib/beta.js.map +1 -0
  5. package/lib/bson.js +5 -5
  6. package/lib/bson.js.map +1 -1
  7. package/lib/bulk/common.js +16 -21
  8. package/lib/bulk/common.js.map +1 -1
  9. package/lib/bulk/ordered.js.map +1 -1
  10. package/lib/bulk/unordered.js.map +1 -1
  11. package/lib/change_stream.js +10 -8
  12. package/lib/change_stream.js.map +1 -1
  13. package/lib/client-side-encryption/auto_encrypter.js +14 -3
  14. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  15. package/lib/client-side-encryption/client_encryption.js +25 -7
  16. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  17. package/lib/client-side-encryption/crypto_callbacks.js +6 -6
  18. package/lib/client-side-encryption/crypto_callbacks.js.map +1 -1
  19. package/lib/client-side-encryption/mongocryptd_manager.js +9 -5
  20. package/lib/client-side-encryption/mongocryptd_manager.js.map +1 -1
  21. package/lib/client-side-encryption/providers/aws.js +1 -2
  22. package/lib/client-side-encryption/providers/aws.js.map +1 -1
  23. package/lib/client-side-encryption/providers/azure.js +5 -5
  24. package/lib/client-side-encryption/providers/azure.js.map +1 -1
  25. package/lib/client-side-encryption/providers/gcp.js +1 -2
  26. package/lib/client-side-encryption/providers/gcp.js.map +1 -1
  27. package/lib/client-side-encryption/providers/index.js +2 -3
  28. package/lib/client-side-encryption/providers/index.js.map +1 -1
  29. package/lib/client-side-encryption/state_machine.js +9 -4
  30. package/lib/client-side-encryption/state_machine.js.map +1 -1
  31. package/lib/cmap/auth/auth_provider.js.map +1 -1
  32. package/lib/cmap/auth/aws_temporary_credentials.js.map +1 -1
  33. package/lib/cmap/auth/gssapi.js +4 -4
  34. package/lib/cmap/auth/gssapi.js.map +1 -1
  35. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  36. package/lib/cmap/auth/mongocr.js.map +1 -1
  37. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  38. package/lib/cmap/auth/mongodb_oidc/automated_callback_workflow.js.map +1 -1
  39. package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js.map +1 -1
  40. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +0 -2
  41. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -1
  42. package/lib/cmap/auth/mongodb_oidc/command_builders.js +2 -3
  43. package/lib/cmap/auth/mongodb_oidc/command_builders.js.map +1 -1
  44. package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js.map +1 -1
  45. package/lib/cmap/auth/mongodb_oidc/human_callback_workflow.js.map +1 -1
  46. package/lib/cmap/auth/mongodb_oidc/machine_workflow.js +0 -2
  47. package/lib/cmap/auth/mongodb_oidc/machine_workflow.js.map +1 -1
  48. package/lib/cmap/auth/mongodb_oidc/token_cache.js.map +1 -1
  49. package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -1
  50. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  51. package/lib/cmap/auth/plain.js.map +1 -1
  52. package/lib/cmap/auth/scram.js.map +1 -1
  53. package/lib/cmap/auth/x509.js.map +1 -1
  54. package/lib/cmap/command_monitoring_events.js.map +1 -1
  55. package/lib/cmap/commands.js +62 -5
  56. package/lib/cmap/commands.js.map +1 -1
  57. package/lib/cmap/connect.js +10 -7
  58. package/lib/cmap/connect.js.map +1 -1
  59. package/lib/cmap/connection.js +3 -5
  60. package/lib/cmap/connection.js.map +1 -1
  61. package/lib/cmap/connection_pool.js +11 -9
  62. package/lib/cmap/connection_pool.js.map +1 -1
  63. package/lib/cmap/connection_pool_events.js +7 -3
  64. package/lib/cmap/connection_pool_events.js.map +1 -1
  65. package/lib/cmap/handshake/client_metadata.js +5 -5
  66. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  67. package/lib/cmap/metrics.js +1 -1
  68. package/lib/cmap/metrics.js.map +1 -1
  69. package/lib/cmap/stream_description.js.map +1 -1
  70. package/lib/cmap/wire_protocol/compression.js +5 -5
  71. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  72. package/lib/cmap/wire_protocol/constants.js +2 -2
  73. package/lib/cmap/wire_protocol/on_data.js +1 -2
  74. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  75. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  76. package/lib/cmap/wire_protocol/responses.js +4 -4
  77. package/lib/cmap/wire_protocol/responses.js.map +1 -1
  78. package/lib/cmap/wire_protocol/shared.js +2 -3
  79. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  80. package/lib/collection.js.map +1 -1
  81. package/lib/connection_string.js +12 -6
  82. package/lib/connection_string.js.map +1 -1
  83. package/lib/constants.js +1 -0
  84. package/lib/constants.js.map +1 -1
  85. package/lib/cursor/abstract_cursor.js +21 -6
  86. package/lib/cursor/abstract_cursor.js.map +1 -1
  87. package/lib/cursor/aggregation_cursor.js +1 -1
  88. package/lib/cursor/aggregation_cursor.js.map +1 -1
  89. package/lib/cursor/change_stream_cursor.js.map +1 -1
  90. package/lib/cursor/find_cursor.js +3 -3
  91. package/lib/cursor/find_cursor.js.map +1 -1
  92. package/lib/db.js +1 -1
  93. package/lib/db.js.map +1 -1
  94. package/lib/deps.js +16 -8
  95. package/lib/deps.js.map +1 -1
  96. package/lib/encrypter.js.map +1 -1
  97. package/lib/error.js +19 -14
  98. package/lib/error.js.map +1 -1
  99. package/lib/explain.js.map +1 -1
  100. package/lib/gridfs/download.js +1 -4
  101. package/lib/gridfs/download.js.map +1 -1
  102. package/lib/gridfs/index.js +1 -1
  103. package/lib/gridfs/index.js.map +1 -1
  104. package/lib/gridfs/upload.js +0 -4
  105. package/lib/gridfs/upload.js.map +1 -1
  106. package/lib/index.js +4 -2
  107. package/lib/index.js.map +1 -1
  108. package/lib/mongo_client.js +15 -1
  109. package/lib/mongo_client.js.map +1 -1
  110. package/lib/mongo_client_auth_providers.js.map +1 -1
  111. package/lib/mongo_logger.js +8 -8
  112. package/lib/mongo_logger.js.map +1 -1
  113. package/lib/mongo_types.js +1 -0
  114. package/lib/mongo_types.js.map +1 -1
  115. package/lib/operations/aggregate.js +1 -0
  116. package/lib/operations/aggregate.js.map +1 -1
  117. package/lib/operations/bulk_write.js.map +1 -1
  118. package/lib/operations/client_bulk_write/command_builder.js +198 -0
  119. package/lib/operations/client_bulk_write/command_builder.js.map +1 -0
  120. package/lib/operations/client_bulk_write/common.js +3 -0
  121. package/lib/operations/client_bulk_write/common.js.map +1 -0
  122. package/lib/operations/collections.js.map +1 -1
  123. package/lib/operations/command.js +1 -1
  124. package/lib/operations/command.js.map +1 -1
  125. package/lib/operations/count.js.map +1 -1
  126. package/lib/operations/create_collection.js.map +1 -1
  127. package/lib/operations/delete.js +2 -2
  128. package/lib/operations/delete.js.map +1 -1
  129. package/lib/operations/distinct.js.map +1 -1
  130. package/lib/operations/drop.js.map +1 -1
  131. package/lib/operations/estimated_document_count.js.map +1 -1
  132. package/lib/operations/execute_operation.js +111 -109
  133. package/lib/operations/execute_operation.js.map +1 -1
  134. package/lib/operations/find.js.map +1 -1
  135. package/lib/operations/find_and_modify.js +2 -8
  136. package/lib/operations/find_and_modify.js.map +1 -1
  137. package/lib/operations/get_more.js.map +1 -1
  138. package/lib/operations/indexes.js.map +1 -1
  139. package/lib/operations/insert.js.map +1 -1
  140. package/lib/operations/is_capped.js.map +1 -1
  141. package/lib/operations/kill_cursors.js.map +1 -1
  142. package/lib/operations/list_collections.js.map +1 -1
  143. package/lib/operations/list_databases.js.map +1 -1
  144. package/lib/operations/operation.js +5 -5
  145. package/lib/operations/operation.js.map +1 -1
  146. package/lib/operations/options_operation.js.map +1 -1
  147. package/lib/operations/profiling_level.js.map +1 -1
  148. package/lib/operations/search_indexes/drop.js.map +1 -1
  149. package/lib/operations/set_profiling_level.js.map +1 -1
  150. package/lib/operations/stats.js.map +1 -1
  151. package/lib/operations/update.js +2 -2
  152. package/lib/operations/update.js.map +1 -1
  153. package/lib/operations/validate_collection.js.map +1 -1
  154. package/lib/read_concern.js.map +1 -1
  155. package/lib/read_preference.js +1 -1
  156. package/lib/read_preference.js.map +1 -1
  157. package/lib/resource_management.js +58 -0
  158. package/lib/resource_management.js.map +1 -0
  159. package/lib/sdam/common.js +3 -3
  160. package/lib/sdam/common.js.map +1 -1
  161. package/lib/sdam/monitor.js +1 -5
  162. package/lib/sdam/monitor.js.map +1 -1
  163. package/lib/sdam/server.js +2 -2
  164. package/lib/sdam/server.js.map +1 -1
  165. package/lib/sdam/server_description.js +3 -3
  166. package/lib/sdam/server_description.js.map +1 -1
  167. package/lib/sdam/server_selection.js +5 -5
  168. package/lib/sdam/server_selection.js.map +1 -1
  169. package/lib/sdam/srv_polling.js +2 -3
  170. package/lib/sdam/srv_polling.js.map +1 -1
  171. package/lib/sdam/topology.js +1 -1
  172. package/lib/sdam/topology.js.map +1 -1
  173. package/lib/sdam/topology_description.js.map +1 -1
  174. package/lib/sessions.js +221 -218
  175. package/lib/sessions.js.map +1 -1
  176. package/lib/sort.js +2 -3
  177. package/lib/sort.js.map +1 -1
  178. package/lib/timeout.js +0 -1
  179. package/lib/timeout.js.map +1 -1
  180. package/lib/transactions.js +2 -2
  181. package/lib/transactions.js.map +1 -1
  182. package/lib/utils.js +49 -51
  183. package/lib/utils.js.map +1 -1
  184. package/lib/write_concern.js +2 -2
  185. package/lib/write_concern.js.map +1 -1
  186. package/mongodb.d.ts +150 -142
  187. package/package.json +27 -28
  188. package/src/beta.ts +22 -0
  189. package/src/bson.ts +1 -2
  190. package/src/bulk/common.ts +18 -18
  191. package/src/change_stream.ts +33 -15
  192. package/src/client-side-encryption/auto_encrypter.ts +18 -82
  193. package/src/client-side-encryption/client_encryption.ts +51 -54
  194. package/src/client-side-encryption/mongocryptd_manager.ts +10 -6
  195. package/src/client-side-encryption/state_machine.ts +28 -6
  196. package/src/cmap/auth/gssapi.ts +1 -1
  197. package/src/cmap/auth/mongodb_aws.ts +2 -2
  198. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +2 -2
  199. package/src/cmap/auth/mongodb_oidc/machine_workflow.ts +2 -2
  200. package/src/cmap/commands.ts +70 -5
  201. package/src/cmap/connect.ts +4 -1
  202. package/src/cmap/connection.ts +2 -2
  203. package/src/cmap/connection_pool.ts +17 -9
  204. package/src/cmap/connection_pool_events.ts +34 -2
  205. package/src/cmap/handshake/client_metadata.ts +1 -1
  206. package/src/cmap/wire_protocol/constants.ts +2 -2
  207. package/src/cmap/wire_protocol/shared.ts +1 -2
  208. package/src/collection.ts +16 -15
  209. package/src/connection_string.ts +10 -3
  210. package/src/constants.ts +1 -0
  211. package/src/cursor/abstract_cursor.ts +38 -13
  212. package/src/cursor/aggregation_cursor.ts +6 -4
  213. package/src/deps.ts +8 -1
  214. package/src/error.ts +33 -14
  215. package/src/gridfs/download.ts +28 -4
  216. package/src/gridfs/upload.ts +1 -6
  217. package/src/index.ts +5 -1
  218. package/src/mongo_client.ts +29 -5
  219. package/src/mongo_logger.ts +5 -3
  220. package/src/mongo_types.ts +69 -68
  221. package/src/operations/aggregate.ts +2 -1
  222. package/src/operations/bulk_write.ts +2 -2
  223. package/src/operations/client_bulk_write/command_builder.ts +283 -0
  224. package/src/operations/client_bulk_write/common.ts +146 -0
  225. package/src/operations/command.ts +1 -1
  226. package/src/operations/execute_operation.ts +137 -131
  227. package/src/operations/find_and_modify.ts +2 -7
  228. package/src/operations/insert.ts +3 -4
  229. package/src/operations/operation.ts +7 -10
  230. package/src/operations/search_indexes/drop.ts +4 -1
  231. package/src/resource_management.ts +74 -0
  232. package/src/sdam/monitor.ts +3 -5
  233. package/src/sdam/server.ts +1 -1
  234. package/src/sdam/server_description.ts +5 -6
  235. package/src/sdam/srv_polling.ts +1 -2
  236. package/src/sessions.ts +291 -277
  237. package/src/sort.ts +1 -1
  238. package/src/timeout.ts +0 -1
  239. package/src/transactions.ts +1 -2
  240. package/src/utils.ts +9 -4
  241. package/src/write_concern.ts +2 -2
  242. package/tsconfig.json +2 -1
package/src/sessions.ts CHANGED
@@ -27,6 +27,7 @@ import { executeOperation } from './operations/execute_operation';
27
27
  import { RunAdminCommandOperation } from './operations/run_command';
28
28
  import { ReadConcernLevel } from './read_concern';
29
29
  import { ReadPreference } from './read_preference';
30
+ import { type AsyncDisposable, configureResourceManagement } from './resource_management';
30
31
  import { _advanceClusterTime, type ClusterTime, TopologyType } from './sdam/common';
31
32
  import {
32
33
  isTransactionCommand,
@@ -45,7 +46,7 @@ import {
45
46
  squashError,
46
47
  uuidV4
47
48
  } from './utils';
48
- import { WriteConcern } from './write_concern';
49
+ import { WriteConcern, type WriteConcernOptions, type WriteConcernSettings } from './write_concern';
49
50
 
50
51
  const minWireVersionForShardedTransactions = 8;
51
52
 
@@ -105,7 +106,10 @@ export interface EndSessionOptions {
105
106
  * NOTE: not meant to be instantiated directly.
106
107
  * @public
107
108
  */
108
- export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
109
+ export class ClientSession
110
+ extends TypedEventEmitter<ClientSessionEvents>
111
+ implements AsyncDisposable
112
+ {
109
113
  /** @internal */
110
114
  client: MongoClient;
111
115
  /** @internal */
@@ -255,7 +259,10 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
255
259
  }
256
260
 
257
261
  /**
258
- * Ends this session on the server
262
+ * Frees any client-side resources held by the current session. If a session is in a transaction,
263
+ * the transaction is aborted.
264
+ *
265
+ * Does not end the session on the server.
259
266
  *
260
267
  * @param options - Optional settings. Currently reserved for future use
261
268
  */
@@ -286,6 +293,16 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
286
293
  maybeClearPinnedConnection(this, { force: true, ...options });
287
294
  }
288
295
  }
296
+ /**
297
+ * @beta
298
+ * @experimental
299
+ * An alias for {@link ClientSession.endSession|ClientSession.endSession()}.
300
+ */
301
+ declare [Symbol.asyncDispose]: () => Promise<void>;
302
+ /** @internal */
303
+ async asyncDispose() {
304
+ await this.endSession({ force: true });
305
+ }
289
306
 
290
307
  /**
291
308
  * Advances the operationTime for a ClientSession.
@@ -426,14 +443,167 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
426
443
  * Commits the currently active transaction in this session.
427
444
  */
428
445
  async commitTransaction(): Promise<void> {
429
- return await endTransaction(this, 'commitTransaction');
446
+ if (this.transaction.state === TxnState.NO_TRANSACTION) {
447
+ throw new MongoTransactionError('No transaction started');
448
+ }
449
+
450
+ if (
451
+ this.transaction.state === TxnState.STARTING_TRANSACTION ||
452
+ this.transaction.state === TxnState.TRANSACTION_COMMITTED_EMPTY
453
+ ) {
454
+ // the transaction was never started, we can safely exit here
455
+ this.transaction.transition(TxnState.TRANSACTION_COMMITTED_EMPTY);
456
+ return;
457
+ }
458
+
459
+ if (this.transaction.state === TxnState.TRANSACTION_ABORTED) {
460
+ throw new MongoTransactionError(
461
+ 'Cannot call commitTransaction after calling abortTransaction'
462
+ );
463
+ }
464
+
465
+ const command: {
466
+ commitTransaction: 1;
467
+ writeConcern?: WriteConcernSettings;
468
+ recoveryToken?: Document;
469
+ maxTimeMS?: number;
470
+ } = { commitTransaction: 1 };
471
+
472
+ const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
473
+ if (wc != null) {
474
+ WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
475
+ }
476
+
477
+ if (this.transaction.state === TxnState.TRANSACTION_COMMITTED) {
478
+ WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
479
+ }
480
+
481
+ if (typeof this.transaction.options.maxTimeMS === 'number') {
482
+ command.maxTimeMS = this.transaction.options.maxTimeMS;
483
+ }
484
+
485
+ if (this.transaction.recoveryToken) {
486
+ command.recoveryToken = this.transaction.recoveryToken;
487
+ }
488
+
489
+ const operation = new RunAdminCommandOperation(command, {
490
+ session: this,
491
+ readPreference: ReadPreference.primary,
492
+ bypassPinningCheck: true
493
+ });
494
+
495
+ try {
496
+ await executeOperation(this.client, operation);
497
+ return;
498
+ } catch (firstCommitError) {
499
+ if (firstCommitError instanceof MongoError && isRetryableWriteError(firstCommitError)) {
500
+ // SPEC-1185: apply majority write concern when retrying commitTransaction
501
+ WriteConcern.apply(command, { wtimeoutMS: 10000, ...wc, w: 'majority' });
502
+ // per txns spec, must unpin session in this case
503
+ this.unpin({ force: true });
504
+
505
+ try {
506
+ await executeOperation(this.client, operation);
507
+ return;
508
+ } catch (retryCommitError) {
509
+ // If the retry failed, we process that error instead of the original
510
+ if (shouldAddUnknownTransactionCommitResultLabel(retryCommitError)) {
511
+ retryCommitError.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
512
+ }
513
+
514
+ if (shouldUnpinAfterCommitError(retryCommitError)) {
515
+ this.unpin({ error: retryCommitError });
516
+ }
517
+
518
+ throw retryCommitError;
519
+ }
520
+ }
521
+
522
+ if (shouldAddUnknownTransactionCommitResultLabel(firstCommitError)) {
523
+ firstCommitError.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
524
+ }
525
+
526
+ if (shouldUnpinAfterCommitError(firstCommitError)) {
527
+ this.unpin({ error: firstCommitError });
528
+ }
529
+
530
+ throw firstCommitError;
531
+ } finally {
532
+ this.transaction.transition(TxnState.TRANSACTION_COMMITTED);
533
+ }
430
534
  }
431
535
 
432
536
  /**
433
537
  * Aborts the currently active transaction in this session.
434
538
  */
435
539
  async abortTransaction(): Promise<void> {
436
- return await endTransaction(this, 'abortTransaction');
540
+ if (this.transaction.state === TxnState.NO_TRANSACTION) {
541
+ throw new MongoTransactionError('No transaction started');
542
+ }
543
+
544
+ if (this.transaction.state === TxnState.STARTING_TRANSACTION) {
545
+ // the transaction was never started, we can safely exit here
546
+ this.transaction.transition(TxnState.TRANSACTION_ABORTED);
547
+ return;
548
+ }
549
+
550
+ if (this.transaction.state === TxnState.TRANSACTION_ABORTED) {
551
+ throw new MongoTransactionError('Cannot call abortTransaction twice');
552
+ }
553
+
554
+ if (
555
+ this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
556
+ this.transaction.state === TxnState.TRANSACTION_COMMITTED_EMPTY
557
+ ) {
558
+ throw new MongoTransactionError(
559
+ 'Cannot call abortTransaction after calling commitTransaction'
560
+ );
561
+ }
562
+
563
+ const command: {
564
+ abortTransaction: 1;
565
+ writeConcern?: WriteConcernOptions;
566
+ recoveryToken?: Document;
567
+ } = { abortTransaction: 1 };
568
+
569
+ const wc = this.transaction.options.writeConcern ?? this.clientOptions?.writeConcern;
570
+ if (wc != null) {
571
+ WriteConcern.apply(command, { wtimeoutMS: 10000, w: 'majority', ...wc });
572
+ }
573
+
574
+ if (this.transaction.recoveryToken) {
575
+ command.recoveryToken = this.transaction.recoveryToken;
576
+ }
577
+
578
+ const operation = new RunAdminCommandOperation(command, {
579
+ session: this,
580
+ readPreference: ReadPreference.primary,
581
+ bypassPinningCheck: true
582
+ });
583
+
584
+ try {
585
+ await executeOperation(this.client, operation);
586
+ this.unpin();
587
+ return;
588
+ } catch (firstAbortError) {
589
+ this.unpin();
590
+
591
+ if (firstAbortError instanceof MongoError && isRetryableWriteError(firstAbortError)) {
592
+ try {
593
+ await executeOperation(this.client, operation);
594
+ return;
595
+ } catch {
596
+ // we do not retry the retry
597
+ }
598
+ }
599
+
600
+ // The spec indicates that if the operation times out or fails with a non-retryable error, we should ignore all errors on `abortTransaction`
601
+ } finally {
602
+ this.transaction.transition(TxnState.TRANSACTION_ABORTED);
603
+ if (this.loadBalanced) {
604
+ maybeClearPinnedConnection(this, { force: false });
605
+ }
606
+ }
437
607
  }
438
608
 
439
609
  /**
@@ -479,23 +649,132 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
479
649
  fn: WithTransactionCallback<T>,
480
650
  options?: TransactionOptions
481
651
  ): Promise<T> {
652
+ const MAX_TIMEOUT = 120000;
482
653
  const startTime = now();
483
- return await attemptTransaction(this, startTime, fn, options);
654
+
655
+ let committed = false;
656
+ let result: any;
657
+
658
+ while (!committed) {
659
+ this.startTransaction(options); // may throw on error
660
+
661
+ try {
662
+ const promise = fn(this);
663
+ if (!isPromiseLike(promise)) {
664
+ throw new MongoInvalidArgumentError(
665
+ 'Function provided to `withTransaction` must return a Promise'
666
+ );
667
+ }
668
+
669
+ result = await promise;
670
+
671
+ if (
672
+ this.transaction.state === TxnState.NO_TRANSACTION ||
673
+ this.transaction.state === TxnState.TRANSACTION_COMMITTED ||
674
+ this.transaction.state === TxnState.TRANSACTION_ABORTED
675
+ ) {
676
+ // Assume callback intentionally ended the transaction
677
+ return result;
678
+ }
679
+ } catch (fnError) {
680
+ if (!(fnError instanceof MongoError) || fnError instanceof MongoInvalidArgumentError) {
681
+ await this.abortTransaction();
682
+ throw fnError;
683
+ }
684
+
685
+ if (
686
+ this.transaction.state === TxnState.STARTING_TRANSACTION ||
687
+ this.transaction.state === TxnState.TRANSACTION_IN_PROGRESS
688
+ ) {
689
+ await this.abortTransaction();
690
+ }
691
+
692
+ if (
693
+ fnError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
694
+ now() - startTime < MAX_TIMEOUT
695
+ ) {
696
+ continue;
697
+ }
698
+
699
+ throw fnError;
700
+ }
701
+
702
+ while (!committed) {
703
+ try {
704
+ /*
705
+ * We will rely on ClientSession.commitTransaction() to
706
+ * apply a majority write concern if commitTransaction is
707
+ * being retried (see: DRIVERS-601)
708
+ */
709
+ await this.commitTransaction();
710
+ committed = true;
711
+ } catch (commitError) {
712
+ /*
713
+ * Note: a maxTimeMS error will have the MaxTimeMSExpired
714
+ * code (50) and can be reported as a top-level error or
715
+ * inside writeConcernError, ex.
716
+ * { ok:0, code: 50, codeName: 'MaxTimeMSExpired' }
717
+ * { ok:1, writeConcernError: { code: 50, codeName: 'MaxTimeMSExpired' } }
718
+ */
719
+ if (
720
+ !isMaxTimeMSExpiredError(commitError) &&
721
+ commitError.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult) &&
722
+ now() - startTime < MAX_TIMEOUT
723
+ ) {
724
+ continue;
725
+ }
726
+
727
+ if (
728
+ commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
729
+ now() - startTime < MAX_TIMEOUT
730
+ ) {
731
+ break;
732
+ }
733
+
734
+ throw commitError;
735
+ }
736
+ }
737
+ }
738
+
739
+ return result;
484
740
  }
485
741
  }
486
742
 
487
- const MAX_WITH_TRANSACTION_TIMEOUT = 120000;
743
+ configureResourceManagement(ClientSession.prototype);
744
+
488
745
  const NON_DETERMINISTIC_WRITE_CONCERN_ERRORS = new Set([
489
746
  'CannotSatisfyWriteConcern',
490
747
  'UnknownReplWriteConcern',
491
748
  'UnsatisfiableWriteConcern'
492
749
  ]);
493
750
 
494
- function hasNotTimedOut(startTime: number, max: number) {
495
- return calculateDurationInMs(startTime) < max;
751
+ function shouldUnpinAfterCommitError(commitError: Error) {
752
+ if (commitError instanceof MongoError) {
753
+ if (
754
+ isRetryableWriteError(commitError) ||
755
+ commitError instanceof MongoWriteConcernError ||
756
+ isMaxTimeMSExpiredError(commitError)
757
+ ) {
758
+ if (isUnknownTransactionCommitResult(commitError)) {
759
+ // per txns spec, must unpin session in this case
760
+ return true;
761
+ }
762
+ } else if (commitError.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
763
+ return true;
764
+ }
765
+ }
766
+ return false;
767
+ }
768
+
769
+ function shouldAddUnknownTransactionCommitResultLabel(commitError: MongoError) {
770
+ let ok = isRetryableWriteError(commitError);
771
+ ok ||= commitError instanceof MongoWriteConcernError;
772
+ ok ||= isMaxTimeMSExpiredError(commitError);
773
+ ok &&= isUnknownTransactionCommitResult(commitError);
774
+ return ok;
496
775
  }
497
776
 
498
- function isUnknownTransactionCommitResult(err: MongoError) {
777
+ function isUnknownTransactionCommitResult(err: MongoError): err is MongoError {
499
778
  const isNonDeterministicWriteConcernError =
500
779
  err instanceof MongoServerError &&
501
780
  err.codeName &&
@@ -550,282 +829,17 @@ export function maybeClearPinnedConnection(
550
829
  }
551
830
  }
552
831
 
553
- function isMaxTimeMSExpiredError(err: MongoError) {
832
+ function isMaxTimeMSExpiredError(err: MongoError): boolean {
554
833
  if (err == null || !(err instanceof MongoServerError)) {
555
834
  return false;
556
835
  }
557
836
 
558
837
  return (
559
838
  err.code === MONGODB_ERROR_CODES.MaxTimeMSExpired ||
560
- (err.writeConcernError && err.writeConcernError.code === MONGODB_ERROR_CODES.MaxTimeMSExpired)
839
+ err.writeConcernError?.code === MONGODB_ERROR_CODES.MaxTimeMSExpired
561
840
  );
562
841
  }
563
842
 
564
- async function attemptTransactionCommit<T>(
565
- session: ClientSession,
566
- startTime: number,
567
- fn: WithTransactionCallback<T>,
568
- result: T,
569
- options: TransactionOptions
570
- ): Promise<T> {
571
- try {
572
- await session.commitTransaction();
573
- return result;
574
- } catch (commitErr) {
575
- if (
576
- commitErr instanceof MongoError &&
577
- hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT) &&
578
- !isMaxTimeMSExpiredError(commitErr)
579
- ) {
580
- if (commitErr.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult)) {
581
- return await attemptTransactionCommit(session, startTime, fn, result, options);
582
- }
583
-
584
- if (commitErr.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
585
- return await attemptTransaction(session, startTime, fn, options);
586
- }
587
- }
588
-
589
- throw commitErr;
590
- }
591
- }
592
-
593
- const USER_EXPLICIT_TXN_END_STATES = new Set<TxnState>([
594
- TxnState.NO_TRANSACTION,
595
- TxnState.TRANSACTION_COMMITTED,
596
- TxnState.TRANSACTION_ABORTED
597
- ]);
598
-
599
- function userExplicitlyEndedTransaction(session: ClientSession) {
600
- return USER_EXPLICIT_TXN_END_STATES.has(session.transaction.state);
601
- }
602
-
603
- async function attemptTransaction<T>(
604
- session: ClientSession,
605
- startTime: number,
606
- fn: WithTransactionCallback<T>,
607
- options: TransactionOptions = {}
608
- ): Promise<T> {
609
- session.startTransaction(options);
610
-
611
- let promise;
612
- try {
613
- promise = fn(session);
614
- } catch (err) {
615
- promise = Promise.reject(err);
616
- }
617
-
618
- if (!isPromiseLike(promise)) {
619
- try {
620
- await session.abortTransaction();
621
- } catch (error) {
622
- squashError(error);
623
- }
624
- throw new MongoInvalidArgumentError(
625
- 'Function provided to `withTransaction` must return a Promise'
626
- );
627
- }
628
-
629
- try {
630
- const result = await promise;
631
- if (userExplicitlyEndedTransaction(session)) {
632
- return result;
633
- }
634
- return await attemptTransactionCommit(session, startTime, fn, result, options);
635
- } catch (err) {
636
- if (session.inTransaction()) {
637
- await session.abortTransaction();
638
- }
639
-
640
- if (
641
- err instanceof MongoError &&
642
- err.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
643
- hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT)
644
- ) {
645
- return await attemptTransaction(session, startTime, fn, options);
646
- }
647
-
648
- if (isMaxTimeMSExpiredError(err)) {
649
- err.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
650
- }
651
-
652
- throw err;
653
- }
654
- }
655
-
656
- async function endTransaction(
657
- session: ClientSession,
658
- commandName: 'abortTransaction' | 'commitTransaction'
659
- ): Promise<void> {
660
- // handle any initial problematic cases
661
- const txnState = session.transaction.state;
662
-
663
- if (txnState === TxnState.NO_TRANSACTION) {
664
- throw new MongoTransactionError('No transaction started');
665
- }
666
-
667
- if (commandName === 'commitTransaction') {
668
- if (
669
- txnState === TxnState.STARTING_TRANSACTION ||
670
- txnState === TxnState.TRANSACTION_COMMITTED_EMPTY
671
- ) {
672
- // the transaction was never started, we can safely exit here
673
- session.transaction.transition(TxnState.TRANSACTION_COMMITTED_EMPTY);
674
- return;
675
- }
676
-
677
- if (txnState === TxnState.TRANSACTION_ABORTED) {
678
- throw new MongoTransactionError(
679
- 'Cannot call commitTransaction after calling abortTransaction'
680
- );
681
- }
682
- } else {
683
- if (txnState === TxnState.STARTING_TRANSACTION) {
684
- // the transaction was never started, we can safely exit here
685
- session.transaction.transition(TxnState.TRANSACTION_ABORTED);
686
- return;
687
- }
688
-
689
- if (txnState === TxnState.TRANSACTION_ABORTED) {
690
- throw new MongoTransactionError('Cannot call abortTransaction twice');
691
- }
692
-
693
- if (
694
- txnState === TxnState.TRANSACTION_COMMITTED ||
695
- txnState === TxnState.TRANSACTION_COMMITTED_EMPTY
696
- ) {
697
- throw new MongoTransactionError(
698
- 'Cannot call abortTransaction after calling commitTransaction'
699
- );
700
- }
701
- }
702
-
703
- // construct and send the command
704
- const command: Document = { [commandName]: 1 };
705
-
706
- // apply a writeConcern if specified
707
- let writeConcern;
708
- if (session.transaction.options.writeConcern) {
709
- writeConcern = Object.assign({}, session.transaction.options.writeConcern);
710
- } else if (session.clientOptions && session.clientOptions.writeConcern) {
711
- writeConcern = { w: session.clientOptions.writeConcern.w };
712
- }
713
-
714
- if (txnState === TxnState.TRANSACTION_COMMITTED) {
715
- writeConcern = Object.assign({ wtimeoutMS: 10000 }, writeConcern, { w: 'majority' });
716
- }
717
-
718
- if (writeConcern) {
719
- WriteConcern.apply(command, writeConcern);
720
- }
721
-
722
- if (commandName === 'commitTransaction' && session.transaction.options.maxTimeMS) {
723
- Object.assign(command, { maxTimeMS: session.transaction.options.maxTimeMS });
724
- }
725
-
726
- if (session.transaction.recoveryToken) {
727
- command.recoveryToken = session.transaction.recoveryToken;
728
- }
729
-
730
- try {
731
- // send the command
732
- await executeOperation(
733
- session.client,
734
- new RunAdminCommandOperation(command, {
735
- session,
736
- readPreference: ReadPreference.primary,
737
- bypassPinningCheck: true
738
- })
739
- );
740
- if (command.abortTransaction) {
741
- // always unpin on abort regardless of command outcome
742
- session.unpin();
743
- }
744
- if (commandName !== 'commitTransaction') {
745
- session.transaction.transition(TxnState.TRANSACTION_ABORTED);
746
- if (session.loadBalanced) {
747
- maybeClearPinnedConnection(session, { force: false });
748
- }
749
- } else {
750
- session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
751
- }
752
- } catch (firstAttemptErr) {
753
- if (command.abortTransaction) {
754
- // always unpin on abort regardless of command outcome
755
- session.unpin();
756
- }
757
- if (firstAttemptErr instanceof MongoError && isRetryableWriteError(firstAttemptErr)) {
758
- // SPEC-1185: apply majority write concern when retrying commitTransaction
759
- if (command.commitTransaction) {
760
- // per txns spec, must unpin session in this case
761
- session.unpin({ force: true });
762
-
763
- command.writeConcern = Object.assign({ wtimeout: 10000 }, command.writeConcern, {
764
- w: 'majority'
765
- });
766
- }
767
-
768
- try {
769
- await executeOperation(
770
- session.client,
771
- new RunAdminCommandOperation(command, {
772
- session,
773
- readPreference: ReadPreference.primary,
774
- bypassPinningCheck: true
775
- })
776
- );
777
- if (commandName !== 'commitTransaction') {
778
- session.transaction.transition(TxnState.TRANSACTION_ABORTED);
779
- if (session.loadBalanced) {
780
- maybeClearPinnedConnection(session, { force: false });
781
- }
782
- } else {
783
- session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
784
- }
785
- } catch (secondAttemptErr) {
786
- handleEndTransactionError(session, commandName, secondAttemptErr);
787
- }
788
- } else {
789
- handleEndTransactionError(session, commandName, firstAttemptErr);
790
- }
791
- }
792
- }
793
-
794
- function handleEndTransactionError(
795
- session: ClientSession,
796
- commandName: 'abortTransaction' | 'commitTransaction',
797
- error: Error
798
- ) {
799
- if (commandName !== 'commitTransaction') {
800
- session.transaction.transition(TxnState.TRANSACTION_ABORTED);
801
- if (session.loadBalanced) {
802
- maybeClearPinnedConnection(session, { force: false });
803
- }
804
- // The spec indicates that if the operation times out or fails with a non-retryable error, we should ignore all errors on `abortTransaction`
805
- return;
806
- }
807
-
808
- session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
809
- if (error instanceof MongoError) {
810
- if (
811
- isRetryableWriteError(error) ||
812
- error instanceof MongoWriteConcernError ||
813
- isMaxTimeMSExpiredError(error)
814
- ) {
815
- if (isUnknownTransactionCommitResult(error)) {
816
- error.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
817
-
818
- // per txns spec, must unpin session in this case
819
- session.unpin({ error });
820
- }
821
- } else if (error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
822
- session.unpin({ error });
823
- }
824
- }
825
-
826
- throw error;
827
- }
828
-
829
843
  /** @public */
830
844
  export type ServerSessionId = { id: Binary };
831
845
 
package/src/sort.ts CHANGED
@@ -60,7 +60,7 @@ function isPair(t: Sort): t is [string, SortDirection] {
60
60
  try {
61
61
  prepareDirection(t[1]);
62
62
  return true;
63
- } catch (e) {
63
+ } catch {
64
64
  return false;
65
65
  }
66
66
  }
package/src/timeout.ts CHANGED
@@ -89,7 +89,6 @@ export class Timeout extends Promise<never> {
89
89
  Symbol.toStringTag in timeout &&
90
90
  timeout[Symbol.toStringTag] === 'MongoDBTimeout' &&
91
91
  'then' in timeout &&
92
- // eslint-disable-next-line github/no-then
93
92
  typeof timeout.then === 'function'
94
93
  );
95
94
  }
@@ -2,8 +2,7 @@ import type { Document } from './bson';
2
2
  import { MongoRuntimeError, MongoTransactionError } from './error';
3
3
  import type { CommandOperationOptions } from './operations/command';
4
4
  import { ReadConcern, type ReadConcernLike } from './read_concern';
5
- import type { ReadPreferenceLike } from './read_preference';
6
- import { ReadPreference } from './read_preference';
5
+ import { ReadPreference, type ReadPreferenceLike } from './read_preference';
7
6
  import type { Server } from './sdam/server';
8
7
  import { WriteConcern } from './write_concern';
9
8