mongodb 4.4.1 → 4.6.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 (204) hide show
  1. package/README.md +3 -2
  2. package/lib/admin.js +5 -6
  3. package/lib/admin.js.map +1 -1
  4. package/lib/bulk/common.js +34 -7
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/bulk/unordered.js.map +1 -1
  7. package/lib/change_stream.js +251 -245
  8. package/lib/change_stream.js.map +1 -1
  9. package/lib/cmap/auth/gssapi.js.map +1 -1
  10. package/lib/cmap/auth/mongocr.js.map +1 -1
  11. package/lib/cmap/auth/mongodb_aws.js +3 -0
  12. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  13. package/lib/cmap/auth/plain.js.map +1 -1
  14. package/lib/cmap/auth/scram.js +1 -0
  15. package/lib/cmap/auth/scram.js.map +1 -1
  16. package/lib/cmap/auth/x509.js.map +1 -1
  17. package/lib/cmap/commands.js +12 -11
  18. package/lib/cmap/commands.js.map +1 -1
  19. package/lib/cmap/connect.js +8 -1
  20. package/lib/cmap/connect.js.map +1 -1
  21. package/lib/cmap/connection.js +111 -145
  22. package/lib/cmap/connection.js.map +1 -1
  23. package/lib/cmap/errors.js.map +1 -1
  24. package/lib/cmap/message_stream.js.map +1 -1
  25. package/lib/cmap/stream_description.js +3 -0
  26. package/lib/cmap/stream_description.js.map +1 -1
  27. package/lib/collection.js +60 -29
  28. package/lib/collection.js.map +1 -1
  29. package/lib/connection_string.js +32 -11
  30. package/lib/connection_string.js.map +1 -1
  31. package/lib/constants.js +8 -1
  32. package/lib/constants.js.map +1 -1
  33. package/lib/cursor/abstract_cursor.js +64 -51
  34. package/lib/cursor/abstract_cursor.js.map +1 -1
  35. package/lib/cursor/aggregation_cursor.js +2 -2
  36. package/lib/cursor/aggregation_cursor.js.map +1 -1
  37. package/lib/cursor/find_cursor.js +9 -4
  38. package/lib/cursor/find_cursor.js.map +1 -1
  39. package/lib/db.js +20 -13
  40. package/lib/db.js.map +1 -1
  41. package/lib/encrypter.js +21 -10
  42. package/lib/encrypter.js.map +1 -1
  43. package/lib/error.js +121 -59
  44. package/lib/error.js.map +1 -1
  45. package/lib/gridfs/download.js +2 -0
  46. package/lib/gridfs/download.js.map +1 -1
  47. package/lib/gridfs/index.js +42 -51
  48. package/lib/gridfs/index.js.map +1 -1
  49. package/lib/gridfs/upload.js +1 -1
  50. package/lib/gridfs/upload.js.map +1 -1
  51. package/lib/index.js +7 -3
  52. package/lib/index.js.map +1 -1
  53. package/lib/mongo_client.js +21 -27
  54. package/lib/mongo_client.js.map +1 -1
  55. package/lib/operations/add_user.js +8 -1
  56. package/lib/operations/add_user.js.map +1 -1
  57. package/lib/operations/aggregate.js +5 -0
  58. package/lib/operations/aggregate.js.map +1 -1
  59. package/lib/operations/bulk_write.js.map +1 -1
  60. package/lib/operations/collections.js.map +1 -1
  61. package/lib/operations/command.js +0 -3
  62. package/lib/operations/command.js.map +1 -1
  63. package/lib/operations/common_functions.js +8 -1
  64. package/lib/operations/common_functions.js.map +1 -1
  65. package/lib/operations/count.js.map +1 -1
  66. package/lib/operations/count_documents.js.map +1 -1
  67. package/lib/operations/create_collection.js +51 -17
  68. package/lib/operations/create_collection.js.map +1 -1
  69. package/lib/operations/delete.js +5 -3
  70. package/lib/operations/delete.js.map +1 -1
  71. package/lib/operations/distinct.js.map +1 -1
  72. package/lib/operations/drop.js +67 -7
  73. package/lib/operations/drop.js.map +1 -1
  74. package/lib/operations/estimated_document_count.js.map +1 -1
  75. package/lib/operations/eval.js.map +1 -1
  76. package/lib/operations/execute_operation.js +71 -79
  77. package/lib/operations/execute_operation.js.map +1 -1
  78. package/lib/operations/find.js +3 -52
  79. package/lib/operations/find.js.map +1 -1
  80. package/lib/operations/find_and_modify.js +5 -0
  81. package/lib/operations/find_and_modify.js.map +1 -1
  82. package/lib/operations/get_more.js +5 -0
  83. package/lib/operations/get_more.js.map +1 -1
  84. package/lib/operations/indexes.js +8 -9
  85. package/lib/operations/indexes.js.map +1 -1
  86. package/lib/operations/insert.js +8 -2
  87. package/lib/operations/insert.js.map +1 -1
  88. package/lib/operations/is_capped.js.map +1 -1
  89. package/lib/operations/list_collections.js +10 -42
  90. package/lib/operations/list_collections.js.map +1 -1
  91. package/lib/operations/list_databases.js +5 -0
  92. package/lib/operations/list_databases.js.map +1 -1
  93. package/lib/operations/map_reduce.js +1 -2
  94. package/lib/operations/map_reduce.js.map +1 -1
  95. package/lib/operations/operation.js +1 -3
  96. package/lib/operations/operation.js.map +1 -1
  97. package/lib/operations/options_operation.js.map +1 -1
  98. package/lib/operations/profiling_level.js.map +1 -1
  99. package/lib/operations/remove_user.js.map +1 -1
  100. package/lib/operations/rename.js +1 -1
  101. package/lib/operations/rename.js.map +1 -1
  102. package/lib/operations/run_command.js.map +1 -1
  103. package/lib/operations/set_profiling_level.js.map +1 -1
  104. package/lib/operations/stats.js.map +1 -1
  105. package/lib/operations/update.js +5 -0
  106. package/lib/operations/update.js.map +1 -1
  107. package/lib/operations/validate_collection.js.map +1 -1
  108. package/lib/read_concern.js +1 -0
  109. package/lib/read_concern.js.map +1 -1
  110. package/lib/sdam/common.js +1 -7
  111. package/lib/sdam/common.js.map +1 -1
  112. package/lib/sdam/events.js +1 -1
  113. package/lib/sdam/events.js.map +1 -1
  114. package/lib/sdam/monitor.js +1 -2
  115. package/lib/sdam/monitor.js.map +1 -1
  116. package/lib/sdam/server.js +108 -78
  117. package/lib/sdam/server.js.map +1 -1
  118. package/lib/sdam/topology.js +38 -55
  119. package/lib/sdam/topology.js.map +1 -1
  120. package/lib/sdam/topology_description.js +3 -4
  121. package/lib/sdam/topology_description.js.map +1 -1
  122. package/lib/sessions.js +93 -68
  123. package/lib/sessions.js.map +1 -1
  124. package/lib/utils.js +21 -97
  125. package/lib/utils.js.map +1 -1
  126. package/mongodb.d.ts +417 -92
  127. package/package.json +25 -29
  128. package/src/admin.ts +6 -10
  129. package/src/bulk/common.ts +45 -14
  130. package/src/bulk/unordered.ts +1 -1
  131. package/src/change_stream.ts +559 -425
  132. package/src/cmap/auth/gssapi.ts +1 -1
  133. package/src/cmap/auth/mongocr.ts +1 -1
  134. package/src/cmap/auth/mongodb_aws.ts +6 -1
  135. package/src/cmap/auth/plain.ts +1 -1
  136. package/src/cmap/auth/scram.ts +3 -2
  137. package/src/cmap/auth/x509.ts +6 -2
  138. package/src/cmap/commands.ts +26 -22
  139. package/src/cmap/connect.ts +15 -14
  140. package/src/cmap/connection.ts +163 -185
  141. package/src/cmap/errors.ts +2 -2
  142. package/src/cmap/message_stream.ts +2 -2
  143. package/src/cmap/stream_description.ts +4 -1
  144. package/src/collection.ts +66 -35
  145. package/src/connection_string.ts +46 -18
  146. package/src/constants.ts +6 -0
  147. package/src/cursor/abstract_cursor.ts +87 -65
  148. package/src/cursor/aggregation_cursor.ts +4 -4
  149. package/src/cursor/find_cursor.ts +16 -8
  150. package/src/db.ts +27 -24
  151. package/src/deps.ts +40 -0
  152. package/src/encrypter.ts +22 -11
  153. package/src/error.ts +170 -89
  154. package/src/gridfs/download.ts +3 -1
  155. package/src/gridfs/index.ts +51 -68
  156. package/src/gridfs/upload.ts +13 -13
  157. package/src/index.ts +21 -0
  158. package/src/mongo_client.ts +36 -50
  159. package/src/mongo_types.ts +1 -1
  160. package/src/operations/add_user.ts +14 -3
  161. package/src/operations/aggregate.ts +15 -5
  162. package/src/operations/bulk_write.ts +6 -2
  163. package/src/operations/collections.ts +6 -2
  164. package/src/operations/command.ts +23 -12
  165. package/src/operations/common_functions.ts +8 -1
  166. package/src/operations/count.ts +6 -2
  167. package/src/operations/count_documents.ts +5 -1
  168. package/src/operations/create_collection.ts +91 -23
  169. package/src/operations/delete.ts +19 -13
  170. package/src/operations/distinct.ts +6 -2
  171. package/src/operations/drop.ts +100 -10
  172. package/src/operations/estimated_document_count.ts +11 -3
  173. package/src/operations/eval.ts +6 -2
  174. package/src/operations/execute_operation.ts +103 -101
  175. package/src/operations/find.ts +9 -85
  176. package/src/operations/find_and_modify.ts +21 -2
  177. package/src/operations/get_more.ts +20 -6
  178. package/src/operations/indexes.ts +54 -36
  179. package/src/operations/insert.ts +28 -7
  180. package/src/operations/is_capped.ts +6 -2
  181. package/src/operations/list_collections.ts +24 -59
  182. package/src/operations/list_databases.ts +13 -3
  183. package/src/operations/map_reduce.ts +7 -6
  184. package/src/operations/operation.ts +10 -9
  185. package/src/operations/options_operation.ts +6 -2
  186. package/src/operations/profiling_level.ts +6 -2
  187. package/src/operations/remove_user.ts +6 -2
  188. package/src/operations/rename.ts +7 -3
  189. package/src/operations/run_command.ts +6 -2
  190. package/src/operations/set_profiling_level.ts +6 -2
  191. package/src/operations/stats.ts +12 -4
  192. package/src/operations/update.ts +19 -9
  193. package/src/operations/validate_collection.ts +6 -2
  194. package/src/read_concern.ts +1 -0
  195. package/src/sdam/common.ts +0 -6
  196. package/src/sdam/events.ts +2 -2
  197. package/src/sdam/monitor.ts +4 -5
  198. package/src/sdam/server.ts +125 -117
  199. package/src/sdam/topology.ts +39 -78
  200. package/src/sdam/topology_description.ts +3 -4
  201. package/src/sessions.ts +108 -78
  202. package/src/utils.ts +39 -119
  203. package/tsconfig.json +40 -0
  204. package/mongodb.ts34.d.ts +0 -5720
package/src/sessions.ts CHANGED
@@ -6,16 +6,14 @@ import { PINNED, UNPINNED } from './constants';
6
6
  import type { AbstractCursor } from './cursor/abstract_cursor';
7
7
  import {
8
8
  AnyError,
9
- isRetryableEndTransactionError,
10
- isRetryableError,
11
9
  MongoAPIError,
12
10
  MongoCompatibilityError,
13
11
  MONGODB_ERROR_CODES,
14
12
  MongoDriverError,
15
13
  MongoError,
14
+ MongoErrorLabel,
16
15
  MongoExpiredSessionError,
17
16
  MongoInvalidArgumentError,
18
- MongoNetworkError,
19
17
  MongoRuntimeError,
20
18
  MongoServerError,
21
19
  MongoTransactionError,
@@ -41,24 +39,9 @@ import {
41
39
  now,
42
40
  uuidV4
43
41
  } from './utils';
44
- import type { WriteConcern } from './write_concern';
45
42
 
46
43
  const minWireVersionForShardedTransactions = 8;
47
44
 
48
- function assertAlive(session: ClientSession, callback?: Callback): boolean {
49
- if (session.serverSession == null) {
50
- const error = new MongoExpiredSessionError();
51
- if (typeof callback === 'function') {
52
- callback(error);
53
- return false;
54
- }
55
-
56
- throw error;
57
- }
58
-
59
- return true;
60
- }
61
-
62
45
  /** @public */
63
46
  export interface ClientSessionOptions {
64
47
  /** Whether causal consistency should be enabled on this session */
@@ -92,6 +75,8 @@ const kSnapshotTime = Symbol('snapshotTime');
92
75
  const kSnapshotEnabled = Symbol('snapshotEnabled');
93
76
  /** @internal */
94
77
  const kPinnedConnection = Symbol('pinnedConnection');
78
+ /** @internal Accumulates total number of increments to add to txnNumber when applying session to command */
79
+ const kTxnNumberIncrement = Symbol('txnNumberIncrement');
95
80
 
96
81
  /** @public */
97
82
  export interface EndSessionOptions {
@@ -126,13 +111,15 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
126
111
  defaultTransactionOptions: TransactionOptions;
127
112
  transaction: Transaction;
128
113
  /** @internal */
129
- [kServerSession]?: ServerSession;
114
+ [kServerSession]: ServerSession | null;
130
115
  /** @internal */
131
116
  [kSnapshotTime]?: Timestamp;
132
117
  /** @internal */
133
118
  [kSnapshotEnabled] = false;
134
119
  /** @internal */
135
120
  [kPinnedConnection]?: Connection;
121
+ /** @internal */
122
+ [kTxnNumberIncrement]: number;
136
123
 
137
124
  /**
138
125
  * Create a client session.
@@ -175,7 +162,10 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
175
162
  this.sessionPool = sessionPool;
176
163
  this.hasEnded = false;
177
164
  this.clientOptions = clientOptions;
178
- this[kServerSession] = undefined;
165
+
166
+ this.explicit = !!options.explicit;
167
+ this[kServerSession] = this.explicit ? this.sessionPool.acquire() : null;
168
+ this[kTxnNumberIncrement] = 0;
179
169
 
180
170
  this.supports = {
181
171
  causalConsistency: options.snapshot !== true && options.causalConsistency !== false
@@ -184,7 +174,6 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
184
174
  this.clusterTime = options.initialClusterTime;
185
175
 
186
176
  this.operationTime = undefined;
187
- this.explicit = !!options.explicit;
188
177
  this.owner = options.owner;
189
178
  this.defaultTransactionOptions = Object.assign({}, options.defaultTransactionOptions);
190
179
  this.transaction = new Transaction();
@@ -192,16 +181,22 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
192
181
 
193
182
  /** The server id associated with this session */
194
183
  get id(): ServerSessionId | undefined {
195
- return this.serverSession?.id;
184
+ return this[kServerSession]?.id;
196
185
  }
197
186
 
198
187
  get serverSession(): ServerSession {
199
- if (this[kServerSession] == null) {
200
- this[kServerSession] = this.sessionPool.acquire();
188
+ let serverSession = this[kServerSession];
189
+ if (serverSession == null) {
190
+ if (this.explicit) {
191
+ throw new MongoRuntimeError('Unexpected null serverSession for an explicit session');
192
+ }
193
+ if (this.hasEnded) {
194
+ throw new MongoRuntimeError('Unexpected null serverSession for an ended implicit session');
195
+ }
196
+ serverSession = this.sessionPool.acquire();
197
+ this[kServerSession] = serverSession;
201
198
  }
202
-
203
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
204
- return this[kServerSession]!;
199
+ return serverSession;
205
200
  }
206
201
 
207
202
  /** Whether or not this session is configured for snapshot reads */
@@ -270,9 +265,15 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
270
265
  const completeEndSession = () => {
271
266
  maybeClearPinnedConnection(this, finalOptions);
272
267
 
273
- // release the server session back to the pool
274
- this.sessionPool.release(this.serverSession);
275
- this[kServerSession] = undefined;
268
+ const serverSession = this[kServerSession];
269
+ if (serverSession != null) {
270
+ // release the server session back to the pool
271
+ this.sessionPool.release(serverSession);
272
+ // Make sure a new serverSession never makes it on to the ClientSession
273
+ Object.defineProperty(this, kServerSession, {
274
+ value: ServerSession.clone(serverSession)
275
+ });
276
+ }
276
277
 
277
278
  // mark the session as ended, and emit a signal
278
279
  this.hasEnded = true;
@@ -282,7 +283,9 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
282
283
  done();
283
284
  };
284
285
 
285
- if (this.serverSession && this.inTransaction()) {
286
+ if (this.inTransaction()) {
287
+ // If we've reached endSession and the transaction is still active
288
+ // by default we abort it
286
289
  this.abortTransaction(err => {
287
290
  if (err) return done(err);
288
291
  completeEndSession();
@@ -356,12 +359,16 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
356
359
  return this.id.id.buffer.equals(session.id.id.buffer);
357
360
  }
358
361
 
359
- /** Increment the transaction number on the internal ServerSession */
362
+ /**
363
+ * Increment the transaction number on the internal ServerSession
364
+ *
365
+ * @privateRemarks
366
+ * This helper increments a value stored on the client session that will be
367
+ * added to the serverSession's txnNumber upon applying it to a command.
368
+ * This is because the serverSession is lazily acquired after a connection is obtained
369
+ */
360
370
  incrementTransactionNumber(): void {
361
- if (this.serverSession) {
362
- this.serverSession.txnNumber =
363
- typeof this.serverSession.txnNumber === 'number' ? this.serverSession.txnNumber + 1 : 0;
364
- }
371
+ this[kTxnNumberIncrement] += 1;
365
372
  }
366
373
 
367
374
  /** @returns whether this session is currently in a transaction or not */
@@ -379,7 +386,6 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
379
386
  throw new MongoCompatibilityError('Transactions are not allowed with snapshot sessions');
380
387
  }
381
388
 
382
- assertAlive(this);
383
389
  if (this.inTransaction()) {
384
390
  throw new MongoTransactionError('Transaction already in progress');
385
391
  }
@@ -507,7 +513,7 @@ export function maybeClearPinnedConnection(
507
513
  session.inTransaction() &&
508
514
  error &&
509
515
  error instanceof MongoError &&
510
- error.hasErrorLabel('TransientTransactionError')
516
+ error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)
511
517
  ) {
512
518
  return;
513
519
  }
@@ -559,11 +565,11 @@ function attemptTransactionCommit<T>(
559
565
  hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT) &&
560
566
  !isMaxTimeMSExpiredError(err)
561
567
  ) {
562
- if (err.hasErrorLabel('UnknownTransactionCommitResult')) {
568
+ if (err.hasErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult)) {
563
569
  return attemptTransactionCommit(session, startTime, fn, options);
564
570
  }
565
571
 
566
- if (err.hasErrorLabel('TransientTransactionError')) {
572
+ if (err.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
567
573
  return attemptTransaction(session, startTime, fn, options);
568
574
  }
569
575
  }
@@ -617,20 +623,20 @@ function attemptTransaction<TSchema>(
617
623
  function maybeRetryOrThrow(err: MongoError): Promise<any> {
618
624
  if (
619
625
  err instanceof MongoError &&
620
- err.hasErrorLabel('TransientTransactionError') &&
626
+ err.hasErrorLabel(MongoErrorLabel.TransientTransactionError) &&
621
627
  hasNotTimedOut(startTime, MAX_WITH_TRANSACTION_TIMEOUT)
622
628
  ) {
623
629
  return attemptTransaction(session, startTime, fn, options);
624
630
  }
625
631
 
626
632
  if (isMaxTimeMSExpiredError(err)) {
627
- err.addErrorLabel('UnknownTransactionCommitResult');
633
+ err.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
628
634
  }
629
635
 
630
636
  throw err;
631
637
  }
632
638
 
633
- if (session.transaction.isActive) {
639
+ if (session.inTransaction()) {
634
640
  return session.abortTransaction().then(() => maybeRetryOrThrow(err));
635
641
  }
636
642
 
@@ -639,12 +645,11 @@ function attemptTransaction<TSchema>(
639
645
  );
640
646
  }
641
647
 
642
- function endTransaction(session: ClientSession, commandName: string, callback: Callback<Document>) {
643
- if (!assertAlive(session, callback)) {
644
- // checking result in case callback was called
645
- return;
646
- }
647
-
648
+ function endTransaction(
649
+ session: ClientSession,
650
+ commandName: 'abortTransaction' | 'commitTransaction',
651
+ callback: Callback<Document>
652
+ ) {
648
653
  // handle any initial problematic cases
649
654
  const txnState = session.transaction.state;
650
655
 
@@ -717,7 +722,7 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
717
722
  Object.assign(command, { maxTimeMS: session.transaction.options.maxTimeMS });
718
723
  }
719
724
 
720
- function commandHandler(e?: MongoError, r?: Document) {
725
+ function commandHandler(error?: Error, result?: Document) {
721
726
  if (commandName !== 'commitTransaction') {
722
727
  session.transaction.transition(TxnState.TRANSACTION_ABORTED);
723
728
  if (session.loadBalanced) {
@@ -729,47 +734,45 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
729
734
  }
730
735
 
731
736
  session.transaction.transition(TxnState.TRANSACTION_COMMITTED);
732
- if (e) {
737
+ if (error instanceof MongoError) {
733
738
  if (
734
- e instanceof MongoNetworkError ||
735
- e instanceof MongoWriteConcernError ||
736
- isRetryableError(e) ||
737
- isMaxTimeMSExpiredError(e)
739
+ error.hasErrorLabel(MongoErrorLabel.RetryableWriteError) ||
740
+ error instanceof MongoWriteConcernError ||
741
+ isMaxTimeMSExpiredError(error)
738
742
  ) {
739
- if (isUnknownTransactionCommitResult(e)) {
740
- e.addErrorLabel('UnknownTransactionCommitResult');
743
+ if (isUnknownTransactionCommitResult(error)) {
744
+ error.addErrorLabel(MongoErrorLabel.UnknownTransactionCommitResult);
741
745
 
742
746
  // per txns spec, must unpin session in this case
743
- session.unpin({ error: e });
747
+ session.unpin({ error });
744
748
  }
745
- } else if (e.hasErrorLabel('TransientTransactionError')) {
746
- session.unpin({ error: e });
749
+ } else if (error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)) {
750
+ session.unpin({ error });
747
751
  }
748
752
  }
749
753
 
750
- callback(e, r);
754
+ callback(error, result);
751
755
  }
752
756
 
753
- // Assumption here that commandName is "commitTransaction" or "abortTransaction"
754
757
  if (session.transaction.recoveryToken) {
755
758
  command.recoveryToken = session.transaction.recoveryToken;
756
759
  }
757
760
 
758
761
  // send the command
759
762
  executeOperation(
760
- session.topology,
763
+ session,
761
764
  new RunAdminCommandOperation(undefined, command, {
762
765
  session,
763
766
  readPreference: ReadPreference.primary,
764
767
  bypassPinningCheck: true
765
768
  }),
766
- (err, reply) => {
769
+ (error, result) => {
767
770
  if (command.abortTransaction) {
768
771
  // always unpin on abort regardless of command outcome
769
772
  session.unpin();
770
773
  }
771
774
 
772
- if (err && isRetryableEndTransactionError(err as MongoError)) {
775
+ if (error instanceof MongoError && error.hasErrorLabel(MongoErrorLabel.RetryableWriteError)) {
773
776
  // SPEC-1185: apply majority write concern when retrying commitTransaction
774
777
  if (command.commitTransaction) {
775
778
  // per txns spec, must unpin session in this case
@@ -781,17 +784,17 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
781
784
  }
782
785
 
783
786
  return executeOperation(
784
- session.topology,
787
+ session,
785
788
  new RunAdminCommandOperation(undefined, command, {
786
789
  session,
787
790
  readPreference: ReadPreference.primary,
788
791
  bypassPinningCheck: true
789
792
  }),
790
- (_err, _reply) => commandHandler(_err as MongoError, _reply)
793
+ commandHandler
791
794
  );
792
795
  }
793
796
 
794
- commandHandler(err as MongoError, reply);
797
+ commandHandler(error, result);
795
798
  }
796
799
  );
797
800
  }
@@ -832,6 +835,30 @@ export class ServerSession {
832
835
 
833
836
  return idleTimeMinutes > sessionTimeoutMinutes - 1;
834
837
  }
838
+
839
+ /**
840
+ * @internal
841
+ * Cloning meant to keep a readable reference to the server session data
842
+ * after ClientSession has ended
843
+ */
844
+ static clone(serverSession: ServerSession): Readonly<ServerSession> {
845
+ const arrayBuffer = new ArrayBuffer(16);
846
+ const idBytes = Buffer.from(arrayBuffer);
847
+ idBytes.set(serverSession.id.id.buffer);
848
+
849
+ const id = new Binary(idBytes, serverSession.id.id.sub_type);
850
+
851
+ // Manual prototype construction to avoid modifying the constructor of this class
852
+ return Object.setPrototypeOf(
853
+ {
854
+ id: { id },
855
+ lastUse: serverSession.lastUse,
856
+ txnNumber: serverSession.txnNumber,
857
+ isDirty: serverSession.isDirty
858
+ },
859
+ ServerSession.prototype
860
+ );
861
+ }
835
862
  }
836
863
 
837
864
  /**
@@ -936,26 +963,27 @@ export class ServerSessionPool {
936
963
  * @param session - the session tracking transaction state
937
964
  * @param command - the command to decorate
938
965
  * @param options - Optional settings passed to calling operation
966
+ *
967
+ * @internal
939
968
  */
940
969
  export function applySession(
941
970
  session: ClientSession,
942
971
  command: Document,
943
- options?: CommandOptions
972
+ options: CommandOptions
944
973
  ): MongoDriverError | undefined {
945
- // TODO: merge this with `assertAlive`, did not want to throw a try/catch here
946
974
  if (session.hasEnded) {
947
975
  return new MongoExpiredSessionError();
948
976
  }
949
977
 
978
+ // May acquire serverSession here
950
979
  const serverSession = session.serverSession;
951
980
  if (serverSession == null) {
952
981
  return new MongoRuntimeError('Unable to acquire server session');
953
982
  }
954
983
 
955
- // SPEC-1019: silently ignore explicit session with unacknowledged write for backwards compatibility
956
- // FIXME: NODE-2781, this check for write concern shouldn't be happening here, but instead during command construction
957
- if (options && options.writeConcern && (options.writeConcern as WriteConcern).w === 0) {
984
+ if (options.writeConcern?.w === 0) {
958
985
  if (session && session.explicit) {
986
+ // Error if user provided an explicit session to an unacknowledged write (SPEC-1019)
959
987
  return new MongoAPIError('Cannot have explicit session with unacknowledged writes');
960
988
  }
961
989
  return;
@@ -965,15 +993,16 @@ export function applySession(
965
993
  serverSession.lastUse = now();
966
994
  command.lsid = serverSession.id;
967
995
 
968
- // first apply non-transaction-specific sessions data
969
- const inTransaction = session.inTransaction() || isTransactionCommand(command);
970
- const isRetryableWrite = options?.willRetryWrite || false;
996
+ const inTxnOrTxnCommand = session.inTransaction() || isTransactionCommand(command);
997
+ const isRetryableWrite = !!options.willRetryWrite;
971
998
 
972
- if (serverSession.txnNumber && (isRetryableWrite || inTransaction)) {
999
+ if (isRetryableWrite || inTxnOrTxnCommand) {
1000
+ serverSession.txnNumber += session[kTxnNumberIncrement];
1001
+ session[kTxnNumberIncrement] = 0;
973
1002
  command.txnNumber = Long.fromNumber(serverSession.txnNumber);
974
1003
  }
975
1004
 
976
- if (!inTransaction) {
1005
+ if (!inTxnOrTxnCommand) {
977
1006
  if (session.transaction.state !== TxnState.NO_TRANSACTION) {
978
1007
  session.transaction.transition(TxnState.NO_TRANSACTION);
979
1008
  }
@@ -1015,6 +1044,7 @@ export function applySession(
1015
1044
  Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
1016
1045
  }
1017
1046
  }
1047
+ return;
1018
1048
  }
1019
1049
 
1020
1050
  export function updateSessionFromResponse(session: ClientSession, document: Document): void {
package/src/utils.ts CHANGED
@@ -8,11 +8,12 @@ import type { Connection } from './cmap/connection';
8
8
  import { MAX_SUPPORTED_WIRE_VERSION } from './cmap/wire_protocol/constants';
9
9
  import type { Collection } from './collection';
10
10
  import { LEGACY_HELLO_COMMAND } from './constants';
11
+ import type { AbstractCursor } from './cursor/abstract_cursor';
12
+ import type { FindCursor } from './cursor/find_cursor';
11
13
  import type { Db } from './db';
12
14
  import {
13
15
  AnyError,
14
16
  MongoCompatibilityError,
15
- MongoExpiredSessionError,
16
17
  MongoInvalidArgumentError,
17
18
  MongoNotConnectedError,
18
19
  MongoParseError,
@@ -37,8 +38,6 @@ import { W, WriteConcern, WriteConcernOptions } from './write_concern';
37
38
  * @public
38
39
  */
39
40
  export type Callback<T = any> = (error?: AnyError, result?: T) => void;
40
- /** @public */
41
- export type CallbackWithType<E = AnyError, T0 = any> = (error?: E, result?: T0) => void;
42
41
 
43
42
  export const MAX_JS_INT = Number.MAX_SAFE_INTEGER + 1;
44
43
 
@@ -177,7 +176,7 @@ export function mergeOptions<T, S>(target: T, source: S): T & S {
177
176
  }
178
177
 
179
178
  /** @internal */
180
- export function filterOptions(options: AnyOptions, names: string[]): AnyOptions {
179
+ export function filterOptions(options: AnyOptions, names: ReadonlyArray<string>): AnyOptions {
181
180
  const filterOptions: AnyOptions = {};
182
181
 
183
182
  for (const name in options) {
@@ -190,110 +189,6 @@ export function filterOptions(options: AnyOptions, names: string[]): AnyOptions
190
189
  return filterOptions;
191
190
  }
192
191
 
193
- /**
194
- * Executes the given operation with provided arguments.
195
- *
196
- * @remarks
197
- * This method reduces large amounts of duplication in the entire codebase by providing
198
- * a single point for determining whether callbacks or promises should be used. Additionally
199
- * it allows for a single point of entry to provide features such as implicit sessions, which
200
- * are required by the Driver Sessions specification in the event that a ClientSession is
201
- * not provided
202
- *
203
- * @internal
204
- *
205
- * @param topology - The topology to execute this operation on
206
- * @param operation - The operation to execute
207
- * @param args - Arguments to apply the provided operation
208
- * @param options - Options that modify the behavior of the method
209
- */
210
- export function executeLegacyOperation(
211
- topology: Topology,
212
- operation: (...args: any[]) => void | Promise<Document>,
213
- args: any[],
214
- options?: AnyOptions
215
- ): void | Promise<any> {
216
- const Promise = PromiseProvider.get();
217
-
218
- if (!Array.isArray(args)) {
219
- // TODO(NODE-3483)
220
- throw new MongoRuntimeError('This method requires an array of arguments to apply');
221
- }
222
-
223
- options = options ?? {};
224
-
225
- let callback = args[args.length - 1];
226
-
227
- // The driver sessions spec mandates that we implicitly create sessions for operations
228
- // that are not explicitly provided with a session.
229
- let session: ClientSession;
230
- let opOptions: any;
231
- let owner: any;
232
- if (!options.skipSessions && topology.hasSessionSupport()) {
233
- opOptions = args[args.length - 2];
234
- if (opOptions == null || opOptions.session == null) {
235
- owner = Symbol();
236
- session = topology.startSession({ owner });
237
- const optionsIndex = args.length - 2;
238
- args[optionsIndex] = Object.assign({}, args[optionsIndex], { session: session });
239
- } else if (opOptions.session && opOptions.session.hasEnded) {
240
- throw new MongoExpiredSessionError();
241
- }
242
- }
243
-
244
- function makeExecuteCallback(
245
- resolve: (value?: Document) => void,
246
- reject: (reason?: AnyError) => void
247
- ) {
248
- return function (err?: AnyError, result?: any) {
249
- if (session && session.owner === owner && !options?.returnsCursor) {
250
- session.endSession(() => {
251
- delete opOptions.session;
252
- if (err) return reject(err);
253
- resolve(result);
254
- });
255
- } else {
256
- if (err) return reject(err);
257
- resolve(result);
258
- }
259
- };
260
- }
261
-
262
- // Execute using callback
263
- if (typeof callback === 'function') {
264
- callback = args.pop();
265
- const handler = makeExecuteCallback(
266
- result => callback(undefined, result),
267
- err => callback(err, null)
268
- );
269
- args.push(handler);
270
-
271
- try {
272
- return operation(...args);
273
- } catch (e) {
274
- handler(e);
275
- throw e;
276
- }
277
- }
278
-
279
- // Return a Promise
280
- if (args[args.length - 1] != null) {
281
- // TODO(NODE-3483)
282
- throw new MongoRuntimeError('Final argument to `executeLegacyOperation` must be a callback');
283
- }
284
-
285
- return new Promise<any>((resolve, reject) => {
286
- const handler = makeExecuteCallback(resolve, reject);
287
- args[args.length - 1] = handler;
288
-
289
- try {
290
- return operation(...args);
291
- } catch (e) {
292
- handler(e);
293
- }
294
- });
295
- }
296
-
297
192
  interface HasRetryableWrites {
298
193
  retryWrites?: boolean;
299
194
  }
@@ -434,17 +329,30 @@ export function decorateWithExplain(command: Document, explain: Explain): Docume
434
329
  return { explain: command, verbosity: explain.verbosity };
435
330
  }
436
331
 
332
+ /**
333
+ * @internal
334
+ */
335
+ export type TopologyProvider =
336
+ | MongoClient
337
+ | ClientSession
338
+ | FindCursor
339
+ | AbstractCursor
340
+ | Collection<any>
341
+ | Db;
342
+
437
343
  /**
438
344
  * A helper function to get the topology from a given provider. Throws
439
345
  * if the topology cannot be found.
346
+ * @throws MongoNotConnectedError
440
347
  * @internal
441
348
  */
442
- export function getTopology<T>(provider: MongoClient | Db | Collection<T>): Topology {
349
+ export function getTopology(provider: TopologyProvider): Topology {
350
+ // MongoClient or ClientSession or AbstractCursor
443
351
  if (`topology` in provider && provider.topology) {
444
352
  return provider.topology;
445
- } else if ('client' in provider.s && provider.s.client.topology) {
353
+ } else if ('s' in provider && 'client' in provider.s && provider.s.client.topology) {
446
354
  return provider.s.client.topology;
447
- } else if ('db' in provider.s && provider.s.db.s.client.topology) {
355
+ } else if ('s' in provider && 'db' in provider.s && provider.s.db.s.client.topology) {
448
356
  return provider.s.db.s.client.topology;
449
357
  }
450
358
 
@@ -1181,7 +1089,7 @@ export function deepCopy<T>(value: T): T {
1181
1089
  case 'set':
1182
1090
  return new Set(value as any) as unknown as T;
1183
1091
  case 'buffer':
1184
- return Buffer.from(value as Buffer) as unknown as T;
1092
+ return Buffer.from(value as unknown as Buffer) as unknown as T;
1185
1093
  }
1186
1094
  }
1187
1095
 
@@ -1419,13 +1327,25 @@ export function enumToString(en: Record<string, unknown>): string {
1419
1327
  *
1420
1328
  * @internal
1421
1329
  */
1422
- export function supportsRetryableWrites(server: Server): boolean {
1423
- return (
1424
- !!server.loadBalanced ||
1425
- (server.description.maxWireVersion >= 6 &&
1426
- !!server.description.logicalSessionTimeoutMinutes &&
1427
- server.description.type !== ServerType.Standalone)
1428
- );
1330
+ export function supportsRetryableWrites(server?: Server): boolean {
1331
+ if (!server) {
1332
+ return false;
1333
+ }
1334
+
1335
+ if (server.loadBalanced) {
1336
+ // Loadbalanced topologies will always support retry writes
1337
+ return true;
1338
+ }
1339
+
1340
+ if (server.description.logicalSessionTimeoutMinutes != null) {
1341
+ // that supports sessions
1342
+ if (server.description.type !== ServerType.Standalone) {
1343
+ // and that is not a standalone
1344
+ return true;
1345
+ }
1346
+ }
1347
+
1348
+ return false;
1429
1349
  }
1430
1350
 
1431
1351
  export function parsePackageVersion({ version }: { version: string }): {
package/tsconfig.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "compilerOptions": {
3
+ "allowJs": false,
4
+ "checkJs": false,
5
+ "strict": true,
6
+ "alwaysStrict": true,
7
+ "target": "ES2019",
8
+ "module": "commonJS",
9
+ "moduleResolution": "node",
10
+ "skipLibCheck": true,
11
+ "lib": ["es2020"],
12
+ // We don't make use of tslib helpers, all syntax used is supported by target engine
13
+ "importHelpers": false,
14
+ "noEmitHelpers": true,
15
+ // Never emit error filled code
16
+ "noEmitOnError": true,
17
+ "outDir": "lib",
18
+ "importsNotUsedAsValues": "error",
19
+ // We want the sourcemaps in a separate file
20
+ "inlineSourceMap": false,
21
+ "sourceMap": true,
22
+ // API-Extractor uses declaration maps to report problems in source, no need to distribute
23
+ "declaration": true,
24
+ "declarationMap": true,
25
+ // we include sources in the release
26
+ "inlineSources": false,
27
+ // Prevents web types from being suggested by vscode.
28
+ "types": ["node"],
29
+ "forceConsistentCasingInFileNames": true,
30
+ "noImplicitOverride": true,
31
+ "noImplicitReturns": true,
32
+ // TODO(NODE-3659): Enable useUnknownInCatchVariables and add type assertions or remove unnecessary catch blocks
33
+ "useUnknownInCatchVariables": false
34
+ },
35
+ "ts-node": {
36
+ "transpileOnly": true,
37
+ "compiler": "typescript-cached-transpile"
38
+ },
39
+ "include": ["src/**/*"]
40
+ }