mongodb 4.4.1 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/README.md +3 -2
  2. package/lib/admin.js +5 -6
  3. package/lib/admin.js.map +1 -1
  4. package/lib/bulk/common.js +34 -7
  5. package/lib/bulk/common.js.map +1 -1
  6. package/lib/bulk/unordered.js.map +1 -1
  7. package/lib/change_stream.js +251 -245
  8. package/lib/change_stream.js.map +1 -1
  9. package/lib/cmap/auth/gssapi.js.map +1 -1
  10. package/lib/cmap/auth/mongocr.js.map +1 -1
  11. package/lib/cmap/auth/mongodb_aws.js +3 -0
  12. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  13. package/lib/cmap/auth/plain.js.map +1 -1
  14. package/lib/cmap/auth/scram.js +1 -0
  15. package/lib/cmap/auth/scram.js.map +1 -1
  16. package/lib/cmap/auth/x509.js.map +1 -1
  17. package/lib/cmap/commands.js +12 -11
  18. package/lib/cmap/commands.js.map +1 -1
  19. package/lib/cmap/connect.js +8 -1
  20. package/lib/cmap/connect.js.map +1 -1
  21. package/lib/cmap/connection.js +111 -145
  22. package/lib/cmap/connection.js.map +1 -1
  23. package/lib/cmap/errors.js.map +1 -1
  24. package/lib/cmap/message_stream.js.map +1 -1
  25. package/lib/cmap/stream_description.js +3 -0
  26. package/lib/cmap/stream_description.js.map +1 -1
  27. package/lib/collection.js +60 -29
  28. package/lib/collection.js.map +1 -1
  29. package/lib/connection_string.js +32 -11
  30. package/lib/connection_string.js.map +1 -1
  31. package/lib/constants.js +8 -1
  32. package/lib/constants.js.map +1 -1
  33. package/lib/cursor/abstract_cursor.js +64 -51
  34. package/lib/cursor/abstract_cursor.js.map +1 -1
  35. package/lib/cursor/aggregation_cursor.js +2 -2
  36. package/lib/cursor/aggregation_cursor.js.map +1 -1
  37. package/lib/cursor/find_cursor.js +9 -4
  38. package/lib/cursor/find_cursor.js.map +1 -1
  39. package/lib/db.js +20 -13
  40. package/lib/db.js.map +1 -1
  41. package/lib/encrypter.js +21 -10
  42. package/lib/encrypter.js.map +1 -1
  43. package/lib/error.js +121 -59
  44. package/lib/error.js.map +1 -1
  45. package/lib/gridfs/download.js +2 -0
  46. package/lib/gridfs/download.js.map +1 -1
  47. package/lib/gridfs/index.js +42 -51
  48. package/lib/gridfs/index.js.map +1 -1
  49. package/lib/gridfs/upload.js +1 -1
  50. package/lib/gridfs/upload.js.map +1 -1
  51. package/lib/index.js +7 -3
  52. package/lib/index.js.map +1 -1
  53. package/lib/mongo_client.js +21 -27
  54. package/lib/mongo_client.js.map +1 -1
  55. package/lib/operations/add_user.js +8 -1
  56. package/lib/operations/add_user.js.map +1 -1
  57. package/lib/operations/aggregate.js +5 -0
  58. package/lib/operations/aggregate.js.map +1 -1
  59. package/lib/operations/bulk_write.js.map +1 -1
  60. package/lib/operations/collections.js.map +1 -1
  61. package/lib/operations/command.js +0 -3
  62. package/lib/operations/command.js.map +1 -1
  63. package/lib/operations/common_functions.js +8 -1
  64. package/lib/operations/common_functions.js.map +1 -1
  65. package/lib/operations/count.js.map +1 -1
  66. package/lib/operations/count_documents.js.map +1 -1
  67. package/lib/operations/create_collection.js +51 -17
  68. package/lib/operations/create_collection.js.map +1 -1
  69. package/lib/operations/delete.js +5 -3
  70. package/lib/operations/delete.js.map +1 -1
  71. package/lib/operations/distinct.js.map +1 -1
  72. package/lib/operations/drop.js +67 -7
  73. package/lib/operations/drop.js.map +1 -1
  74. package/lib/operations/estimated_document_count.js.map +1 -1
  75. package/lib/operations/eval.js.map +1 -1
  76. package/lib/operations/execute_operation.js +71 -79
  77. package/lib/operations/execute_operation.js.map +1 -1
  78. package/lib/operations/find.js +3 -52
  79. package/lib/operations/find.js.map +1 -1
  80. package/lib/operations/find_and_modify.js +5 -0
  81. package/lib/operations/find_and_modify.js.map +1 -1
  82. package/lib/operations/get_more.js +5 -0
  83. package/lib/operations/get_more.js.map +1 -1
  84. package/lib/operations/indexes.js +8 -9
  85. package/lib/operations/indexes.js.map +1 -1
  86. package/lib/operations/insert.js +8 -2
  87. package/lib/operations/insert.js.map +1 -1
  88. package/lib/operations/is_capped.js.map +1 -1
  89. package/lib/operations/list_collections.js +10 -42
  90. package/lib/operations/list_collections.js.map +1 -1
  91. package/lib/operations/list_databases.js +5 -0
  92. package/lib/operations/list_databases.js.map +1 -1
  93. package/lib/operations/map_reduce.js +1 -2
  94. package/lib/operations/map_reduce.js.map +1 -1
  95. package/lib/operations/operation.js +1 -3
  96. package/lib/operations/operation.js.map +1 -1
  97. package/lib/operations/options_operation.js.map +1 -1
  98. package/lib/operations/profiling_level.js.map +1 -1
  99. package/lib/operations/remove_user.js.map +1 -1
  100. package/lib/operations/rename.js +1 -1
  101. package/lib/operations/rename.js.map +1 -1
  102. package/lib/operations/run_command.js.map +1 -1
  103. package/lib/operations/set_profiling_level.js.map +1 -1
  104. package/lib/operations/stats.js.map +1 -1
  105. package/lib/operations/update.js +5 -0
  106. package/lib/operations/update.js.map +1 -1
  107. package/lib/operations/validate_collection.js.map +1 -1
  108. package/lib/read_concern.js +1 -0
  109. package/lib/read_concern.js.map +1 -1
  110. package/lib/sdam/common.js +1 -7
  111. package/lib/sdam/common.js.map +1 -1
  112. package/lib/sdam/events.js +1 -1
  113. package/lib/sdam/events.js.map +1 -1
  114. package/lib/sdam/monitor.js +1 -2
  115. package/lib/sdam/monitor.js.map +1 -1
  116. package/lib/sdam/server.js +108 -78
  117. package/lib/sdam/server.js.map +1 -1
  118. package/lib/sdam/topology.js +38 -55
  119. package/lib/sdam/topology.js.map +1 -1
  120. package/lib/sdam/topology_description.js +3 -4
  121. package/lib/sdam/topology_description.js.map +1 -1
  122. package/lib/sessions.js +93 -68
  123. package/lib/sessions.js.map +1 -1
  124. package/lib/utils.js +21 -97
  125. package/lib/utils.js.map +1 -1
  126. package/mongodb.d.ts +417 -92
  127. package/package.json +25 -29
  128. package/src/admin.ts +6 -10
  129. package/src/bulk/common.ts +45 -14
  130. package/src/bulk/unordered.ts +1 -1
  131. package/src/change_stream.ts +559 -425
  132. package/src/cmap/auth/gssapi.ts +1 -1
  133. package/src/cmap/auth/mongocr.ts +1 -1
  134. package/src/cmap/auth/mongodb_aws.ts +6 -1
  135. package/src/cmap/auth/plain.ts +1 -1
  136. package/src/cmap/auth/scram.ts +3 -2
  137. package/src/cmap/auth/x509.ts +6 -2
  138. package/src/cmap/commands.ts +26 -22
  139. package/src/cmap/connect.ts +15 -14
  140. package/src/cmap/connection.ts +163 -185
  141. package/src/cmap/errors.ts +2 -2
  142. package/src/cmap/message_stream.ts +2 -2
  143. package/src/cmap/stream_description.ts +4 -1
  144. package/src/collection.ts +66 -35
  145. package/src/connection_string.ts +46 -18
  146. package/src/constants.ts +6 -0
  147. package/src/cursor/abstract_cursor.ts +87 -65
  148. package/src/cursor/aggregation_cursor.ts +4 -4
  149. package/src/cursor/find_cursor.ts +16 -8
  150. package/src/db.ts +27 -24
  151. package/src/deps.ts +40 -0
  152. package/src/encrypter.ts +22 -11
  153. package/src/error.ts +170 -89
  154. package/src/gridfs/download.ts +3 -1
  155. package/src/gridfs/index.ts +51 -68
  156. package/src/gridfs/upload.ts +13 -13
  157. package/src/index.ts +21 -0
  158. package/src/mongo_client.ts +36 -50
  159. package/src/mongo_types.ts +1 -1
  160. package/src/operations/add_user.ts +14 -3
  161. package/src/operations/aggregate.ts +15 -5
  162. package/src/operations/bulk_write.ts +6 -2
  163. package/src/operations/collections.ts +6 -2
  164. package/src/operations/command.ts +23 -12
  165. package/src/operations/common_functions.ts +8 -1
  166. package/src/operations/count.ts +6 -2
  167. package/src/operations/count_documents.ts +5 -1
  168. package/src/operations/create_collection.ts +91 -23
  169. package/src/operations/delete.ts +19 -13
  170. package/src/operations/distinct.ts +6 -2
  171. package/src/operations/drop.ts +100 -10
  172. package/src/operations/estimated_document_count.ts +11 -3
  173. package/src/operations/eval.ts +6 -2
  174. package/src/operations/execute_operation.ts +103 -101
  175. package/src/operations/find.ts +9 -85
  176. package/src/operations/find_and_modify.ts +21 -2
  177. package/src/operations/get_more.ts +20 -6
  178. package/src/operations/indexes.ts +54 -36
  179. package/src/operations/insert.ts +28 -7
  180. package/src/operations/is_capped.ts +6 -2
  181. package/src/operations/list_collections.ts +24 -59
  182. package/src/operations/list_databases.ts +13 -3
  183. package/src/operations/map_reduce.ts +7 -6
  184. package/src/operations/operation.ts +10 -9
  185. package/src/operations/options_operation.ts +6 -2
  186. package/src/operations/profiling_level.ts +6 -2
  187. package/src/operations/remove_user.ts +6 -2
  188. package/src/operations/rename.ts +7 -3
  189. package/src/operations/run_command.ts +6 -2
  190. package/src/operations/set_profiling_level.ts +6 -2
  191. package/src/operations/stats.ts +12 -4
  192. package/src/operations/update.ts +19 -9
  193. package/src/operations/validate_collection.ts +6 -2
  194. package/src/read_concern.ts +1 -0
  195. package/src/sdam/common.ts +0 -6
  196. package/src/sdam/events.ts +2 -2
  197. package/src/sdam/monitor.ts +4 -5
  198. package/src/sdam/server.ts +125 -117
  199. package/src/sdam/topology.ts +39 -78
  200. package/src/sdam/topology_description.ts +3 -4
  201. package/src/sessions.ts +108 -78
  202. package/src/utils.ts +39 -119
  203. package/tsconfig.json +40 -0
  204. package/mongodb.ts34.d.ts +0 -5720
@@ -1,11 +1,5 @@
1
1
  import type { Document, Long } from '../bson';
2
- import {
3
- CommandOptions,
4
- Connection,
5
- DestroyOptions,
6
- GetMoreOptions,
7
- QueryOptions
8
- } from '../cmap/connection';
2
+ import { CommandOptions, Connection, DestroyOptions, GetMoreOptions } from '../cmap/connection';
9
3
  import {
10
4
  ConnectionPool,
11
5
  ConnectionPoolEvents,
@@ -27,14 +21,16 @@ import type { AutoEncrypter } from '../deps';
27
21
  import {
28
22
  isNetworkErrorBeforeHandshake,
29
23
  isNodeShuttingDownError,
30
- isRetryableWriteError,
31
24
  isSDAMUnrecoverableError,
32
25
  MongoCompatibilityError,
33
26
  MongoError,
27
+ MongoErrorLabel,
34
28
  MongoInvalidArgumentError,
35
29
  MongoNetworkError,
36
30
  MongoNetworkTimeoutError,
37
- MongoServerClosedError
31
+ MongoServerClosedError,
32
+ MongoUnexpectedServerResponseError,
33
+ needsRetryableWriteLabel
38
34
  } from '../error';
39
35
  import { Logger } from '../logger';
40
36
  import type { ServerApi } from '../mongo_client';
@@ -43,7 +39,6 @@ import type { ClientSession } from '../sessions';
43
39
  import { isTransactionCommand } from '../transactions';
44
40
  import {
45
41
  Callback,
46
- CallbackWithType,
47
42
  collationNotSupported,
48
43
  EventEmitterWithState,
49
44
  makeStateMachine,
@@ -98,6 +93,8 @@ export interface ServerPrivate {
98
93
  pool: ConnectionPool;
99
94
  /** MongoDB server API version */
100
95
  serverApi?: ServerApi;
96
+ /** A count of the operations currently running against the server. */
97
+ operationCount: number;
101
98
  }
102
99
 
103
100
  /** @public */
@@ -119,7 +116,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
119
116
  s: ServerPrivate;
120
117
  serverApi?: ServerApi;
121
118
  hello?: Document;
122
- [kMonitor]: Monitor;
119
+ [kMonitor]: Monitor | null;
123
120
 
124
121
  /** @event */
125
122
  static readonly SERVER_HEARTBEAT_STARTED = SERVER_HEARTBEAT_STARTED;
@@ -152,7 +149,8 @@ export class Server extends TypedEventEmitter<ServerEvents> {
152
149
  logger: new Logger('Server'),
153
150
  state: STATE_CLOSED,
154
151
  topology,
155
- pool: new ConnectionPool(poolOptions)
152
+ pool: new ConnectionPool(poolOptions),
153
+ operationCount: 0
156
154
  };
157
155
 
158
156
  for (const event of [...CMAP_EVENTS, ...APM_EVENTS]) {
@@ -163,22 +161,27 @@ export class Server extends TypedEventEmitter<ServerEvents> {
163
161
  this.clusterTime = clusterTime;
164
162
  });
165
163
 
166
- // monitoring is disabled in load balancing mode
167
- if (this.loadBalanced) return;
164
+ if (this.loadBalanced) {
165
+ this[kMonitor] = null;
166
+ // monitoring is disabled in load balancing mode
167
+ return;
168
+ }
168
169
 
169
170
  // create the monitor
170
- this[kMonitor] = new Monitor(this, this.s.options);
171
+ // TODO(NODE-4144): Remove new variable for type narrowing
172
+ const monitor = new Monitor(this, this.s.options);
173
+ this[kMonitor] = monitor;
171
174
 
172
175
  for (const event of HEARTBEAT_EVENTS) {
173
- this[kMonitor].on(event, (e: any) => this.emit(event, e));
176
+ monitor.on(event, (e: any) => this.emit(event, e));
174
177
  }
175
178
 
176
- this[kMonitor].on('resetConnectionPool', () => {
179
+ monitor.on('resetConnectionPool', () => {
177
180
  this.s.pool.clear();
178
181
  });
179
182
 
180
- this[kMonitor].on('resetServer', (error: MongoError) => markServerUnknown(this, error));
181
- this[kMonitor].on(Server.SERVER_HEARTBEAT_SUCCEEDED, (event: ServerHeartbeatSucceededEvent) => {
183
+ monitor.on('resetServer', (error: MongoError) => markServerUnknown(this, error));
184
+ monitor.on(Server.SERVER_HEARTBEAT_SUCCEEDED, (event: ServerHeartbeatSucceededEvent) => {
182
185
  this.emit(
183
186
  Server.DESCRIPTION_RECEIVED,
184
187
  new ServerDescription(this.description.hostAddress, event.reply, {
@@ -213,6 +216,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
213
216
  if (this.s.options && this.s.options.autoEncrypter) {
214
217
  return this.s.options.autoEncrypter;
215
218
  }
219
+ return;
216
220
  }
217
221
 
218
222
  get loadBalanced(): boolean {
@@ -233,7 +237,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
233
237
  // a load balancer. It never transitions out of this state and
234
238
  // has no monitor.
235
239
  if (!this.loadBalanced) {
236
- this[kMonitor].connect();
240
+ this[kMonitor]?.connect();
237
241
  } else {
238
242
  stateTransition(this, STATE_CONNECTED);
239
243
  this.emit(Server.CONNECT, this);
@@ -256,7 +260,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
256
260
  stateTransition(this, STATE_CLOSING);
257
261
 
258
262
  if (!this.loadBalanced) {
259
- this[kMonitor].close();
263
+ this[kMonitor]?.close();
260
264
  }
261
265
 
262
266
  this.s.pool.close(options, err => {
@@ -274,7 +278,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
274
278
  */
275
279
  requestCheck(): void {
276
280
  if (!this.loadBalanced) {
277
- this[kMonitor].requestCheck();
281
+ this[kMonitor]?.requestCheck();
278
282
  }
279
283
  }
280
284
 
@@ -282,24 +286,12 @@ export class Server extends TypedEventEmitter<ServerEvents> {
282
286
  * Execute a command
283
287
  * @internal
284
288
  */
285
- command(ns: MongoDBNamespace, cmd: Document, callback: Callback): void;
286
- /** @internal */
287
289
  command(
288
290
  ns: MongoDBNamespace,
289
291
  cmd: Document,
290
292
  options: CommandOptions,
291
293
  callback: Callback<Document>
292
- ): void;
293
- command(
294
- ns: MongoDBNamespace,
295
- cmd: Document,
296
- options?: CommandOptions | Callback<Document>,
297
- callback?: Callback<Document>
298
294
  ): void {
299
- if (typeof options === 'function') {
300
- (callback = options), (options = {}), (options = options ?? {});
301
- }
302
-
303
295
  if (callback == null) {
304
296
  throw new MongoInvalidArgumentError('Callback must be provided');
305
297
  }
@@ -336,6 +328,15 @@ export class Server extends TypedEventEmitter<ServerEvents> {
336
328
  // NOTE: This is a hack! We can't retrieve the connections used for executing an operation
337
329
  // (and prevent them from being checked back in) at the point of operation execution.
338
330
  // This should be considered as part of the work for NODE-2882
331
+ // NOTE:
332
+ // When incrementing operation count, it's important that we increment it before we
333
+ // attempt to check out a connection from the pool. This ensures that operations that
334
+ // are waiting for a connection are included in the operation count. Load balanced
335
+ // mode will only ever have a single server, so the operation count doesn't matter.
336
+ // Incrementing the operation count above the logic to handle load balanced mode would
337
+ // require special logic to decrement it again, or would double increment (the load
338
+ // balanced code makes a recursive call). Instead, we increment the count after this
339
+ // check.
339
340
  if (this.loadBalanced && session && conn == null && isPinnableCommand(cmd, session)) {
340
341
  this.s.pool.checkOut((err, checkedOut) => {
341
342
  if (err || checkedOut == null) {
@@ -344,16 +345,18 @@ export class Server extends TypedEventEmitter<ServerEvents> {
344
345
  }
345
346
 
346
347
  session.pin(checkedOut);
347
- this.command(ns, cmd, finalOptions, callback as Callback<Document>);
348
+ this.command(ns, cmd, finalOptions, callback);
348
349
  });
349
-
350
350
  return;
351
351
  }
352
352
 
353
+ this.s.operationCount += 1;
354
+
353
355
  this.s.pool.withConnection(
354
356
  conn,
355
357
  (err, conn, cb) => {
356
358
  if (err || !conn) {
359
+ this.s.operationCount -= 1;
357
360
  markServerUnknown(this, err);
358
361
  return cb(err);
359
362
  }
@@ -362,36 +365,10 @@ export class Server extends TypedEventEmitter<ServerEvents> {
362
365
  ns,
363
366
  cmd,
364
367
  finalOptions,
365
- makeOperationHandler(this, conn, cmd, finalOptions, cb) as Callback<Document>
366
- );
367
- },
368
- callback
369
- );
370
- }
371
-
372
- /**
373
- * Execute a query against the server
374
- * @internal
375
- */
376
- query(ns: MongoDBNamespace, cmd: Document, options: QueryOptions, callback: Callback): void {
377
- if (this.s.state === STATE_CLOSING || this.s.state === STATE_CLOSED) {
378
- callback(new MongoServerClosedError());
379
- return;
380
- }
381
-
382
- this.s.pool.withConnection(
383
- undefined,
384
- (err, conn, cb) => {
385
- if (err || !conn) {
386
- markServerUnknown(this, err);
387
- return cb(err);
388
- }
389
-
390
- conn.query(
391
- ns,
392
- cmd,
393
- options,
394
- makeOperationHandler(this, conn, cmd, options, cb) as Callback
368
+ makeOperationHandler(this, conn, cmd, finalOptions, (error, response) => {
369
+ this.s.operationCount -= 1;
370
+ cb(error, response);
371
+ })
395
372
  );
396
373
  },
397
374
  callback
@@ -413,10 +390,13 @@ export class Server extends TypedEventEmitter<ServerEvents> {
413
390
  return;
414
391
  }
415
392
 
393
+ this.s.operationCount += 1;
394
+
416
395
  this.s.pool.withConnection(
417
396
  options.session?.pinnedConnection,
418
397
  (err, conn, cb) => {
419
398
  if (err || !conn) {
399
+ this.s.operationCount -= 1;
420
400
  markServerUnknown(this, err);
421
401
  return cb(err);
422
402
  }
@@ -425,7 +405,10 @@ export class Server extends TypedEventEmitter<ServerEvents> {
425
405
  ns,
426
406
  cursorId,
427
407
  options,
428
- makeOperationHandler(this, conn, {}, options, cb) as Callback
408
+ makeOperationHandler(this, conn, {}, options, (error, response) => {
409
+ this.s.operationCount -= 1;
410
+ cb(error, response);
411
+ })
429
412
  );
430
413
  },
431
414
  callback
@@ -450,10 +433,12 @@ export class Server extends TypedEventEmitter<ServerEvents> {
450
433
  return;
451
434
  }
452
435
 
436
+ this.s.operationCount += 1;
453
437
  this.s.pool.withConnection(
454
438
  options.session?.pinnedConnection,
455
439
  (err, conn, cb) => {
456
440
  if (err || !conn) {
441
+ this.s.operationCount -= 1;
457
442
  markServerUnknown(this, err);
458
443
  return cb(err);
459
444
  }
@@ -462,7 +447,10 @@ export class Server extends TypedEventEmitter<ServerEvents> {
462
447
  ns,
463
448
  cursorIds,
464
449
  options,
465
- makeOperationHandler(this, conn, {}, undefined, cb) as Callback
450
+ makeOperationHandler(this, conn, {}, undefined, (error, response) => {
451
+ this.s.operationCount -= 1;
452
+ cb(error, response);
453
+ })
466
454
  );
467
455
  },
468
456
  callback
@@ -486,7 +474,7 @@ function markServerUnknown(server: Server, error?: MongoError) {
486
474
  }
487
475
 
488
476
  if (error instanceof MongoNetworkError && !(error instanceof MongoNetworkTimeoutError)) {
489
- server[kMonitor].reset();
477
+ server[kMonitor]?.reset();
490
478
  }
491
479
 
492
480
  server.emit(
@@ -546,67 +534,87 @@ function makeOperationHandler(
546
534
  cmd: Document,
547
535
  options: CommandOptions | GetMoreOptions | undefined,
548
536
  callback: Callback
549
- ): CallbackWithType<MongoError, Document> {
537
+ ): Callback {
550
538
  const session = options?.session;
551
- return function handleOperationResult(err, result) {
552
- if (err && !connectionIsStale(server.s.pool, connection)) {
553
- if (err instanceof MongoNetworkError) {
554
- if (session && !session.hasEnded && session.serverSession) {
555
- session.serverSession.isDirty = true;
556
- }
539
+ return function handleOperationResult(error, result) {
540
+ if (result != null) {
541
+ return callback(undefined, result);
542
+ }
557
543
 
558
- // inActiveTransaction check handles commit and abort.
559
- if (inActiveTransaction(session, cmd) && !err.hasErrorLabel('TransientTransactionError')) {
560
- err.addErrorLabel('TransientTransactionError');
561
- }
544
+ if (!error) {
545
+ return callback(new MongoUnexpectedServerResponseError('Empty response with no error'));
546
+ }
562
547
 
563
- if (
564
- (isRetryableWritesEnabled(server.s.topology) || isTransactionCommand(cmd)) &&
565
- supportsRetryableWrites(server) &&
566
- !inActiveTransaction(session, cmd)
567
- ) {
568
- err.addErrorLabel('RetryableWriteError');
569
- }
548
+ if (!(error instanceof MongoError)) {
549
+ // Node.js or some other error we have not special handling for
550
+ return callback(error);
551
+ }
570
552
 
571
- if (!(err instanceof MongoNetworkTimeoutError) || isNetworkErrorBeforeHandshake(err)) {
572
- // In load balanced mode we never mark the server as unknown and always
573
- // clear for the specific service id.
553
+ if (connectionIsStale(server.s.pool, connection)) {
554
+ return callback(error);
555
+ }
574
556
 
575
- server.s.pool.clear(connection.serviceId);
576
- if (!server.loadBalanced) {
577
- markServerUnknown(server, err);
578
- }
579
- }
580
- } else {
581
- // if pre-4.4 server, then add error label if its a retryable write error
582
- if (
583
- (isRetryableWritesEnabled(server.s.topology) || isTransactionCommand(cmd)) &&
584
- maxWireVersion(server) < 9 &&
585
- isRetryableWriteError(err) &&
586
- !inActiveTransaction(session, cmd)
587
- ) {
588
- err.addErrorLabel('RetryableWriteError');
557
+ if (error instanceof MongoNetworkError) {
558
+ if (session && !session.hasEnded && session.serverSession) {
559
+ session.serverSession.isDirty = true;
560
+ }
561
+
562
+ // inActiveTransaction check handles commit and abort.
563
+ if (
564
+ inActiveTransaction(session, cmd) &&
565
+ !error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)
566
+ ) {
567
+ error.addErrorLabel(MongoErrorLabel.TransientTransactionError);
568
+ }
569
+
570
+ if (
571
+ (isRetryableWritesEnabled(server.s.topology) || isTransactionCommand(cmd)) &&
572
+ supportsRetryableWrites(server) &&
573
+ !inActiveTransaction(session, cmd)
574
+ ) {
575
+ error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
576
+ }
577
+
578
+ if (!(error instanceof MongoNetworkTimeoutError) || isNetworkErrorBeforeHandshake(error)) {
579
+ // In load balanced mode we never mark the server as unknown and always
580
+ // clear for the specific service id.
581
+
582
+ server.s.pool.clear(connection.serviceId);
583
+ if (!server.loadBalanced) {
584
+ markServerUnknown(server, error);
589
585
  }
586
+ }
587
+ } else {
588
+ if (
589
+ (isRetryableWritesEnabled(server.s.topology) || isTransactionCommand(cmd)) &&
590
+ needsRetryableWriteLabel(error, maxWireVersion(server)) &&
591
+ !inActiveTransaction(session, cmd)
592
+ ) {
593
+ error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
594
+ }
590
595
 
591
- if (isSDAMUnrecoverableError(err)) {
592
- if (shouldHandleStateChangeError(server, err)) {
593
- if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(err)) {
594
- server.s.pool.clear(connection.serviceId);
595
- }
596
+ if (isSDAMUnrecoverableError(error)) {
597
+ if (shouldHandleStateChangeError(server, error)) {
598
+ if (maxWireVersion(server) <= 7 || isNodeShuttingDownError(error)) {
599
+ server.s.pool.clear(connection.serviceId);
600
+ }
596
601
 
597
- if (!server.loadBalanced) {
598
- markServerUnknown(server, err);
599
- process.nextTick(() => server.requestCheck());
600
- }
602
+ if (!server.loadBalanced) {
603
+ markServerUnknown(server, error);
604
+ process.nextTick(() => server.requestCheck());
601
605
  }
602
606
  }
603
607
  }
608
+ }
604
609
 
605
- if (session && session.isPinned && err.hasErrorLabel('TransientTransactionError')) {
606
- session.unpin({ force: true });
607
- }
610
+ if (
611
+ session &&
612
+ session.isPinned &&
613
+ error.hasErrorLabel(MongoErrorLabel.TransientTransactionError)
614
+ ) {
615
+ session.unpin({ force: true });
608
616
  }
609
617
 
610
- callback(err, result);
618
+ return callback(error);
611
619
  };
612
620
  }
@@ -4,7 +4,7 @@ import { deserialize, serialize } from '../bson';
4
4
  import type { MongoCredentials } from '../cmap/auth/mongo_credentials';
5
5
  import type { ConnectionEvents, DestroyOptions } from '../cmap/connection';
6
6
  import type { CloseOptions, ConnectionPoolEvents } from '../cmap/connection_pool';
7
- import { DEFAULT_OPTIONS } from '../connection_string';
7
+ import { DEFAULT_OPTIONS, FEATURE_FLAGS } from '../connection_string';
8
8
  import {
9
9
  CLOSE,
10
10
  CONNECT,
@@ -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,
@@ -154,6 +153,8 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
154
153
  metadata: ClientMetadata;
155
154
  /** MongoDB server API version */
156
155
  serverApi?: ServerApi;
156
+ /** @internal */
157
+ [featureFlag: symbol]: any;
157
158
  }
158
159
 
159
160
  /** @public */
@@ -249,22 +250,8 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
249
250
  // Options should only be undefined in tests, MongoClient will always have defined options
250
251
  options = options ?? {
251
252
  hosts: [HostAddress.fromString('localhost:27017')],
252
- retryReads: DEFAULT_OPTIONS.get('retryReads'),
253
- retryWrites: DEFAULT_OPTIONS.get('retryWrites'),
254
- serverSelectionTimeoutMS: DEFAULT_OPTIONS.get('serverSelectionTimeoutMS'),
255
- directConnection: DEFAULT_OPTIONS.get('directConnection'),
256
- loadBalanced: DEFAULT_OPTIONS.get('loadBalanced'),
257
- metadata: DEFAULT_OPTIONS.get('metadata'),
258
- monitorCommands: DEFAULT_OPTIONS.get('monitorCommands'),
259
- tls: DEFAULT_OPTIONS.get('tls'),
260
- maxPoolSize: DEFAULT_OPTIONS.get('maxPoolSize'),
261
- minPoolSize: DEFAULT_OPTIONS.get('minPoolSize'),
262
- waitQueueTimeoutMS: DEFAULT_OPTIONS.get('waitQueueTimeoutMS'),
263
- connectionType: DEFAULT_OPTIONS.get('connectionType'),
264
- connectTimeoutMS: DEFAULT_OPTIONS.get('connectTimeoutMS'),
265
- maxIdleTimeMS: DEFAULT_OPTIONS.get('maxIdleTimeMS'),
266
- heartbeatFrequencyMS: DEFAULT_OPTIONS.get('heartbeatFrequencyMS'),
267
- minHeartbeatFrequencyMS: DEFAULT_OPTIONS.get('minHeartbeatFrequencyMS')
253
+ ...Object.fromEntries(DEFAULT_OPTIONS.entries()),
254
+ ...Object.fromEntries(FEATURE_FLAGS.entries())
268
255
  };
269
256
 
270
257
  if (typeof seeds === 'string') {
@@ -334,7 +321,6 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
334
321
 
335
322
  // timer management
336
323
  connectionTimers: new Set<NodeJS.Timeout>(),
337
-
338
324
  detectShardedTopology: ev => this.detectShardedTopology(ev),
339
325
  detectSrvRecords: ev => this.detectSrvRecords(ev)
340
326
  };
@@ -435,7 +421,12 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
435
421
 
436
422
  // connect all known servers, then attempt server selection to connect
437
423
  const serverDescriptions = Array.from(this.s.description.servers.values());
438
- connectServers(this, serverDescriptions);
424
+ this.s.servers = new Map(
425
+ serverDescriptions.map(serverDescription => [
426
+ serverDescription.address,
427
+ createAndConnectServer(this, serverDescription)
428
+ ])
429
+ );
439
430
 
440
431
  // In load balancer mode we need to fake a server description getting
441
432
  // emitted from the monitor, since the monitor doesn't exist.
@@ -458,8 +449,9 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
458
449
  }
459
450
 
460
451
  // TODO: NODE-2471
461
- if (server && this.s.credentials) {
462
- server.command(ns('admin.$cmd'), { ping: 1 }, err => {
452
+ const skipPingOnConnect = this.s.options[Symbol.for('@@mdb.skipPingOnConnect')] === true;
453
+ if (!skipPingOnConnect && server && this.s.credentials) {
454
+ server.command(ns('admin.$cmd'), { ping: 1 }, {}, err => {
463
455
  if (err) {
464
456
  typeof callback === 'function' ? callback(err) : this.emit(Topology.ERROR, err);
465
457
  return;
@@ -549,27 +541,11 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
549
541
  * @param callback - The callback used to indicate success or failure
550
542
  * @returns An instance of a `Server` meeting the criteria of the predicate provided
551
543
  */
552
- selectServer(options: SelectServerOptions, callback: Callback<Server>): void;
553
- selectServer(
554
- selector: string | ReadPreference | ServerSelector,
555
- callback: Callback<Server>
556
- ): void;
557
544
  selectServer(
558
545
  selector: string | ReadPreference | ServerSelector,
559
546
  options: SelectServerOptions,
560
547
  callback: Callback<Server>
561
- ): void;
562
- selectServer(
563
- selector: string | ReadPreference | ServerSelector | SelectServerOptions,
564
- _options?: SelectServerOptions | Callback<Server>,
565
- _callback?: Callback<Server>
566
548
  ): 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
549
  let serverSelector;
574
550
  if (typeof selector !== 'function') {
575
551
  if (typeof selector === 'string') {
@@ -667,6 +643,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
667
643
 
668
644
  this.selectServer(
669
645
  readPreferenceServerSelector(ReadPreference.primaryPreferred),
646
+ {},
670
647
  (err, server) => {
671
648
  if (err || !server) {
672
649
  if (typeof callback === 'function') callback(err);
@@ -866,22 +843,13 @@ function topologyTypeFromOptions(options?: TopologyOptions) {
866
843
  return TopologyType.Unknown;
867
844
  }
868
845
 
869
- function randomSelection(array: ServerDescription[]): ServerDescription {
870
- return array[Math.floor(Math.random() * array.length)];
871
- }
872
-
873
846
  /**
874
847
  * Creates new server instances and attempts to connect them
875
848
  *
876
849
  * @param topology - The topology that this server belongs to
877
850
  * @param serverDescription - The description for the server to initialize and connect to
878
- * @param connectDelay - Time to wait before attempting initial connection
879
851
  */
880
- function createAndConnectServer(
881
- topology: Topology,
882
- serverDescription: ServerDescription,
883
- connectDelay?: number
884
- ) {
852
+ function createAndConnectServer(topology: Topology, serverDescription: ServerDescription) {
885
853
  topology.emit(
886
854
  Topology.SERVER_OPENING,
887
855
  new ServerOpeningEvent(topology.s.id, serverDescription.address)
@@ -894,38 +862,10 @@ function createAndConnectServer(
894
862
 
895
863
  server.on(Server.DESCRIPTION_RECEIVED, description => topology.serverUpdateHandler(description));
896
864
 
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
865
  server.connect();
908
866
  return server;
909
867
  }
910
868
 
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
869
  /**
930
870
  * @param topology - Topology to update.
931
871
  * @param incomingServerDescription - New server description.
@@ -1019,13 +959,34 @@ function processWaitQueue(topology: Topology) {
1019
959
  continue;
1020
960
  }
1021
961
 
962
+ let selectedServer;
1022
963
  if (selectedDescriptions.length === 0) {
1023
964
  topology[kWaitQueue].push(waitQueueMember);
1024
965
  continue;
966
+ } else if (selectedDescriptions.length === 1) {
967
+ selectedServer = topology.s.servers.get(selectedDescriptions[0].address);
968
+ } else {
969
+ // don't shuffle the array if there are only two elements
970
+ const descriptions =
971
+ selectedDescriptions.length === 2 ? selectedDescriptions : shuffle(selectedDescriptions, 2);
972
+ const server1 = topology.s.servers.get(descriptions[0].address);
973
+ const server2 = topology.s.servers.get(descriptions[1].address);
974
+
975
+ selectedServer =
976
+ server1 && server2 && server1.s.operationCount < server2.s.operationCount
977
+ ? server1
978
+ : server2;
1025
979
  }
1026
980
 
1027
- const selectedServerDescription = randomSelection(selectedDescriptions);
1028
- const selectedServer = topology.s.servers.get(selectedServerDescription.address);
981
+ if (!selectedServer) {
982
+ waitQueueMember.callback(
983
+ new MongoServerSelectionError(
984
+ 'server selection returned a server description but the server was not found in the topology',
985
+ topology.description
986
+ )
987
+ );
988
+ return;
989
+ }
1029
990
  const transaction = waitQueueMember.transaction;
1030
991
  if (isSharded && transaction && transaction.isActive && selectedServer) {
1031
992
  transaction.pinServer(selectedServer);
@@ -58,15 +58,12 @@ 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;
67
64
  this.compatible = true;
68
65
  this.heartbeatFrequencyMS = options.heartbeatFrequencyMS ?? 0;
69
- this.localThresholdMS = options.localThresholdMS ?? 0;
66
+ this.localThresholdMS = options.localThresholdMS ?? 15;
70
67
 
71
68
  if (setName) {
72
69
  this.setName = setName;
@@ -204,6 +201,7 @@ export class TopologyDescription {
204
201
  let { type: topologyType, setName, maxSetVersion, maxElectionId, commonWireVersion } = this;
205
202
 
206
203
  if (serverDescription.setName && setName && serverDescription.setName !== setName) {
204
+ // TODO(NODE-4159): servers with an incorrect setName should be removed not marked Unknown
207
205
  serverDescription = new ServerDescription(address, undefined);
208
206
  }
209
207
 
@@ -321,6 +319,7 @@ export class TopologyDescription {
321
319
  if (descriptionsWithError.length > 0) {
322
320
  return descriptionsWithError[0].error;
323
321
  }
322
+ return;
324
323
  }
325
324
 
326
325
  /**