mongodb 4.3.1 → 4.5.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 (198) hide show
  1. package/README.md +7 -6
  2. package/lib/admin.js +5 -6
  3. package/lib/admin.js.map +1 -1
  4. package/lib/bulk/common.js +31 -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 +29 -20
  8. package/lib/change_stream.js.map +1 -1
  9. package/lib/cmap/auth/gssapi.js +49 -7
  10. package/lib/cmap/auth/gssapi.js.map +1 -1
  11. package/lib/cmap/auth/mongo_credentials.js +12 -1
  12. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  13. package/lib/cmap/auth/mongocr.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  15. package/lib/cmap/auth/plain.js.map +1 -1
  16. package/lib/cmap/auth/scram.js +1 -0
  17. package/lib/cmap/auth/scram.js.map +1 -1
  18. package/lib/cmap/auth/x509.js.map +1 -1
  19. package/lib/cmap/commands.js.map +1 -1
  20. package/lib/cmap/connect.js +0 -6
  21. package/lib/cmap/connect.js.map +1 -1
  22. package/lib/cmap/connection.js +111 -86
  23. package/lib/cmap/connection.js.map +1 -1
  24. package/lib/cmap/errors.js.map +1 -1
  25. package/lib/cmap/message_stream.js.map +1 -1
  26. package/lib/cmap/stream_description.js +3 -0
  27. package/lib/cmap/stream_description.js.map +1 -1
  28. package/lib/collection.js +29 -28
  29. package/lib/collection.js.map +1 -1
  30. package/lib/connection_string.js +53 -40
  31. package/lib/connection_string.js.map +1 -1
  32. package/lib/cursor/abstract_cursor.js +64 -42
  33. package/lib/cursor/abstract_cursor.js.map +1 -1
  34. package/lib/cursor/aggregation_cursor.js +2 -2
  35. package/lib/cursor/aggregation_cursor.js.map +1 -1
  36. package/lib/cursor/find_cursor.js +4 -3
  37. package/lib/cursor/find_cursor.js.map +1 -1
  38. package/lib/db.js +13 -13
  39. package/lib/db.js.map +1 -1
  40. package/lib/encrypter.js +17 -9
  41. package/lib/encrypter.js.map +1 -1
  42. package/lib/error.js +99 -48
  43. package/lib/error.js.map +1 -1
  44. package/lib/gridfs/download.js +2 -0
  45. package/lib/gridfs/download.js.map +1 -1
  46. package/lib/gridfs/index.js +42 -51
  47. package/lib/gridfs/index.js.map +1 -1
  48. package/lib/gridfs/upload.js.map +1 -1
  49. package/lib/index.js +9 -2
  50. package/lib/index.js.map +1 -1
  51. package/lib/mongo_client.js +14 -27
  52. package/lib/mongo_client.js.map +1 -1
  53. package/lib/operations/add_user.js +8 -1
  54. package/lib/operations/add_user.js.map +1 -1
  55. package/lib/operations/aggregate.js +5 -0
  56. package/lib/operations/aggregate.js.map +1 -1
  57. package/lib/operations/bulk_write.js.map +1 -1
  58. package/lib/operations/collections.js.map +1 -1
  59. package/lib/operations/command.js.map +1 -1
  60. package/lib/operations/common_functions.js +8 -1
  61. package/lib/operations/common_functions.js.map +1 -1
  62. package/lib/operations/count.js.map +1 -1
  63. package/lib/operations/count_documents.js.map +1 -1
  64. package/lib/operations/create_collection.js.map +1 -1
  65. package/lib/operations/delete.js +5 -3
  66. package/lib/operations/delete.js.map +1 -1
  67. package/lib/operations/distinct.js.map +1 -1
  68. package/lib/operations/drop.js.map +1 -1
  69. package/lib/operations/estimated_document_count.js.map +1 -1
  70. package/lib/operations/eval.js.map +1 -1
  71. package/lib/operations/execute_operation.js +70 -79
  72. package/lib/operations/execute_operation.js.map +1 -1
  73. package/lib/operations/find.js +3 -1
  74. package/lib/operations/find.js.map +1 -1
  75. package/lib/operations/find_and_modify.js +5 -0
  76. package/lib/operations/find_and_modify.js.map +1 -1
  77. package/lib/operations/get_more.js +5 -0
  78. package/lib/operations/get_more.js.map +1 -1
  79. package/lib/operations/indexes.js +8 -9
  80. package/lib/operations/indexes.js.map +1 -1
  81. package/lib/operations/insert.js +3 -1
  82. package/lib/operations/insert.js.map +1 -1
  83. package/lib/operations/is_capped.js.map +1 -1
  84. package/lib/operations/list_collections.js +10 -42
  85. package/lib/operations/list_collections.js.map +1 -1
  86. package/lib/operations/list_databases.js +5 -0
  87. package/lib/operations/list_databases.js.map +1 -1
  88. package/lib/operations/map_reduce.js +1 -2
  89. package/lib/operations/map_reduce.js.map +1 -1
  90. package/lib/operations/operation.js +1 -3
  91. package/lib/operations/operation.js.map +1 -1
  92. package/lib/operations/options_operation.js.map +1 -1
  93. package/lib/operations/profiling_level.js.map +1 -1
  94. package/lib/operations/remove_user.js.map +1 -1
  95. package/lib/operations/rename.js +1 -1
  96. package/lib/operations/rename.js.map +1 -1
  97. package/lib/operations/run_command.js.map +1 -1
  98. package/lib/operations/set_profiling_level.js.map +1 -1
  99. package/lib/operations/stats.js.map +1 -1
  100. package/lib/operations/update.js +5 -0
  101. package/lib/operations/update.js.map +1 -1
  102. package/lib/operations/validate_collection.js.map +1 -1
  103. package/lib/read_concern.js +1 -0
  104. package/lib/read_concern.js.map +1 -1
  105. package/lib/sdam/common.js +1 -7
  106. package/lib/sdam/common.js.map +1 -1
  107. package/lib/sdam/events.js +1 -1
  108. package/lib/sdam/events.js.map +1 -1
  109. package/lib/sdam/monitor.js +1 -2
  110. package/lib/sdam/monitor.js.map +1 -1
  111. package/lib/sdam/server.js +79 -57
  112. package/lib/sdam/server.js.map +1 -1
  113. package/lib/sdam/topology.js +16 -33
  114. package/lib/sdam/topology.js.map +1 -1
  115. package/lib/sdam/topology_description.js +1 -3
  116. package/lib/sdam/topology_description.js.map +1 -1
  117. package/lib/sessions.js +93 -68
  118. package/lib/sessions.js.map +1 -1
  119. package/lib/utils.js +21 -97
  120. package/lib/utils.js.map +1 -1
  121. package/mongodb.d.ts +188 -29
  122. package/package.json +46 -46
  123. package/src/admin.ts +6 -10
  124. package/src/bulk/common.ts +42 -14
  125. package/src/bulk/unordered.ts +1 -1
  126. package/src/change_stream.ts +58 -42
  127. package/src/cmap/auth/gssapi.ts +58 -7
  128. package/src/cmap/auth/mongo_credentials.ts +17 -2
  129. package/src/cmap/auth/mongocr.ts +1 -1
  130. package/src/cmap/auth/mongodb_aws.ts +1 -1
  131. package/src/cmap/auth/plain.ts +1 -1
  132. package/src/cmap/auth/scram.ts +3 -2
  133. package/src/cmap/auth/x509.ts +6 -2
  134. package/src/cmap/commands.ts +3 -0
  135. package/src/cmap/connect.ts +2 -20
  136. package/src/cmap/connection.ts +162 -111
  137. package/src/cmap/errors.ts +2 -2
  138. package/src/cmap/message_stream.ts +2 -2
  139. package/src/cmap/stream_description.ts +4 -1
  140. package/src/collection.ts +37 -33
  141. package/src/connection_string.ts +77 -45
  142. package/src/cursor/abstract_cursor.ts +85 -56
  143. package/src/cursor/aggregation_cursor.ts +5 -5
  144. package/src/cursor/find_cursor.ts +19 -11
  145. package/src/db.ts +15 -19
  146. package/src/deps.ts +52 -0
  147. package/src/encrypter.ts +18 -10
  148. package/src/error.ts +145 -76
  149. package/src/gridfs/download.ts +3 -1
  150. package/src/gridfs/index.ts +51 -68
  151. package/src/gridfs/upload.ts +12 -12
  152. package/src/index.ts +10 -1
  153. package/src/mongo_client.ts +19 -41
  154. package/src/operations/add_user.ts +14 -3
  155. package/src/operations/aggregate.ts +15 -5
  156. package/src/operations/bulk_write.ts +6 -2
  157. package/src/operations/collections.ts +6 -2
  158. package/src/operations/command.ts +23 -8
  159. package/src/operations/common_functions.ts +8 -1
  160. package/src/operations/count.ts +6 -2
  161. package/src/operations/count_documents.ts +5 -1
  162. package/src/operations/create_collection.ts +6 -2
  163. package/src/operations/delete.ts +19 -13
  164. package/src/operations/distinct.ts +6 -2
  165. package/src/operations/drop.ts +12 -4
  166. package/src/operations/estimated_document_count.ts +11 -3
  167. package/src/operations/eval.ts +6 -2
  168. package/src/operations/execute_operation.ts +102 -101
  169. package/src/operations/find.ts +9 -5
  170. package/src/operations/find_and_modify.ts +21 -2
  171. package/src/operations/get_more.ts +20 -6
  172. package/src/operations/indexes.ts +54 -36
  173. package/src/operations/insert.ts +20 -6
  174. package/src/operations/is_capped.ts +6 -2
  175. package/src/operations/list_collections.ts +24 -59
  176. package/src/operations/list_databases.ts +13 -3
  177. package/src/operations/map_reduce.ts +7 -6
  178. package/src/operations/operation.ts +10 -9
  179. package/src/operations/options_operation.ts +6 -2
  180. package/src/operations/profiling_level.ts +6 -2
  181. package/src/operations/remove_user.ts +6 -2
  182. package/src/operations/rename.ts +7 -3
  183. package/src/operations/run_command.ts +6 -2
  184. package/src/operations/set_profiling_level.ts +6 -2
  185. package/src/operations/stats.ts +12 -4
  186. package/src/operations/update.ts +21 -9
  187. package/src/operations/validate_collection.ts +6 -2
  188. package/src/read_concern.ts +1 -0
  189. package/src/sdam/common.ts +0 -6
  190. package/src/sdam/events.ts +2 -2
  191. package/src/sdam/monitor.ts +4 -5
  192. package/src/sdam/server.ts +95 -90
  193. package/src/sdam/topology.ts +9 -53
  194. package/src/sdam/topology_description.ts +1 -3
  195. package/src/sessions.ts +108 -78
  196. package/src/utils.ts +38 -118
  197. package/tsconfig.json +40 -0
  198. package/mongodb.ts34.d.ts +0 -5649
@@ -50,7 +50,6 @@ import {
50
50
  } from '../utils';
51
51
  import {
52
52
  _advanceClusterTime,
53
- clearAndRemoveTimerFrom,
54
53
  ClusterTime,
55
54
  drainTimerQueue,
56
55
  ServerType,
@@ -435,7 +434,12 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
435
434
 
436
435
  // connect all known servers, then attempt server selection to connect
437
436
  const serverDescriptions = Array.from(this.s.description.servers.values());
438
- connectServers(this, serverDescriptions);
437
+ this.s.servers = new Map(
438
+ serverDescriptions.map(serverDescription => [
439
+ serverDescription.address,
440
+ createAndConnectServer(this, serverDescription)
441
+ ])
442
+ );
439
443
 
440
444
  // In load balancer mode we need to fake a server description getting
441
445
  // emitted from the monitor, since the monitor doesn't exist.
@@ -459,7 +463,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
459
463
 
460
464
  // TODO: NODE-2471
461
465
  if (server && this.s.credentials) {
462
- server.command(ns('admin.$cmd'), { ping: 1 }, err => {
466
+ server.command(ns('admin.$cmd'), { ping: 1 }, {}, err => {
463
467
  if (err) {
464
468
  typeof callback === 'function' ? callback(err) : this.emit(Topology.ERROR, err);
465
469
  return;
@@ -549,27 +553,11 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
549
553
  * @param callback - The callback used to indicate success or failure
550
554
  * @returns An instance of a `Server` meeting the criteria of the predicate provided
551
555
  */
552
- selectServer(options: SelectServerOptions, callback: Callback<Server>): void;
553
- selectServer(
554
- selector: string | ReadPreference | ServerSelector,
555
- callback: Callback<Server>
556
- ): void;
557
556
  selectServer(
558
557
  selector: string | ReadPreference | ServerSelector,
559
558
  options: SelectServerOptions,
560
559
  callback: Callback<Server>
561
- ): void;
562
- selectServer(
563
- selector: string | ReadPreference | ServerSelector | SelectServerOptions,
564
- _options?: SelectServerOptions | Callback<Server>,
565
- _callback?: Callback<Server>
566
560
  ): void {
567
- let options = _options as SelectServerOptions;
568
- const callback = (_callback ?? _options) as Callback<Server>;
569
- if (typeof options === 'function') {
570
- options = {};
571
- }
572
-
573
561
  let serverSelector;
574
562
  if (typeof selector !== 'function') {
575
563
  if (typeof selector === 'string') {
@@ -667,6 +655,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
667
655
 
668
656
  this.selectServer(
669
657
  readPreferenceServerSelector(ReadPreference.primaryPreferred),
658
+ {},
670
659
  (err, server) => {
671
660
  if (err || !server) {
672
661
  if (typeof callback === 'function') callback(err);
@@ -875,13 +864,8 @@ function randomSelection(array: ServerDescription[]): ServerDescription {
875
864
  *
876
865
  * @param topology - The topology that this server belongs to
877
866
  * @param serverDescription - The description for the server to initialize and connect to
878
- * @param connectDelay - Time to wait before attempting initial connection
879
867
  */
880
- function createAndConnectServer(
881
- topology: Topology,
882
- serverDescription: ServerDescription,
883
- connectDelay?: number
884
- ) {
868
+ function createAndConnectServer(topology: Topology, serverDescription: ServerDescription) {
885
869
  topology.emit(
886
870
  Topology.SERVER_OPENING,
887
871
  new ServerOpeningEvent(topology.s.id, serverDescription.address)
@@ -894,38 +878,10 @@ function createAndConnectServer(
894
878
 
895
879
  server.on(Server.DESCRIPTION_RECEIVED, description => topology.serverUpdateHandler(description));
896
880
 
897
- if (connectDelay) {
898
- const connectTimer = setTimeout(() => {
899
- clearAndRemoveTimerFrom(connectTimer, topology.s.connectionTimers);
900
- server.connect();
901
- }, connectDelay);
902
-
903
- topology.s.connectionTimers.add(connectTimer);
904
- return server;
905
- }
906
-
907
881
  server.connect();
908
882
  return server;
909
883
  }
910
884
 
911
- /**
912
- * Create `Server` instances for all initially known servers, connect them, and assign
913
- * them to the passed in `Topology`.
914
- *
915
- * @param topology - The topology responsible for the servers
916
- * @param serverDescriptions - A list of server descriptions to connect
917
- */
918
- function connectServers(topology: Topology, serverDescriptions: ServerDescription[]) {
919
- topology.s.servers = serverDescriptions.reduce(
920
- (servers: Map<string, Server>, serverDescription: ServerDescription) => {
921
- const server = createAndConnectServer(topology, serverDescription);
922
- servers.set(serverDescription.address, server);
923
- return servers;
924
- },
925
- new Map<string, Server>()
926
- );
927
- }
928
-
929
885
  /**
930
886
  * @param topology - Topology to update.
931
887
  * @param incomingServerDescription - New server description.
@@ -58,9 +58,6 @@ export class TopologyDescription {
58
58
  ) {
59
59
  options = options ?? {};
60
60
 
61
- // TODO: consider assigning all these values to a temporary value `s` which
62
- // we use `Object.freeze` on, ensuring the internal state of this type
63
- // is immutable.
64
61
  this.type = topologyType ?? TopologyType.Unknown;
65
62
  this.servers = serverDescriptions ?? new Map();
66
63
  this.stale = false;
@@ -321,6 +318,7 @@ export class TopologyDescription {
321
318
  if (descriptionsWithError.length > 0) {
322
319
  return descriptionsWithError[0].error;
323
320
  }
321
+ return;
324
322
  }
325
323
 
326
324
  /**
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 {