mongodb 4.0.0 → 4.1.2

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 (246) hide show
  1. package/README.md +62 -30
  2. package/lib/bson.js +1 -0
  3. package/lib/bson.js.map +1 -1
  4. package/lib/bulk/common.js +53 -30
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/bulk/ordered.js +3 -2
  7. package/lib/bulk/ordered.js.map +1 -1
  8. package/lib/bulk/unordered.js +3 -2
  9. package/lib/bulk/unordered.js.map +1 -1
  10. package/lib/change_stream.js +23 -13
  11. package/lib/change_stream.js.map +1 -1
  12. package/lib/cmap/auth/auth_provider.js +2 -1
  13. package/lib/cmap/auth/auth_provider.js.map +1 -1
  14. package/lib/cmap/auth/gssapi.js +5 -4
  15. package/lib/cmap/auth/gssapi.js.map +1 -1
  16. package/lib/cmap/auth/mongo_credentials.js +9 -5
  17. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  18. package/lib/cmap/auth/mongocr.js +2 -2
  19. package/lib/cmap/auth/mongocr.js.map +1 -1
  20. package/lib/cmap/auth/mongodb_aws.js +32 -32
  21. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  22. package/lib/cmap/auth/plain.js +1 -1
  23. package/lib/cmap/auth/plain.js.map +1 -1
  24. package/lib/cmap/auth/scram.js +15 -12
  25. package/lib/cmap/auth/scram.js.map +1 -1
  26. package/lib/cmap/auth/x509.js +2 -2
  27. package/lib/cmap/auth/x509.js.map +1 -1
  28. package/lib/cmap/command_monitoring_events.js +26 -10
  29. package/lib/cmap/command_monitoring_events.js.map +1 -1
  30. package/lib/cmap/commands.js +9 -5
  31. package/lib/cmap/commands.js.map +1 -1
  32. package/lib/cmap/connect.js +23 -9
  33. package/lib/cmap/connect.js.map +1 -1
  34. package/lib/cmap/connection.js +43 -46
  35. package/lib/cmap/connection.js.map +1 -1
  36. package/lib/cmap/connection_pool.js +113 -15
  37. package/lib/cmap/connection_pool.js.map +1 -1
  38. package/lib/cmap/connection_pool_events.js +3 -1
  39. package/lib/cmap/connection_pool_events.js.map +1 -1
  40. package/lib/cmap/errors.js +3 -3
  41. package/lib/cmap/errors.js.map +1 -1
  42. package/lib/cmap/message_stream.js +1 -1
  43. package/lib/cmap/message_stream.js.map +1 -1
  44. package/lib/cmap/metrics.js +62 -0
  45. package/lib/cmap/metrics.js.map +1 -0
  46. package/lib/cmap/stream_description.js +3 -1
  47. package/lib/cmap/stream_description.js.map +1 -1
  48. package/lib/cmap/wire_protocol/compression.js +22 -9
  49. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  50. package/lib/cmap/wire_protocol/shared.js +1 -1
  51. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  52. package/lib/collection.js +23 -18
  53. package/lib/collection.js.map +1 -1
  54. package/lib/connection_string.js +76 -30
  55. package/lib/connection_string.js.map +1 -1
  56. package/lib/cursor/abstract_cursor.js +75 -68
  57. package/lib/cursor/abstract_cursor.js.map +1 -1
  58. package/lib/cursor/aggregation_cursor.js +47 -9
  59. package/lib/cursor/aggregation_cursor.js.map +1 -1
  60. package/lib/cursor/find_cursor.js +53 -13
  61. package/lib/cursor/find_cursor.js.map +1 -1
  62. package/lib/db.js +21 -14
  63. package/lib/db.js.map +1 -1
  64. package/lib/deps.js +16 -5
  65. package/lib/deps.js.map +1 -1
  66. package/lib/encrypter.js +5 -8
  67. package/lib/encrypter.js.map +1 -1
  68. package/lib/error.js +230 -34
  69. package/lib/error.js.map +1 -1
  70. package/lib/explain.js +2 -2
  71. package/lib/explain.js.map +1 -1
  72. package/lib/gridfs/download.js +22 -47
  73. package/lib/gridfs/download.js.map +1 -1
  74. package/lib/gridfs/index.js +4 -3
  75. package/lib/gridfs/index.js.map +1 -1
  76. package/lib/gridfs/upload.js +13 -21
  77. package/lib/gridfs/upload.js.map +1 -1
  78. package/lib/index.js +27 -2
  79. package/lib/index.js.map +1 -1
  80. package/lib/logger.js +3 -2
  81. package/lib/logger.js.map +1 -1
  82. package/lib/mongo_client.js +5 -8
  83. package/lib/mongo_client.js.map +1 -1
  84. package/lib/mongo_types.js.map +1 -1
  85. package/lib/operations/add_user.js +2 -3
  86. package/lib/operations/add_user.js.map +1 -1
  87. package/lib/operations/aggregate.js +12 -9
  88. package/lib/operations/aggregate.js.map +1 -1
  89. package/lib/operations/command.js +5 -7
  90. package/lib/operations/command.js.map +1 -1
  91. package/lib/operations/common_functions.js +1 -1
  92. package/lib/operations/common_functions.js.map +1 -1
  93. package/lib/operations/connect.js +3 -2
  94. package/lib/operations/connect.js.map +1 -1
  95. package/lib/operations/count.js +1 -1
  96. package/lib/operations/count.js.map +1 -1
  97. package/lib/operations/count_documents.js +1 -1
  98. package/lib/operations/count_documents.js.map +1 -1
  99. package/lib/operations/delete.js +5 -5
  100. package/lib/operations/delete.js.map +1 -1
  101. package/lib/operations/distinct.js +2 -2
  102. package/lib/operations/distinct.js.map +1 -1
  103. package/lib/operations/estimated_document_count.js +5 -1
  104. package/lib/operations/estimated_document_count.js.map +1 -1
  105. package/lib/operations/eval.js.map +1 -1
  106. package/lib/operations/execute_operation.js +31 -17
  107. package/lib/operations/execute_operation.js.map +1 -1
  108. package/lib/operations/find.js +13 -9
  109. package/lib/operations/find.js.map +1 -1
  110. package/lib/operations/find_and_modify.js +9 -9
  111. package/lib/operations/find_and_modify.js.map +1 -1
  112. package/lib/operations/indexes.js +8 -3
  113. package/lib/operations/indexes.js.map +1 -1
  114. package/lib/operations/insert.js +5 -3
  115. package/lib/operations/insert.js.map +1 -1
  116. package/lib/operations/is_capped.js +2 -1
  117. package/lib/operations/is_capped.js.map +1 -1
  118. package/lib/operations/list_collections.js +6 -3
  119. package/lib/operations/list_collections.js.map +1 -1
  120. package/lib/operations/map_reduce.js +1 -1
  121. package/lib/operations/map_reduce.js.map +1 -1
  122. package/lib/operations/operation.js +3 -1
  123. package/lib/operations/operation.js.map +1 -1
  124. package/lib/operations/options_operation.js +2 -1
  125. package/lib/operations/options_operation.js.map +1 -1
  126. package/lib/operations/profiling_level.js +4 -2
  127. package/lib/operations/profiling_level.js.map +1 -1
  128. package/lib/operations/set_profiling_level.js +4 -2
  129. package/lib/operations/set_profiling_level.js.map +1 -1
  130. package/lib/operations/update.js +12 -12
  131. package/lib/operations/update.js.map +1 -1
  132. package/lib/operations/validate_collection.js +6 -5
  133. package/lib/operations/validate_collection.js.map +1 -1
  134. package/lib/promise_provider.js +1 -1
  135. package/lib/promise_provider.js.map +1 -1
  136. package/lib/read_preference.js +8 -8
  137. package/lib/read_preference.js.map +1 -1
  138. package/lib/sdam/common.js +12 -10
  139. package/lib/sdam/common.js.map +1 -1
  140. package/lib/sdam/server.js +90 -25
  141. package/lib/sdam/server.js.map +1 -1
  142. package/lib/sdam/server_description.js +9 -4
  143. package/lib/sdam/server_description.js.map +1 -1
  144. package/lib/sdam/server_selection.js +10 -4
  145. package/lib/sdam/server_selection.js.map +1 -1
  146. package/lib/sdam/srv_polling.js +1 -1
  147. package/lib/sdam/srv_polling.js.map +1 -1
  148. package/lib/sdam/topology.js +42 -21
  149. package/lib/sdam/topology.js.map +1 -1
  150. package/lib/sdam/topology_description.js +7 -3
  151. package/lib/sdam/topology_description.js.map +1 -1
  152. package/lib/sessions.js +132 -31
  153. package/lib/sessions.js.map +1 -1
  154. package/lib/sort.js +3 -3
  155. package/lib/sort.js.map +1 -1
  156. package/lib/transactions.js +15 -7
  157. package/lib/transactions.js.map +1 -1
  158. package/lib/utils.js +60 -20
  159. package/lib/utils.js.map +1 -1
  160. package/mongodb.d.ts +523 -138
  161. package/mongodb.ts34.d.ts +480 -141
  162. package/package.json +44 -48
  163. package/src/bson.ts +1 -0
  164. package/src/bulk/common.ts +83 -43
  165. package/src/bulk/ordered.ts +4 -3
  166. package/src/bulk/unordered.ts +4 -3
  167. package/src/change_stream.ts +46 -29
  168. package/src/cmap/auth/auth_provider.ts +3 -2
  169. package/src/cmap/auth/gssapi.ts +15 -5
  170. package/src/cmap/auth/mongo_credentials.ts +22 -8
  171. package/src/cmap/auth/mongocr.ts +3 -3
  172. package/src/cmap/auth/mongodb_aws.ts +52 -39
  173. package/src/cmap/auth/plain.ts +2 -2
  174. package/src/cmap/auth/scram.ts +23 -13
  175. package/src/cmap/auth/x509.ts +3 -3
  176. package/src/cmap/command_monitoring_events.ts +36 -14
  177. package/src/cmap/commands.ts +12 -6
  178. package/src/cmap/connect.ts +42 -12
  179. package/src/cmap/connection.ts +54 -62
  180. package/src/cmap/connection_pool.ts +141 -20
  181. package/src/cmap/connection_pool_events.ts +8 -1
  182. package/src/cmap/errors.ts +3 -4
  183. package/src/cmap/message_stream.ts +2 -4
  184. package/src/cmap/metrics.ts +58 -0
  185. package/src/cmap/stream_description.ts +6 -1
  186. package/src/cmap/wire_protocol/compression.ts +26 -13
  187. package/src/cmap/wire_protocol/shared.ts +4 -2
  188. package/src/collection.ts +75 -70
  189. package/src/connection_string.ts +97 -34
  190. package/src/cursor/abstract_cursor.ts +141 -104
  191. package/src/cursor/aggregation_cursor.ts +34 -20
  192. package/src/cursor/find_cursor.ts +41 -21
  193. package/src/db.ts +19 -18
  194. package/src/deps.ts +110 -22
  195. package/src/encrypter.ts +6 -12
  196. package/src/error.ts +264 -48
  197. package/src/explain.ts +3 -3
  198. package/src/gridfs/download.ts +48 -53
  199. package/src/gridfs/index.ts +5 -4
  200. package/src/gridfs/upload.ts +32 -33
  201. package/src/index.ts +42 -4
  202. package/src/logger.ts +6 -3
  203. package/src/mongo_client.ts +20 -23
  204. package/src/mongo_types.ts +19 -20
  205. package/src/operations/add_user.ts +4 -5
  206. package/src/operations/aggregate.ts +18 -17
  207. package/src/operations/command.ts +7 -10
  208. package/src/operations/common_functions.ts +2 -3
  209. package/src/operations/connect.ts +4 -3
  210. package/src/operations/count.ts +2 -2
  211. package/src/operations/count_documents.ts +2 -2
  212. package/src/operations/delete.ts +8 -6
  213. package/src/operations/distinct.ts +5 -3
  214. package/src/operations/estimated_document_count.ts +5 -1
  215. package/src/operations/eval.ts +1 -1
  216. package/src/operations/execute_operation.ts +41 -20
  217. package/src/operations/find.ts +25 -16
  218. package/src/operations/find_and_modify.ts +12 -10
  219. package/src/operations/indexes.ts +39 -8
  220. package/src/operations/insert.ts +7 -4
  221. package/src/operations/is_capped.ts +3 -2
  222. package/src/operations/list_collections.ts +9 -6
  223. package/src/operations/map_reduce.ts +4 -2
  224. package/src/operations/operation.ts +7 -2
  225. package/src/operations/options_operation.ts +3 -2
  226. package/src/operations/profiling_level.ts +5 -3
  227. package/src/operations/set_profiling_level.ts +9 -3
  228. package/src/operations/update.ts +17 -13
  229. package/src/operations/validate_collection.ts +7 -6
  230. package/src/promise_provider.ts +2 -2
  231. package/src/read_preference.ts +11 -9
  232. package/src/sdam/common.ts +11 -9
  233. package/src/sdam/server.ts +168 -69
  234. package/src/sdam/server_description.ts +16 -4
  235. package/src/sdam/server_selection.ts +15 -7
  236. package/src/sdam/srv_polling.ts +2 -2
  237. package/src/sdam/topology.ts +67 -36
  238. package/src/sdam/topology_description.ts +11 -4
  239. package/src/sessions.ts +194 -37
  240. package/src/sort.ts +6 -4
  241. package/src/transactions.ts +18 -9
  242. package/src/utils.ts +73 -20
  243. package/HISTORY.md +0 -2993
  244. package/lib/operations/find_one.js +0 -34
  245. package/lib/operations/find_one.js.map +0 -1
  246. package/src/operations/find_one.ts +0 -43
package/src/sessions.ts CHANGED
@@ -2,16 +2,23 @@ import { PromiseProvider } from './promise_provider';
2
2
  import { Binary, Long, Timestamp, Document } from './bson';
3
3
  import { ReadPreference } from './read_preference';
4
4
  import { isTransactionCommand, TxnState, Transaction, TransactionOptions } from './transactions';
5
- import { resolveClusterTime, ClusterTime } from './sdam/common';
5
+ import { _advanceClusterTime, ClusterTime, TopologyType } from './sdam/common';
6
6
  import { isSharded } from './cmap/wire_protocol/shared';
7
7
  import {
8
8
  MongoError,
9
+ MongoInvalidArgumentError,
9
10
  isRetryableError,
11
+ MongoCompatibilityError,
10
12
  MongoNetworkError,
11
13
  MongoWriteConcernError,
12
14
  MONGODB_ERROR_CODES,
15
+ MongoServerError,
13
16
  MongoDriverError,
14
- MongoServerError
17
+ MongoAPIError,
18
+ AnyError,
19
+ MongoExpiredSessionError,
20
+ MongoTransactionError,
21
+ MongoRuntimeError
15
22
  } from './error';
16
23
  import {
17
24
  now,
@@ -28,6 +35,8 @@ import { executeOperation } from './operations/execute_operation';
28
35
  import { RunAdminCommandOperation } from './operations/run_command';
29
36
  import type { AbstractCursor } from './cursor/abstract_cursor';
30
37
  import type { CommandOptions } from './cmap/connection';
38
+ import { Connection } from './cmap/connection';
39
+ import { ConnectionPoolMetrics } from './cmap/metrics';
31
40
  import type { WriteConcern } from './write_concern';
32
41
  import { TypedEventEmitter } from './mongo_types';
33
42
  import { ReadConcernLevel } from './read_concern';
@@ -36,7 +45,7 @@ const minWireVersionForShardedTransactions = 8;
36
45
 
37
46
  function assertAlive(session: ClientSession, callback?: Callback): boolean {
38
47
  if (session.serverSession == null) {
39
- const error = new MongoDriverError('Cannot use a session that has ended');
48
+ const error = new MongoExpiredSessionError();
40
49
  if (typeof callback === 'function') {
41
50
  callback(error);
42
51
  return false;
@@ -79,6 +88,19 @@ const kServerSession = Symbol('serverSession');
79
88
  const kSnapshotTime = Symbol('snapshotTime');
80
89
  /** @internal */
81
90
  const kSnapshotEnabled = Symbol('snapshotEnabled');
91
+ /** @internal */
92
+ const kPinnedConnection = Symbol('pinnedConnection');
93
+
94
+ /** @public */
95
+ export interface EndSessionOptions {
96
+ /**
97
+ * An optional error which caused the call to end this session
98
+ * @internal
99
+ */
100
+ error?: AnyError;
101
+ force?: boolean;
102
+ forceClear?: boolean;
103
+ }
82
104
 
83
105
  /**
84
106
  * A class representing a client session on the server
@@ -107,6 +129,8 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
107
129
  [kSnapshotTime]?: Timestamp;
108
130
  /** @internal */
109
131
  [kSnapshotEnabled] = false;
132
+ /** @internal */
133
+ [kPinnedConnection]?: Connection;
110
134
 
111
135
  /**
112
136
  * Create a client session.
@@ -125,11 +149,13 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
125
149
  super();
126
150
 
127
151
  if (topology == null) {
128
- throw new MongoDriverError('ClientSession requires a topology');
152
+ // TODO(NODE-3483)
153
+ throw new MongoRuntimeError('ClientSession requires a topology');
129
154
  }
130
155
 
131
156
  if (sessionPool == null || !(sessionPool instanceof ServerSessionPool)) {
132
- throw new MongoDriverError('ClientSession requires a ServerSessionPool');
157
+ // TODO(NODE-3483)
158
+ throw new MongoRuntimeError('ClientSession requires a ServerSessionPool');
133
159
  }
134
160
 
135
161
  options = options ?? {};
@@ -137,7 +163,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
137
163
  if (options.snapshot === true) {
138
164
  this[kSnapshotEnabled] = true;
139
165
  if (options.causalConsistency === true) {
140
- throw new MongoDriverError(
166
+ throw new MongoInvalidArgumentError(
141
167
  'Properties "causalConsistency" and "snapshot" are mutually exclusive'
142
168
  );
143
169
  }
@@ -181,6 +207,41 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
181
207
  return this[kSnapshotEnabled];
182
208
  }
183
209
 
210
+ get loadBalanced(): boolean {
211
+ return this.topology.description.type === TopologyType.LoadBalanced;
212
+ }
213
+
214
+ /** @internal */
215
+ get pinnedConnection(): Connection | undefined {
216
+ return this[kPinnedConnection];
217
+ }
218
+
219
+ /** @internal */
220
+ pin(conn: Connection): void {
221
+ if (this[kPinnedConnection]) {
222
+ throw TypeError('Cannot pin multiple connections to the same session');
223
+ }
224
+
225
+ this[kPinnedConnection] = conn;
226
+ conn.emit(
227
+ Connection.PINNED,
228
+ this.inTransaction() ? ConnectionPoolMetrics.TXN : ConnectionPoolMetrics.CURSOR
229
+ );
230
+ }
231
+
232
+ /** @internal */
233
+ unpin(options?: { force?: boolean; forceClear?: boolean; error?: AnyError }): void {
234
+ if (this.loadBalanced) {
235
+ return maybeClearPinnedConnection(this, options);
236
+ }
237
+
238
+ this.transaction.unpinServer();
239
+ }
240
+
241
+ get isPinned(): boolean {
242
+ return this.loadBalanced ? !!this[kPinnedConnection] : this.transaction.isPinned;
243
+ }
244
+
184
245
  /**
185
246
  * Ends this session on the server
186
247
  *
@@ -189,21 +250,24 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
189
250
  */
190
251
  endSession(): Promise<void>;
191
252
  endSession(callback: Callback<void>): void;
192
- endSession(options: Record<string, unknown>): Promise<void>;
193
- endSession(options: Record<string, unknown>, callback: Callback<void>): void;
253
+ endSession(options: EndSessionOptions): Promise<void>;
254
+ endSession(options: EndSessionOptions, callback: Callback<void>): void;
194
255
  endSession(
195
- options?: Record<string, unknown> | Callback<void>,
256
+ options?: EndSessionOptions | Callback<void>,
196
257
  callback?: Callback<void>
197
258
  ): void | Promise<void> {
198
259
  if (typeof options === 'function') (callback = options), (options = {});
199
- options = options ?? {};
260
+ const finalOptions = { force: true, ...options };
200
261
 
201
262
  return maybePromise(callback, done => {
202
263
  if (this.hasEnded) {
264
+ maybeClearPinnedConnection(this, finalOptions);
203
265
  return done();
204
266
  }
205
267
 
206
268
  const completeEndSession = () => {
269
+ maybeClearPinnedConnection(this, finalOptions);
270
+
207
271
  // release the server session back to the pool
208
272
  this.sessionPool.release(this.serverSession);
209
273
  this[kServerSession] = undefined;
@@ -245,6 +309,34 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
245
309
  }
246
310
  }
247
311
 
312
+ /**
313
+ * Advances the clusterTime for a ClientSession to the provided clusterTime of another ClientSession
314
+ *
315
+ * @param clusterTime - the $clusterTime returned by the server from another session in the form of a document containing the `BSON.Timestamp` clusterTime and signature
316
+ */
317
+ advanceClusterTime(clusterTime: ClusterTime): void {
318
+ if (!clusterTime || typeof clusterTime !== 'object') {
319
+ throw new MongoInvalidArgumentError('input cluster time must be an object');
320
+ }
321
+ if (!clusterTime.clusterTime || clusterTime.clusterTime._bsontype !== 'Timestamp') {
322
+ throw new MongoInvalidArgumentError(
323
+ 'input cluster time "clusterTime" property must be a valid BSON Timestamp'
324
+ );
325
+ }
326
+ if (
327
+ !clusterTime.signature ||
328
+ clusterTime.signature.hash?._bsontype !== 'Binary' ||
329
+ (typeof clusterTime.signature.keyId !== 'number' &&
330
+ clusterTime.signature.keyId?._bsontype !== 'Long') // apparently we decode the key to number?
331
+ ) {
332
+ throw new MongoInvalidArgumentError(
333
+ 'input cluster time must have a valid "signature" property with BSON Binary hash and BSON Long keyId'
334
+ );
335
+ }
336
+
337
+ _advanceClusterTime(this, clusterTime);
338
+ }
339
+
248
340
  /**
249
341
  * Used to determine if this session equals another
250
342
  *
@@ -282,12 +374,16 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
282
374
  */
283
375
  startTransaction(options?: TransactionOptions): void {
284
376
  if (this[kSnapshotEnabled]) {
285
- throw new MongoDriverError('Transactions are not allowed with snapshot sessions');
377
+ throw new MongoCompatibilityError('Transactions are not allowed with snapshot sessions');
286
378
  }
287
379
 
288
380
  assertAlive(this);
289
381
  if (this.inTransaction()) {
290
- throw new MongoDriverError('Transaction already in progress');
382
+ throw new MongoTransactionError('Transaction already in progress');
383
+ }
384
+
385
+ if (this.isPinned && this.transaction.isCommitted) {
386
+ this.unpin();
291
387
  }
292
388
 
293
389
  const topologyMaxWireVersion = maxWireVersion(this.topology);
@@ -296,7 +392,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
296
392
  topologyMaxWireVersion != null &&
297
393
  topologyMaxWireVersion < minWireVersionForShardedTransactions
298
394
  ) {
299
- throw new MongoDriverError(
395
+ throw new MongoCompatibilityError(
300
396
  'Transactions are not supported on sharded clusters in MongoDB < 4.2.'
301
397
  );
302
398
  }
@@ -349,7 +445,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
349
445
  * This is here to ensure that ClientSession is never serialized to BSON.
350
446
  */
351
447
  toBSON(): never {
352
- throw new MongoDriverError('ClientSession cannot be serialized to BSON.');
448
+ throw new MongoRuntimeError('ClientSession cannot be serialized to BSON.');
353
449
  }
354
450
 
355
451
  /**
@@ -397,6 +493,47 @@ function isUnknownTransactionCommitResult(err: MongoError) {
397
493
  );
398
494
  }
399
495
 
496
+ export function maybeClearPinnedConnection(
497
+ session: ClientSession,
498
+ options?: EndSessionOptions
499
+ ): void {
500
+ // unpin a connection if it has been pinned
501
+ const conn = session[kPinnedConnection];
502
+ const error = options?.error;
503
+
504
+ if (
505
+ session.inTransaction() &&
506
+ error &&
507
+ error instanceof MongoError &&
508
+ error.hasErrorLabel('TransientTransactionError')
509
+ ) {
510
+ return;
511
+ }
512
+
513
+ // NOTE: the spec talks about what to do on a network error only, but the tests seem to
514
+ // to validate that we don't unpin on _all_ errors?
515
+ if (conn) {
516
+ const servers = Array.from(session.topology.s.servers.values());
517
+ const loadBalancer = servers[0];
518
+
519
+ if (options?.error == null || options?.force) {
520
+ loadBalancer.s.pool.checkIn(conn);
521
+ conn.emit(
522
+ Connection.UNPINNED,
523
+ session.transaction.state !== TxnState.NO_TRANSACTION
524
+ ? ConnectionPoolMetrics.TXN
525
+ : ConnectionPoolMetrics.CURSOR
526
+ );
527
+
528
+ if (options?.forceClear) {
529
+ loadBalancer.s.pool.clear(conn.serviceId);
530
+ }
531
+ }
532
+
533
+ session[kPinnedConnection] = undefined;
534
+ }
535
+ }
536
+
400
537
  function isMaxTimeMSExpiredError(err: MongoError) {
401
538
  if (err == null || !(err instanceof MongoServerError)) {
402
539
  return false;
@@ -461,7 +598,9 @@ function attemptTransaction<TSchema>(
461
598
 
462
599
  if (!isPromiseLike(promise)) {
463
600
  session.abortTransaction();
464
- throw new MongoDriverError('Function provided to `withTransaction` must return a Promise');
601
+ throw new MongoInvalidArgumentError(
602
+ 'Function provided to `withTransaction` must return a Promise'
603
+ );
465
604
  }
466
605
 
467
606
  return promise.then(
@@ -508,7 +647,7 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
508
647
  const txnState = session.transaction.state;
509
648
 
510
649
  if (txnState === TxnState.NO_TRANSACTION) {
511
- callback(new MongoDriverError('No transaction started'));
650
+ callback(new MongoTransactionError('No transaction started'));
512
651
  return;
513
652
  }
514
653
 
@@ -525,7 +664,7 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
525
664
 
526
665
  if (txnState === TxnState.TRANSACTION_ABORTED) {
527
666
  callback(
528
- new MongoDriverError('Cannot call commitTransaction after calling abortTransaction')
667
+ new MongoTransactionError('Cannot call commitTransaction after calling abortTransaction')
529
668
  );
530
669
  return;
531
670
  }
@@ -538,7 +677,7 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
538
677
  }
539
678
 
540
679
  if (txnState === TxnState.TRANSACTION_ABORTED) {
541
- callback(new MongoDriverError('Cannot call abortTransaction twice'));
680
+ callback(new MongoTransactionError('Cannot call abortTransaction twice'));
542
681
  return;
543
682
  }
544
683
 
@@ -547,7 +686,7 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
547
686
  txnState === TxnState.TRANSACTION_COMMITTED_EMPTY
548
687
  ) {
549
688
  callback(
550
- new MongoDriverError('Cannot call abortTransaction after calling commitTransaction')
689
+ new MongoTransactionError('Cannot call abortTransaction after calling commitTransaction')
551
690
  );
552
691
  return;
553
692
  }
@@ -579,6 +718,10 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
579
718
  function commandHandler(e?: MongoError, r?: Document) {
580
719
  if (commandName !== 'commitTransaction') {
581
720
  session.transaction.transition(TxnState.TRANSACTION_ABORTED);
721
+ if (session.loadBalanced) {
722
+ maybeClearPinnedConnection(session, { force: false });
723
+ }
724
+
582
725
  // The spec indicates that we should ignore all errors on `abortTransaction`
583
726
  return callback();
584
727
  }
@@ -595,12 +738,13 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
595
738
  e.addErrorLabel('UnknownTransactionCommitResult');
596
739
 
597
740
  // per txns spec, must unpin session in this case
598
- session.transaction.unpinServer();
741
+ session.unpin({ error: e });
599
742
  }
600
743
  } else if (e.hasErrorLabel('TransientTransactionError')) {
601
- session.transaction.unpinServer();
744
+ session.unpin({ error: e });
602
745
  }
603
746
  }
747
+
604
748
  callback(e, r);
605
749
  }
606
750
 
@@ -614,14 +758,20 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
614
758
  session.topology,
615
759
  new RunAdminCommandOperation(undefined, command, {
616
760
  session,
617
- readPreference: ReadPreference.primary
761
+ readPreference: ReadPreference.primary,
762
+ bypassPinningCheck: true
618
763
  }),
619
764
  (err, reply) => {
765
+ if (command.abortTransaction) {
766
+ // always unpin on abort regardless of command outcome
767
+ session.unpin();
768
+ }
769
+
620
770
  if (err && isRetryableError(err as MongoError)) {
621
771
  // SPEC-1185: apply majority write concern when retrying commitTransaction
622
772
  if (command.commitTransaction) {
623
773
  // per txns spec, must unpin session in this case
624
- session.transaction.unpinServer();
774
+ session.unpin({ force: true });
625
775
 
626
776
  command.writeConcern = Object.assign({ wtimeout: 10000 }, command.writeConcern, {
627
777
  w: 'majority'
@@ -632,7 +782,8 @@ function endTransaction(session: ClientSession, commandName: string, callback: C
632
782
  session.topology,
633
783
  new RunAdminCommandOperation(undefined, command, {
634
784
  session,
635
- readPreference: ReadPreference.primary
785
+ readPreference: ReadPreference.primary,
786
+ bypassPinningCheck: true
636
787
  }),
637
788
  (_err, _reply) => commandHandler(_err as MongoError, _reply)
638
789
  );
@@ -692,7 +843,7 @@ export class ServerSessionPool {
692
843
 
693
844
  constructor(topology: Topology) {
694
845
  if (topology == null) {
695
- throw new MongoDriverError('ServerSessionPool requires a topology');
846
+ throw new MongoRuntimeError('ServerSessionPool requires a topology');
696
847
  }
697
848
 
698
849
  this.topology = topology;
@@ -731,7 +882,7 @@ export class ServerSessionPool {
731
882
 
732
883
  while (this.sessions.length) {
733
884
  const session = this.sessions.shift();
734
- if (session && !session.hasTimedOut(sessionTimeoutMinutes)) {
885
+ if (session && (this.topology.loadBalanced || !session.hasTimedOut(sessionTimeoutMinutes))) {
735
886
  return session;
736
887
  }
737
888
  }
@@ -748,6 +899,11 @@ export class ServerSessionPool {
748
899
  */
749
900
  release(session: ServerSession): void {
750
901
  const sessionTimeoutMinutes = this.topology.logicalSessionTimeoutMinutes;
902
+
903
+ if (this.topology.loadBalanced && !sessionTimeoutMinutes) {
904
+ this.sessions.unshift(session);
905
+ }
906
+
751
907
  if (!sessionTimeoutMinutes) {
752
908
  return;
753
909
  }
@@ -805,19 +961,19 @@ export function applySession(
805
961
  ): MongoDriverError | undefined {
806
962
  // TODO: merge this with `assertAlive`, did not want to throw a try/catch here
807
963
  if (session.hasEnded) {
808
- return new MongoDriverError('Attempted to use a session that has ended');
964
+ return new MongoExpiredSessionError();
809
965
  }
810
966
 
811
967
  const serverSession = session.serverSession;
812
968
  if (serverSession == null) {
813
- return new MongoDriverError('Unable to acquire server session');
969
+ return new MongoRuntimeError('Unable to acquire server session');
814
970
  }
815
971
 
816
972
  // SPEC-1019: silently ignore explicit session with unacknowledged write for backwards compatibility
817
973
  // FIXME: NODE-2781, this check for write concern shouldn't be happening here, but instead during command construction
818
974
  if (options && options.writeConcern && (options.writeConcern as WriteConcern).w === 0) {
819
975
  if (session && session.explicit) {
820
- return new MongoDriverError('Cannot have explicit session with unacknowledged writes');
976
+ return new MongoAPIError('Cannot have explicit session with unacknowledged writes');
821
977
  }
822
978
  return;
823
979
  }
@@ -848,7 +1004,7 @@ export function applySession(
848
1004
  Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
849
1005
  } else if (session[kSnapshotEnabled]) {
850
1006
  command.readConcern = command.readConcern || { level: ReadConcernLevel.snapshot };
851
- if (session[kSnapshotTime] !== undefined) {
1007
+ if (session[kSnapshotTime] != null) {
852
1008
  Object.assign(command.readConcern, { atClusterTime: session[kSnapshotTime] });
853
1009
  }
854
1010
  }
@@ -880,7 +1036,7 @@ export function applySession(
880
1036
 
881
1037
  export function updateSessionFromResponse(session: ClientSession, document: Document): void {
882
1038
  if (document.$clusterTime) {
883
- resolveClusterTime(session, document.$clusterTime);
1039
+ _advanceClusterTime(session, document.$clusterTime);
884
1040
  }
885
1041
 
886
1042
  if (document.operationTime && session && session.supports.causalConsistency) {
@@ -891,11 +1047,12 @@ export function updateSessionFromResponse(session: ClientSession, document: Docu
891
1047
  session.transaction._recoveryToken = document.recoveryToken;
892
1048
  }
893
1049
 
894
- if (
895
- document.cursor?.atClusterTime &&
896
- session?.[kSnapshotEnabled] &&
897
- session[kSnapshotTime] === undefined
898
- ) {
899
- session[kSnapshotTime] = document.cursor.atClusterTime;
1050
+ if (session?.[kSnapshotEnabled] && session[kSnapshotTime] == null) {
1051
+ // find and aggregate commands return atClusterTime on the cursor
1052
+ // distinct includes it in the response body
1053
+ const atClusterTime = document.cursor?.atClusterTime || document.atClusterTime;
1054
+ if (atClusterTime) {
1055
+ session[kSnapshotTime] = atClusterTime;
1056
+ }
900
1057
  }
901
1058
  }
package/src/sort.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { MongoDriverError } from './error';
1
+ import { MongoInvalidArgumentError } from './error';
2
2
 
3
3
  /** @public */
4
4
  export type SortDirection =
@@ -45,13 +45,13 @@ function prepareDirection(direction: any = 1): SortDirectionForCmd {
45
45
  case '-1':
46
46
  return -1;
47
47
  default:
48
- throw new MongoDriverError(`Invalid sort direction: ${JSON.stringify(direction)}`);
48
+ throw new MongoInvalidArgumentError(`Invalid sort direction: ${JSON.stringify(direction)}`);
49
49
  }
50
50
  }
51
51
 
52
52
  /** @internal */
53
53
  function isMeta(t: SortDirection): t is { $meta: string } {
54
- return typeof t === 'object' && t !== null && '$meta' in t && typeof t.$meta === 'string';
54
+ return typeof t === 'object' && t != null && '$meta' in t && typeof t.$meta === 'string';
55
55
  }
56
56
 
57
57
  /** @internal */
@@ -118,7 +118,9 @@ export function formatSort(
118
118
  if (sort == null) return undefined;
119
119
  if (typeof sort === 'string') return new Map([[sort, prepareDirection(direction)]]);
120
120
  if (typeof sort !== 'object') {
121
- throw new MongoDriverError(`Invalid sort format: ${JSON.stringify(sort)}`);
121
+ throw new MongoInvalidArgumentError(
122
+ `Invalid sort format: ${JSON.stringify(sort)} Sort must be a valid object`
123
+ );
122
124
  }
123
125
  if (!Array.isArray(sort)) {
124
126
  return isMap(sort) ? mapToMap(sort) : Object.keys(sort).length ? objectToMap(sort) : undefined;
@@ -1,5 +1,5 @@
1
1
  import { ReadPreference } from './read_preference';
2
- import { MongoDriverError } from './error';
2
+ import { MongoRuntimeError, MongoTransactionError } from './error';
3
3
  import { ReadConcern } from './read_concern';
4
4
  import { WriteConcern } from './write_concern';
5
5
  import type { Server } from './sdam/server';
@@ -45,6 +45,17 @@ const stateMachine: { [state in TxnState]: TxnState[] } = {
45
45
  ]
46
46
  };
47
47
 
48
+ const ACTIVE_STATES: Set<TxnState> = new Set([
49
+ TxnState.STARTING_TRANSACTION,
50
+ TxnState.TRANSACTION_IN_PROGRESS
51
+ ]);
52
+
53
+ const COMMITTED_STATES: Set<TxnState> = new Set([
54
+ TxnState.TRANSACTION_COMMITTED,
55
+ TxnState.TRANSACTION_COMMITTED_EMPTY,
56
+ TxnState.TRANSACTION_ABORTED
57
+ ]);
58
+
48
59
  /**
49
60
  * Configuration options for a transaction.
50
61
  * @public
@@ -77,14 +88,13 @@ export class Transaction {
77
88
  /** Create a transaction @internal */
78
89
  constructor(options?: TransactionOptions) {
79
90
  options = options ?? {};
80
-
81
91
  this.state = TxnState.NO_TRANSACTION;
82
92
  this.options = {};
83
93
 
84
94
  const writeConcern = WriteConcern.fromOptions(options);
85
95
  if (writeConcern) {
86
96
  if (writeConcern.w === 0) {
87
- throw new MongoDriverError('Transactions do not support unacknowledged write concern');
97
+ throw new MongoTransactionError('Transactions do not support unacknowledged write concern');
88
98
  }
89
99
 
90
100
  this.options.writeConcern = writeConcern;
@@ -129,13 +139,12 @@ export class Transaction {
129
139
  * @returns Whether this session is presently in a transaction
130
140
  */
131
141
  get isActive(): boolean {
132
- const activeStates: TxnState[] = [
133
- TxnState.STARTING_TRANSACTION,
134
- TxnState.TRANSACTION_IN_PROGRESS
135
- ];
136
- return activeStates.includes(this.state);
142
+ return ACTIVE_STATES.has(this.state);
137
143
  }
138
144
 
145
+ get isCommitted(): boolean {
146
+ return COMMITTED_STATES.has(this.state);
147
+ }
139
148
  /**
140
149
  * Transition the transaction in the state machine
141
150
  * @internal
@@ -155,7 +164,7 @@ export class Transaction {
155
164
  return;
156
165
  }
157
166
 
158
- throw new MongoDriverError(
167
+ throw new MongoRuntimeError(
159
168
  `Attempted illegal state transition from [${this.state}] to [${nextState}]`
160
169
  );
161
170
  }