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
@@ -17,9 +17,9 @@ import {
17
17
  HostAddress
18
18
  } from '../utils';
19
19
  import {
20
- AnyError,
21
- MongoDriverError,
22
- MongoError,
20
+ MongoRuntimeError,
21
+ MongoMissingDependencyError,
22
+ MongoCompatibilityError,
23
23
  MongoNetworkError,
24
24
  MongoNetworkTimeoutError,
25
25
  MongoServerError,
@@ -35,13 +35,12 @@ import {
35
35
  OpQueryOptions,
36
36
  Msg
37
37
  } from './commands';
38
- import { BSONSerializeOptions, Document, Long, pluckBSONSerializeOptions } from '../bson';
38
+ import { BSONSerializeOptions, Document, Long, pluckBSONSerializeOptions, ObjectId } from '../bson';
39
39
  import type { AutoEncrypter } from '../deps';
40
40
  import type { MongoCredentials } from './auth/mongo_credentials';
41
41
  import type { Stream } from './connect';
42
42
  import { applyCommonQueryOptions, getReadPreference, isSharded } from './wire_protocol/shared';
43
43
  import { ReadPreference, ReadPreferenceLike } from '../read_preference';
44
- import { isTransactionCommand } from '../transactions';
45
44
  import type { W, WriteConcern, WriteConcernOptions } from '../write_concern';
46
45
  import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client';
47
46
  import { CancellationToken, TypedEventEmitter } from '../mongo_types';
@@ -64,6 +63,8 @@ const kDescription = Symbol('description');
64
63
  const kIsMaster = Symbol('ismaster');
65
64
  /** @internal */
66
65
  const kAutoEncrypter = Symbol('autoEncrypter');
66
+ /** @internal */
67
+ const kFullResult = Symbol('fullResult');
67
68
 
68
69
  /** @internal */
69
70
  export interface QueryOptions extends BSONSerializeOptions {
@@ -82,7 +83,7 @@ export interface QueryOptions extends BSONSerializeOptions {
82
83
  oplogReplay?: boolean;
83
84
  }
84
85
 
85
- /** @public */
86
+ /** @internal */
86
87
  export interface CommandOptions extends BSONSerializeOptions {
87
88
  command?: boolean;
88
89
  slaveOk?: boolean;
@@ -90,7 +91,7 @@ export interface CommandOptions extends BSONSerializeOptions {
90
91
  readPreference?: ReadPreferenceLike;
91
92
  raw?: boolean;
92
93
  monitoring?: boolean;
93
- fullResult?: boolean;
94
+ [kFullResult]?: boolean;
94
95
  socketTimeoutMS?: number;
95
96
  /** Session to use for the operation */
96
97
  session?: ClientSession;
@@ -152,6 +153,8 @@ export type ConnectionEvents = {
152
153
  clusterTimeReceived(clusterTime: Document): void;
153
154
  close(): void;
154
155
  message(message: any): void;
156
+ pinned(pinType: string): void;
157
+ unpinned(pinType: string): void;
155
158
  };
156
159
 
157
160
  /** @internal */
@@ -194,6 +197,10 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
194
197
  static readonly CLOSE = 'close' as const;
195
198
  /** @event */
196
199
  static readonly MESSAGE = 'message' as const;
200
+ /** @event */
201
+ static readonly PINNED = 'pinned' as const;
202
+ /** @event */
203
+ static readonly UNPINNED = 'unpinned' as const;
197
204
 
198
205
  constructor(stream: Stream, options: ConnectionOptions) {
199
206
  super();
@@ -247,10 +254,22 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
247
254
  this[kIsMaster] = response;
248
255
  }
249
256
 
257
+ get serviceId(): ObjectId | undefined {
258
+ return this.ismaster?.serviceId;
259
+ }
260
+
261
+ get loadBalanced(): boolean {
262
+ return this.description.loadBalanced;
263
+ }
264
+
250
265
  get generation(): number {
251
266
  return this[kGeneration] || 0;
252
267
  }
253
268
 
269
+ set generation(generation: number) {
270
+ this[kGeneration] = generation;
271
+ }
272
+
254
273
  get idleTime(): number {
255
274
  return calculateDurationInMs(this[kLastUseTime]);
256
275
  }
@@ -306,6 +325,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
306
325
  options = { force: false };
307
326
  }
308
327
 
328
+ this.removeAllListeners(Connection.PINNED);
329
+ this.removeAllListeners(Connection.UNPINNED);
330
+
309
331
  options = Object.assign({ force: false }, options);
310
332
  if (this[kStream] == null || this.destroyed) {
311
333
  this.destroyed = true;
@@ -341,8 +363,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
341
363
  options: CommandOptions | undefined,
342
364
  callback: Callback
343
365
  ): void {
344
- if (typeof ns.db === 'undefined' || typeof ns === 'string') {
345
- throw new MongoDriverError('ns cannot be a string');
366
+ if (!(ns instanceof MongoDBNamespace)) {
367
+ // TODO(NODE-3483): Replace this with a MongoCommandError
368
+ throw new MongoRuntimeError('Must provide a MongoDBNamespace instance');
346
369
  }
347
370
 
348
371
  const readPreference = getReadPreference(cmd, options);
@@ -351,7 +374,6 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
351
374
 
352
375
  let clusterTime = this.clusterTime;
353
376
  let finalCmd = Object.assign({}, cmd);
354
- const inTransaction = session && (session.inTransaction() || isTransactionCommand(finalCmd));
355
377
 
356
378
  if (this.serverApi) {
357
379
  const { version, strict, deprecationErrors } = this.serverApi;
@@ -369,18 +391,6 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
369
391
  clusterTime = session.clusterTime;
370
392
  }
371
393
 
372
- // We need to unpin any read or write commands that happen outside of a pinned
373
- // transaction, so we check if we have a pinned transaction that is no longer
374
- // active, and unpin for all except start or commit.
375
- if (
376
- !session.transaction.isActive &&
377
- session.transaction.isPinned &&
378
- !finalCmd.startTransaction &&
379
- !finalCmd.commitTransaction
380
- ) {
381
- session.transaction.unpinServer();
382
- }
383
-
384
394
  const err = applySession(session, finalCmd, options as CommandOptions);
385
395
  if (err) {
386
396
  return callback(err);
@@ -416,41 +426,16 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
416
426
  ? new Msg(cmdNs, finalCmd, commandOptions)
417
427
  : new Query(cmdNs, finalCmd, commandOptions);
418
428
 
419
- const commandResponseHandler = inTransaction
420
- ? (err?: AnyError, ...args: Document[]) => {
421
- // We need to add a TransientTransactionError errorLabel, as stated in the transaction spec.
422
- if (
423
- err &&
424
- err instanceof MongoNetworkError &&
425
- !err.hasErrorLabel('TransientTransactionError')
426
- ) {
427
- err.addErrorLabel('TransientTransactionError');
428
- }
429
-
430
- if (
431
- session &&
432
- !cmd.commitTransaction &&
433
- err &&
434
- err instanceof MongoError &&
435
- err.hasErrorLabel('TransientTransactionError')
436
- ) {
437
- session.transaction.unpinServer();
438
- }
439
-
440
- return callback(err, ...args);
441
- }
442
- : callback;
443
-
444
429
  try {
445
- write(this, message, commandOptions, commandResponseHandler);
430
+ write(this, message, commandOptions, callback);
446
431
  } catch (err) {
447
- commandResponseHandler(err);
432
+ callback(err);
448
433
  }
449
434
  }
450
435
 
451
436
  /** @internal */
452
437
  query(ns: MongoDBNamespace, cmd: Document, options: QueryOptions, callback: Callback): void {
453
- const isExplain = typeof cmd.$explain !== 'undefined';
438
+ const isExplain = cmd.$explain != null;
454
439
  const readPreference = options.readPreference ?? ReadPreference.primary;
455
440
  const batchSize = options.batchSize || 0;
456
441
  const limit = options.limit;
@@ -509,7 +494,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
509
494
  write(
510
495
  this,
511
496
  query,
512
- { fullResult: true, ...pluckBSONSerializeOptions(options) },
497
+ { [kFullResult]: true, ...pluckBSONSerializeOptions(options) },
513
498
  (err, result) => {
514
499
  if (err || !result) return callback(err, result);
515
500
  if (isExplain && result.documents && result.documents[0]) {
@@ -528,10 +513,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
528
513
  options: GetMoreOptions,
529
514
  callback: Callback<Document>
530
515
  ): void {
531
- const fullResult = typeof options.fullResult === 'boolean' ? options.fullResult : false;
516
+ const fullResult = !!options[kFullResult];
532
517
  const wireVersion = maxWireVersion(this);
533
518
  if (!cursorId) {
534
- callback(new MongoDriverError('Invalid internal cursor state, no known cursor id'));
519
+ // TODO(NODE-3483): Replace this with a MongoCommandError
520
+ callback(new MongoRuntimeError('Invalid internal cursor state, no known cursor id'));
535
521
  return;
536
522
  }
537
523
 
@@ -542,7 +528,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
542
528
  Object.assign(options, { ...pluckBSONSerializeOptions(options) })
543
529
  );
544
530
 
545
- queryOptions.fullResult = true;
531
+ queryOptions[kFullResult] = true;
546
532
  queryOptions.command = true;
547
533
  write(this, getMoreOp, queryOptions, (err, response) => {
548
534
  if (fullResult) return callback(err, response);
@@ -585,7 +571,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
585
571
  callback: Callback
586
572
  ): void {
587
573
  if (!cursorIds || !Array.isArray(cursorIds)) {
588
- throw new MongoDriverError('Invalid list of cursor ids provided: ' + cursorIds);
574
+ // TODO(NODE-3483): Replace this with a MongoCommandError
575
+ throw new MongoRuntimeError(`Invalid list of cursor ids provided: ${cursorIds}`);
589
576
  }
590
577
 
591
578
  if (maxWireVersion(this) < 4) {
@@ -606,7 +593,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
606
593
  this.command(
607
594
  ns,
608
595
  { killCursors: ns.collection, cursors: cursorIds },
609
- { fullResult: true, ...options },
596
+ { [kFullResult]: true, ...options },
610
597
  (err, response) => {
611
598
  if (err || !response) return callback(err);
612
599
  if (response.cursorNotFound) {
@@ -615,7 +602,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
615
602
 
616
603
  if (!Array.isArray(response.documents) || response.documents.length === 0) {
617
604
  return callback(
618
- new MongoDriverError(
605
+ // TODO(NODE-3483)
606
+ new MongoRuntimeError(
619
607
  `invalid killCursors result returned for cursor id ${cursorIds[0]}`
620
608
  )
621
609
  );
@@ -648,7 +636,7 @@ export class CryptoConnection extends Connection {
648
636
  command(ns: MongoDBNamespace, cmd: Document, options: CommandOptions, callback: Callback): void {
649
637
  const autoEncrypter = this[kAutoEncrypter];
650
638
  if (!autoEncrypter) {
651
- return callback(new MongoDriverError('No AutoEncrypter available for encryption'));
639
+ return callback(new MongoMissingDependencyError('No AutoEncrypter available for encryption'));
652
640
  }
653
641
 
654
642
  const serverWireVersion = maxWireVersion(this);
@@ -658,7 +646,9 @@ export class CryptoConnection extends Connection {
658
646
  }
659
647
 
660
648
  if (serverWireVersion < 8) {
661
- callback(new MongoDriverError('Auto-encryption requires a minimum MongoDB version of 4.2'));
649
+ callback(
650
+ new MongoCompatibilityError('Auto-encryption requires a minimum MongoDB version of 4.2')
651
+ );
662
652
  return;
663
653
  }
664
654
 
@@ -680,8 +670,10 @@ export class CryptoConnection extends Connection {
680
670
  }
681
671
  }
682
672
 
683
- function hasSessionSupport(conn: Connection) {
684
- return conn.description.logicalSessionTimeoutMinutes != null;
673
+ /** @internal */
674
+ export function hasSessionSupport(conn: Connection): boolean {
675
+ const description = conn.description;
676
+ return description.logicalSessionTimeoutMinutes != null || !!description.loadBalanced;
685
677
  }
686
678
 
687
679
  function supportsOpMsg(conn: Connection) {
@@ -784,7 +776,7 @@ function write(
784
776
  requestId: command.requestId,
785
777
  cb: callback,
786
778
  session: options.session,
787
- fullResult: typeof options.fullResult === 'boolean' ? options.fullResult : false,
779
+ fullResult: !!options[kFullResult],
788
780
  noResponse: typeof options.noResponse === 'boolean' ? options.noResponse : false,
789
781
  documentsReturnedIn: options.documentsReturnedIn,
790
782
  command: !!options.command,
@@ -1,9 +1,11 @@
1
1
  import Denque = require('denque');
2
- import { Logger } from '../logger';
3
2
  import { APM_EVENTS, Connection, ConnectionEvents, ConnectionOptions } from './connection';
3
+ import type { ObjectId } from 'bson';
4
+ import { Logger } from '../logger';
5
+ import { ConnectionPoolMetrics } from './metrics';
4
6
  import { connect } from './connect';
5
7
  import { eachAsync, makeCounter, Callback } from '../utils';
6
- import { MongoDriverError, MongoError } from '../error';
8
+ import { MongoError, MongoInvalidArgumentError, MongoRuntimeError } from '../error';
7
9
  import { PoolClosedError, WaitQueueTimeoutError } from './errors';
8
10
  import {
9
11
  ConnectionPoolCreatedEvent,
@@ -30,6 +32,8 @@ const kMinPoolSizeTimer = Symbol('minPoolSizeTimer');
30
32
  /** @internal */
31
33
  const kGeneration = Symbol('generation');
32
34
  /** @internal */
35
+ const kServiceGenerations = Symbol('serviceGenerations');
36
+ /** @internal */
33
37
  const kConnectionCounter = Symbol('connectionCounter');
34
38
  /** @internal */
35
39
  const kCancellationToken = Symbol('cancellationToken');
@@ -37,6 +41,12 @@ const kCancellationToken = Symbol('cancellationToken');
37
41
  const kWaitQueue = Symbol('waitQueue');
38
42
  /** @internal */
39
43
  const kCancelled = Symbol('cancelled');
44
+ /** @internal */
45
+ const kMetrics = Symbol('metrics');
46
+ /** @internal */
47
+ const kCheckedOut = Symbol('checkedOut');
48
+ /** @internal */
49
+ const kProcessingWaitQueue = Symbol('processingWaitQueue');
40
50
 
41
51
  /** @public */
42
52
  export interface ConnectionPoolOptions extends Omit<ConnectionOptions, 'id' | 'generation'> {
@@ -48,6 +58,8 @@ export interface ConnectionPoolOptions extends Omit<ConnectionOptions, 'id' | 'g
48
58
  maxIdleTimeMS: number;
49
59
  /** The maximum amount of time operation execution should wait for a connection to become available. The default is 0 which means there is no limit. */
50
60
  waitQueueTimeoutMS: number;
61
+ /** If we are in load balancer mode. */
62
+ loadBalanced: boolean;
51
63
  }
52
64
 
53
65
  /** @internal */
@@ -99,12 +111,22 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
99
111
  * @internal
100
112
  */
101
113
  [kGeneration]: number;
114
+ /** A map of generations to service ids
115
+ * @internal
116
+ */
117
+ [kServiceGenerations]: Map<string, number>;
102
118
  /** @internal */
103
119
  [kConnectionCounter]: Generator<number>;
104
120
  /** @internal */
105
121
  [kCancellationToken]: CancellationToken;
106
122
  /** @internal */
107
123
  [kWaitQueue]: Denque<WaitQueueMember>;
124
+ /** @internal */
125
+ [kMetrics]: ConnectionPoolMetrics;
126
+ /** @internal */
127
+ [kCheckedOut]: number;
128
+ /** @internal */
129
+ [kProcessingWaitQueue]: boolean;
108
130
 
109
131
  /**
110
132
  * Emitted when the connection pool is created.
@@ -174,7 +196,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
174
196
  });
175
197
 
176
198
  if (this.options.minPoolSize > this.options.maxPoolSize) {
177
- throw new MongoDriverError(
199
+ throw new MongoInvalidArgumentError(
178
200
  'Connection pool minimum size must not be greater than maximum pool size'
179
201
  );
180
202
  }
@@ -184,10 +206,14 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
184
206
  this[kPermits] = this.options.maxPoolSize;
185
207
  this[kMinPoolSizeTimer] = undefined;
186
208
  this[kGeneration] = 0;
209
+ this[kServiceGenerations] = new Map();
187
210
  this[kConnectionCounter] = makeCounter(1);
188
211
  this[kCancellationToken] = new CancellationToken();
189
212
  this[kCancellationToken].setMaxListeners(Infinity);
190
213
  this[kWaitQueue] = new Denque();
214
+ this[kMetrics] = new ConnectionPoolMetrics();
215
+ this[kCheckedOut] = 0;
216
+ this[kProcessingWaitQueue] = false;
191
217
 
192
218
  process.nextTick(() => {
193
219
  this.emit(ConnectionPool.CONNECTION_POOL_CREATED, new ConnectionPoolCreatedEvent(this));
@@ -219,6 +245,25 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
219
245
  return this[kWaitQueue].length;
220
246
  }
221
247
 
248
+ get loadBalanced(): boolean {
249
+ return this.options.loadBalanced;
250
+ }
251
+
252
+ get serviceGenerations(): Map<string, number> {
253
+ return this[kServiceGenerations];
254
+ }
255
+
256
+ get currentCheckedOutCount(): number {
257
+ return this[kCheckedOut];
258
+ }
259
+
260
+ /**
261
+ * Get the metrics information for the pool when a wait queue timeout occurs.
262
+ */
263
+ private waitQueueErrorMetrics(): string {
264
+ return this[kMetrics].info(this.options.maxPoolSize);
265
+ }
266
+
222
267
  /**
223
268
  * Check a connection out of this pool. The connection will continue to be tracked, but no reference to it
224
269
  * will be held by the pool. This means that if a connection is checked out it MUST be checked back in or
@@ -250,10 +295,18 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
250
295
  ConnectionPool.CONNECTION_CHECK_OUT_FAILED,
251
296
  new ConnectionCheckOutFailedEvent(this, 'timeout')
252
297
  );
253
- waitQueueMember.callback(new WaitQueueTimeoutError(this));
298
+ waitQueueMember.callback(
299
+ new WaitQueueTimeoutError(
300
+ this.loadBalanced
301
+ ? this.waitQueueErrorMetrics()
302
+ : 'Timed out while checking out a connection from connection pool',
303
+ this.address
304
+ )
305
+ );
254
306
  }, waitQueueTimeoutMS);
255
307
  }
256
308
 
309
+ this[kCheckedOut] = this[kCheckedOut] + 1;
257
310
  this[kWaitQueue].push(waitQueueMember);
258
311
  process.nextTick(processWaitQueue, this);
259
312
  }
@@ -270,9 +323,10 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
270
323
 
271
324
  if (!willDestroy) {
272
325
  connection.markAvailable();
273
- this[kConnections].push(connection);
326
+ this[kConnections].unshift(connection);
274
327
  }
275
328
 
329
+ this[kCheckedOut] = this[kCheckedOut] - 1;
276
330
  this.emit(ConnectionPool.CONNECTION_CHECKED_IN, new ConnectionCheckedInEvent(this, connection));
277
331
 
278
332
  if (willDestroy) {
@@ -289,9 +343,24 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
289
343
  * Pool reset is handled by incrementing the pool's generation count. Any existing connection of a
290
344
  * previous generation will eventually be pruned during subsequent checkouts.
291
345
  */
292
- clear(): void {
293
- this[kGeneration] += 1;
294
- this.emit('connectionPoolCleared', new ConnectionPoolClearedEvent(this));
346
+ clear(serviceId?: ObjectId): void {
347
+ if (this.loadBalanced && serviceId) {
348
+ const sid = serviceId.toHexString();
349
+ const generation = this.serviceGenerations.get(sid);
350
+ // Only need to worry if the generation exists, since it should
351
+ // always be there but typescript needs the check.
352
+ if (generation == null) {
353
+ // TODO(NODE-3483)
354
+ throw new MongoRuntimeError('Service generations are required in load balancer mode.');
355
+ } else {
356
+ // Increment the generation for the service id.
357
+ this.serviceGenerations.set(sid, generation + 1);
358
+ }
359
+ } else {
360
+ this[kGeneration] += 1;
361
+ }
362
+
363
+ this.emit('connectionPoolCleared', new ConnectionPoolClearedEvent(this, serviceId));
295
364
  }
296
365
 
297
366
  /** Close the pool */
@@ -320,7 +389,8 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
320
389
  clearTimeout(waitQueueMember.timer);
321
390
  }
322
391
  if (!waitQueueMember[kCancelled]) {
323
- waitQueueMember.callback(new MongoDriverError('connection pool closed'));
392
+ // TODO(NODE-3483): Replace with MongoConnectionPoolClosedError
393
+ waitQueueMember.callback(new MongoRuntimeError('Connection pool closed'));
324
394
  }
325
395
  }
326
396
  }
@@ -338,7 +408,6 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
338
408
 
339
409
  // mark the pool as closed immediately
340
410
  this.closed = true;
341
-
342
411
  eachAsync<Connection>(
343
412
  this[kConnections].toArray(),
344
413
  (conn, cb) => {
@@ -362,10 +431,34 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
362
431
  *
363
432
  * NOTE: please note the required signature of `fn`
364
433
  *
434
+ * @remarks When in load balancer mode, connections can be pinned to cursors or transactions.
435
+ * In these cases we pass the connection in to this method to ensure it is used and a new
436
+ * connection is not checked out.
437
+ *
438
+ * @param conn - A pinned connection for use in load balancing mode.
365
439
  * @param fn - A function which operates on a managed connection
366
440
  * @param callback - The original callback
367
441
  */
368
- withConnection(fn: WithConnectionCallback, callback?: Callback<Connection>): void {
442
+ withConnection(
443
+ conn: Connection | undefined,
444
+ fn: WithConnectionCallback,
445
+ callback?: Callback<Connection>
446
+ ): void {
447
+ if (conn) {
448
+ // use the provided connection, and do _not_ check it in after execution
449
+ fn(undefined, conn, (fnErr, result) => {
450
+ if (typeof callback === 'function') {
451
+ if (fnErr) {
452
+ callback(fnErr);
453
+ } else {
454
+ callback(undefined, result);
455
+ }
456
+ }
457
+ });
458
+
459
+ return;
460
+ }
461
+
369
462
  this.checkOut((err, conn) => {
370
463
  // don't callback with `err` here, we might want to act upon it inside `fn`
371
464
  fn(err as MongoError, conn, (fnErr, result) => {
@@ -399,6 +492,13 @@ function ensureMinPoolSize(pool: ConnectionPool) {
399
492
  }
400
493
 
401
494
  function connectionIsStale(pool: ConnectionPool, connection: Connection) {
495
+ const serviceId = connection.serviceId;
496
+ if (pool.loadBalanced && serviceId) {
497
+ const sid = serviceId.toHexString();
498
+ const generation = pool.serviceGenerations.get(sid);
499
+ return connection.generation !== generation;
500
+ }
501
+
402
502
  return connection.generation !== pool[kGeneration];
403
503
  }
404
504
 
@@ -437,7 +537,24 @@ function createConnection(pool: ConnectionPool, callback?: Callback<Connection>)
437
537
  connection.on(event, (e: any) => pool.emit(event, e));
438
538
  }
439
539
 
440
- pool.emit(ConnectionPool.CONNECTION_POOL_CREATED, new ConnectionCreatedEvent(pool, connection));
540
+ pool.emit(ConnectionPool.CONNECTION_CREATED, new ConnectionCreatedEvent(pool, connection));
541
+
542
+ if (pool.loadBalanced) {
543
+ connection.on(Connection.PINNED, pinType => pool[kMetrics].markPinned(pinType));
544
+ connection.on(Connection.UNPINNED, pinType => pool[kMetrics].markUnpinned(pinType));
545
+
546
+ const serviceId = connection.serviceId;
547
+ if (serviceId) {
548
+ let generation;
549
+ const sid = serviceId.toHexString();
550
+ if ((generation = pool.serviceGenerations.get(sid))) {
551
+ connection.generation = generation;
552
+ } else {
553
+ pool.serviceGenerations.set(sid, 0);
554
+ connection.generation = 0;
555
+ }
556
+ }
557
+ }
441
558
 
442
559
  connection.markAvailable();
443
560
  pool.emit(ConnectionPool.CONNECTION_READY, new ConnectionReadyEvent(pool, connection));
@@ -465,10 +582,11 @@ function destroyConnection(pool: ConnectionPool, connection: Connection, reason:
465
582
  }
466
583
 
467
584
  function processWaitQueue(pool: ConnectionPool) {
468
- if (pool.closed) {
585
+ if (pool.closed || pool[kProcessingWaitQueue]) {
469
586
  return;
470
587
  }
471
588
 
589
+ pool[kProcessingWaitQueue] = true;
472
590
  while (pool.waitQueueSize) {
473
591
  const waitQueueMember = pool[kWaitQueue].peekFront();
474
592
  if (!waitQueueMember) {
@@ -502,11 +620,11 @@ function processWaitQueue(pool: ConnectionPool) {
502
620
  }
503
621
 
504
622
  pool[kWaitQueue].shift();
505
- return waitQueueMember.callback(undefined, connection);
623
+ waitQueueMember.callback(undefined, connection);
624
+ } else {
625
+ const reason = connection.closed ? 'error' : isStale ? 'stale' : 'idle';
626
+ destroyConnection(pool, connection, reason);
506
627
  }
507
-
508
- const reason = connection.closed ? 'error' : isStale ? 'stale' : 'idle';
509
- destroyConnection(pool, connection, reason);
510
628
  }
511
629
 
512
630
  const maxPoolSize = pool.options.maxPoolSize;
@@ -518,6 +636,7 @@ function processWaitQueue(pool: ConnectionPool) {
518
636
  pool[kConnections].push(connection);
519
637
  }
520
638
 
639
+ pool[kProcessingWaitQueue] = false;
521
640
  return;
522
641
  }
523
642
 
@@ -537,9 +656,11 @@ function processWaitQueue(pool: ConnectionPool) {
537
656
  clearTimeout(waitQueueMember.timer);
538
657
  }
539
658
  waitQueueMember.callback(err, connection);
659
+ pool[kProcessingWaitQueue] = false;
660
+ process.nextTick(() => processWaitQueue(pool));
540
661
  });
541
-
542
- return;
662
+ } else {
663
+ pool[kProcessingWaitQueue] = false;
543
664
  }
544
665
  }
545
666
 
@@ -565,7 +686,7 @@ export const CMAP_EVENTS = [
565
686
  * @param callback - A function to call back after connection management is complete
566
687
  */
567
688
  export type WithConnectionCallback = (
568
- error: MongoError,
689
+ error: MongoError | undefined,
569
690
  connection: Connection | undefined,
570
691
  callback: Callback<Connection>
571
692
  ) => void;
@@ -1,3 +1,4 @@
1
+ import type { ObjectId } from '../bson';
1
2
  import type { Connection } from './connection';
2
3
  import type { ConnectionPool, ConnectionPoolOptions } from './connection_pool';
3
4
  import type { AnyError } from '../error';
@@ -90,12 +91,14 @@ export class ConnectionClosedEvent extends ConnectionPoolMonitoringEvent {
90
91
  connectionId: number | '<monitor>';
91
92
  /** The reason the connection was closed */
92
93
  reason: string;
94
+ serviceId?: ObjectId;
93
95
 
94
96
  /** @internal */
95
97
  constructor(pool: ConnectionPool, connection: Connection, reason: string) {
96
98
  super(pool);
97
99
  this.connectionId = connection.id;
98
100
  this.reason = reason || 'unknown';
101
+ this.serviceId = connection.serviceId;
99
102
  }
100
103
  }
101
104
 
@@ -166,7 +169,11 @@ export class ConnectionCheckedInEvent extends ConnectionPoolMonitoringEvent {
166
169
  */
167
170
  export class ConnectionPoolClearedEvent extends ConnectionPoolMonitoringEvent {
168
171
  /** @internal */
169
- constructor(pool: ConnectionPool) {
172
+ serviceId?: ObjectId;
173
+
174
+ /** @internal */
175
+ constructor(pool: ConnectionPool, serviceId?: ObjectId) {
170
176
  super(pool);
177
+ this.serviceId = serviceId;
171
178
  }
172
179
  }
@@ -1,5 +1,4 @@
1
1
  import { MongoDriverError } from '../error';
2
- import type { Connection } from './connection';
3
2
  import type { ConnectionPool } from './connection_pool';
4
3
 
5
4
  /**
@@ -28,9 +27,9 @@ export class WaitQueueTimeoutError extends MongoDriverError {
28
27
  /** The address of the connection pool */
29
28
  address: string;
30
29
 
31
- constructor(pool: Connection | ConnectionPool) {
32
- super('Timed out while checking out a connection from connection pool');
33
- this.address = pool.address;
30
+ constructor(message: string, address: string) {
31
+ super(message);
32
+ this.address = address;
34
33
  }
35
34
 
36
35
  get name(): string {
@@ -1,6 +1,6 @@
1
1
  import { Duplex, DuplexOptions } from 'stream';
2
2
  import { Response, Msg, BinMsg, Query, WriteProtocolMessageType, MessageHeader } from './commands';
3
- import { MongoDriverError, MongoParseError } from '../error';
3
+ import { MongoDecompressionError, MongoParseError } from '../error';
4
4
  import { OP_COMPRESSED, OP_MSG } from './wire_protocol/constants';
5
5
  import {
6
6
  compress,
@@ -191,9 +191,7 @@ function processIncomingData(stream: MessageStream, callback: Callback<Buffer>)
191
191
 
192
192
  if (messageBody.length !== messageHeader.length) {
193
193
  callback(
194
- new MongoDriverError(
195
- 'Decompressing a compressed message from the server failed. The message is corrupt.'
196
- )
194
+ new MongoDecompressionError('Message body and message header must be the same length')
197
195
  );
198
196
 
199
197
  return;