mongodb 6.4.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/lib/bulk/common.js +16 -24
  2. package/lib/bulk/common.js.map +1 -1
  3. package/lib/cmap/commands.js +0 -4
  4. package/lib/cmap/commands.js.map +1 -1
  5. package/lib/cmap/connect.js +3 -7
  6. package/lib/cmap/connect.js.map +1 -1
  7. package/lib/cmap/connection.js +57 -44
  8. package/lib/cmap/connection.js.map +1 -1
  9. package/lib/cmap/connection_pool.js +12 -20
  10. package/lib/cmap/connection_pool.js.map +1 -1
  11. package/lib/cmap/handshake/client_metadata.js +44 -1
  12. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  13. package/lib/cmap/wire_protocol/on_data.js +2 -14
  14. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  15. package/lib/cmap/wire_protocol/shared.js +2 -6
  16. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  17. package/lib/connection_string.js +12 -3
  18. package/lib/connection_string.js.map +1 -1
  19. package/lib/error.js +6 -1
  20. package/lib/error.js.map +1 -1
  21. package/lib/index.js.map +1 -1
  22. package/lib/mongo_client.js +6 -16
  23. package/lib/mongo_client.js.map +1 -1
  24. package/lib/mongo_logger.js +2 -1
  25. package/lib/mongo_logger.js.map +1 -1
  26. package/lib/operations/common_functions.js +7 -6
  27. package/lib/operations/common_functions.js.map +1 -1
  28. package/lib/operations/insert.js +4 -2
  29. package/lib/operations/insert.js.map +1 -1
  30. package/lib/sdam/monitor.js +7 -7
  31. package/lib/sdam/monitor.js.map +1 -1
  32. package/lib/sdam/server.js +8 -17
  33. package/lib/sdam/server.js.map +1 -1
  34. package/lib/sdam/topology.js +26 -34
  35. package/lib/sdam/topology.js.map +1 -1
  36. package/lib/sdam/topology_description.js.map +1 -1
  37. package/lib/utils.js +26 -34
  38. package/lib/utils.js.map +1 -1
  39. package/mongodb.d.ts +13 -4
  40. package/package.json +2 -2
  41. package/src/bulk/common.ts +21 -30
  42. package/src/cmap/commands.ts +1 -7
  43. package/src/cmap/connect.ts +4 -9
  44. package/src/cmap/connection.ts +61 -67
  45. package/src/cmap/connection_pool.ts +20 -33
  46. package/src/cmap/handshake/client_metadata.ts +54 -4
  47. package/src/cmap/stream_description.ts +1 -1
  48. package/src/cmap/wire_protocol/on_data.ts +2 -16
  49. package/src/cmap/wire_protocol/shared.ts +2 -6
  50. package/src/connection_string.ts +15 -4
  51. package/src/error.ts +11 -1
  52. package/src/index.ts +0 -1
  53. package/src/mongo_client.ts +11 -15
  54. package/src/mongo_logger.ts +2 -1
  55. package/src/operations/common_functions.ts +16 -5
  56. package/src/operations/insert.ts +5 -3
  57. package/src/sdam/monitor.ts +7 -7
  58. package/src/sdam/server.ts +12 -24
  59. package/src/sdam/topology.ts +31 -47
  60. package/src/sdam/topology_description.ts +1 -1
  61. package/src/utils.ts +25 -40
@@ -16,11 +16,9 @@ type PendingPromises = Omit<
16
16
  * https://nodejs.org/api/events.html#eventsonemitter-eventname-options
17
17
  *
18
18
  * Returns an AsyncIterator that iterates each 'data' event emitted from emitter.
19
- * It will reject upon an error event or if the provided signal is aborted.
19
+ * It will reject upon an error event.
20
20
  */
21
- export function onData(emitter: EventEmitter, options: { signal: AbortSignal }) {
22
- const signal = options.signal;
23
-
21
+ export function onData(emitter: EventEmitter) {
24
22
  // Setup pending events and pending promise lists
25
23
  /**
26
24
  * When the caller has not yet called .next(), we store the
@@ -89,19 +87,8 @@ export function onData(emitter: EventEmitter, options: { signal: AbortSignal })
89
87
  emitter.on('data', eventHandler);
90
88
  emitter.on('error', errorHandler);
91
89
 
92
- if (signal.aborted) {
93
- // If the signal is aborted, set up the first .next() call to be a rejection
94
- queueMicrotask(abortListener);
95
- } else {
96
- signal.addEventListener('abort', abortListener, { once: true });
97
- }
98
-
99
90
  return iterator;
100
91
 
101
- function abortListener() {
102
- errorHandler(signal.reason);
103
- }
104
-
105
92
  function eventHandler(value: Buffer) {
106
93
  const promise = unconsumedPromises.shift();
107
94
  if (promise != null) promise.resolve({ value, done: false });
@@ -119,7 +106,6 @@ export function onData(emitter: EventEmitter, options: { signal: AbortSignal })
119
106
  // Adding event handlers
120
107
  emitter.off('data', eventHandler);
121
108
  emitter.off('error', errorHandler);
122
- signal.removeEventListener('abort', abortListener);
123
109
  finished = true;
124
110
  const doneResult = { value: undefined, done: finished } as const;
125
111
 
@@ -13,12 +13,8 @@ export interface ReadPreferenceOption {
13
13
  }
14
14
 
15
15
  export function getReadPreference(options?: ReadPreferenceOption): ReadPreference {
16
- // Default to command version of the readPreference
16
+ // Default to command version of the readPreference.
17
17
  let readPreference = options?.readPreference ?? ReadPreference.primary;
18
- // If we have an option readPreference override the command one
19
- if (options?.readPreference) {
20
- readPreference = options.readPreference;
21
- }
22
18
 
23
19
  if (typeof readPreference === 'string') {
24
20
  readPreference = ReadPreference.fromString(readPreference);
@@ -43,7 +39,7 @@ export function isSharded(topologyOrServer?: Topology | Server | Connection): bo
43
39
  }
44
40
 
45
41
  // NOTE: This is incredibly inefficient, and should be removed once command construction
46
- // happens based on `Server` not `Topology`.
42
+ // happens based on `Server` not `Topology`.
47
43
  if (topologyOrServer.description && topologyOrServer.description instanceof TopologyDescription) {
48
44
  const servers: ServerDescription[] = Array.from(topologyOrServer.description.servers.values());
49
45
  return servers.some((server: ServerDescription) => server.type === ServerType.Mongos);
@@ -5,7 +5,7 @@ import { URLSearchParams } from 'url';
5
5
  import type { Document } from './bson';
6
6
  import { MongoCredentials } from './cmap/auth/mongo_credentials';
7
7
  import { AUTH_MECHS_AUTH_SRC_EXTERNAL, AuthMechanism } from './cmap/auth/providers';
8
- import { makeClientMetadata } from './cmap/handshake/client_metadata';
8
+ import { addContainerMetadata, makeClientMetadata } from './cmap/handshake/client_metadata';
9
9
  import { Compressor, type CompressorName } from './cmap/wire_protocol/compression';
10
10
  import { Encrypter } from './encrypter';
11
11
  import {
@@ -68,8 +68,15 @@ export async function resolveSRVRecord(options: MongoOptions): Promise<HostAddre
68
68
  throw new MongoAPIError('URI must include hostname, domain name, and tld');
69
69
  }
70
70
 
71
- // Resolve the SRV record and use the result as the list of hosts to connect to.
71
+ // Asynchronously start TXT resolution so that we do not have to wait until
72
+ // the SRV record is resolved before starting a second DNS query.
72
73
  const lookupAddress = options.srvHost;
74
+ const txtResolutionPromise = dns.promises.resolveTxt(lookupAddress);
75
+ txtResolutionPromise.catch(() => {
76
+ /* rejections will be handled later */
77
+ });
78
+
79
+ // Resolve the SRV record and use the result as the list of hosts to connect to.
73
80
  const addresses = await dns.promises.resolveSrv(
74
81
  `_${options.srvServiceName}._tcp.${lookupAddress}`
75
82
  );
@@ -88,10 +95,10 @@ export async function resolveSRVRecord(options: MongoOptions): Promise<HostAddre
88
95
 
89
96
  validateLoadBalancedOptions(hostAddresses, options, true);
90
97
 
91
- // Resolve TXT record and add options from there if they exist.
98
+ // Use the result of resolving the TXT record and add options from there if they exist.
92
99
  let record;
93
100
  try {
94
- record = await dns.promises.resolveTxt(lookupAddress);
101
+ record = await txtResolutionPromise;
95
102
  } catch (error) {
96
103
  if (error.code !== 'ENODATA' && error.code !== 'ENOTFOUND') {
97
104
  throw error;
@@ -545,6 +552,10 @@ export function parseOptions(
545
552
 
546
553
  mongoOptions.metadata = makeClientMetadata(mongoOptions);
547
554
 
555
+ mongoOptions.extendedMetadata = addContainerMetadata(mongoOptions.metadata).catch(() => {
556
+ /* rejections will be handled later */
557
+ });
558
+
548
559
  return mongoOptions;
549
560
  }
550
561
 
package/src/error.ts CHANGED
@@ -200,6 +200,8 @@ export class MongoError extends Error {
200
200
  * @category Error
201
201
  */
202
202
  export class MongoServerError extends MongoError {
203
+ /** Raw error result document returned by server. */
204
+ errorResponse: ErrorDescription;
203
205
  codeName?: string;
204
206
  writeConcernError?: Document;
205
207
  errInfo?: Document;
@@ -223,9 +225,17 @@ export class MongoServerError extends MongoError {
223
225
  this[kErrorLabels] = new Set(message.errorLabels);
224
226
  }
225
227
 
228
+ this.errorResponse = message;
229
+
226
230
  for (const name in message) {
227
- if (name !== 'errorLabels' && name !== 'errmsg' && name !== 'message')
231
+ if (
232
+ name !== 'errorLabels' &&
233
+ name !== 'errmsg' &&
234
+ name !== 'message' &&
235
+ name !== 'errorResponse'
236
+ ) {
228
237
  this[name] = message[name];
238
+ }
229
239
  }
230
240
  }
231
241
 
package/src/index.ts CHANGED
@@ -275,7 +275,6 @@ export type {
275
275
  Connection,
276
276
  ConnectionEvents,
277
277
  ConnectionOptions,
278
- DestroyOptions,
279
278
  ProxyOptions
280
279
  } from './cmap/connection';
281
280
  export type {
@@ -86,7 +86,7 @@ export interface Auth {
86
86
 
87
87
  /** @public */
88
88
  export interface PkFactory {
89
- createPk(): any; // TODO: when js-bson is typed, function should return some BSON type
89
+ createPk(): any;
90
90
  }
91
91
 
92
92
  /** @public */
@@ -552,7 +552,7 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
552
552
  try {
553
553
  await promisify(callback => this.topology?.connect(options, callback))();
554
554
  } catch (error) {
555
- this.topology?.close({ force: true });
555
+ this.topology?.close();
556
556
  throw error;
557
557
  }
558
558
  };
@@ -614,19 +614,12 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> {
614
614
  const topology = this.topology;
615
615
  this.topology = undefined;
616
616
 
617
- await new Promise<void>((resolve, reject) => {
618
- topology.close({ force }, error => {
619
- if (error) return reject(error);
620
- const { encrypter } = this[kOptions];
621
- if (encrypter) {
622
- return encrypter.closeCallback(this, force, error => {
623
- if (error) return reject(error);
624
- resolve();
625
- });
626
- }
627
- resolve();
628
- });
629
- });
617
+ topology.close();
618
+
619
+ const { encrypter } = this[kOptions];
620
+ if (encrypter) {
621
+ await encrypter.close(this, force);
622
+ }
630
623
  }
631
624
 
632
625
  /**
@@ -821,12 +814,15 @@ export interface MongoOptions
821
814
  readPreference: ReadPreference;
822
815
  readConcern: ReadConcern;
823
816
  loadBalanced: boolean;
817
+ directConnection: boolean;
824
818
  serverApi: ServerApi;
825
819
  compressors: CompressorName[];
826
820
  writeConcern: WriteConcern;
827
821
  dbName: string;
828
822
  metadata: ClientMetadata;
829
823
  /** @internal */
824
+ extendedMetadata: Promise<Document>;
825
+ /** @internal */
830
826
  autoEncrypter?: AutoEncrypter;
831
827
  proxyHost?: string;
832
828
  proxyPort?: number;
@@ -220,7 +220,8 @@ export function createStdioLogger(stream: {
220
220
  }): MongoDBLogWritable {
221
221
  return {
222
222
  write: promisify((log: Log, cb: (error?: Error) => void): unknown => {
223
- stream.write(inspect(log, { compact: true, breakLength: Infinity }), 'utf-8', cb);
223
+ const logLine = inspect(log, { compact: true, breakLength: Infinity });
224
+ stream.write(`${logLine}\n`, 'utf-8', cb);
224
225
  return;
225
226
  })
226
227
  };
@@ -43,11 +43,21 @@ export async function indexInformation(
43
43
  return info;
44
44
  }
45
45
 
46
- export function prepareDocs(
46
+ export function maybeAddIdToDocuments(
47
47
  coll: Collection,
48
48
  docs: Document[],
49
49
  options: { forceServerObjectId?: boolean }
50
- ): Document[] {
50
+ ): Document[];
51
+ export function maybeAddIdToDocuments(
52
+ coll: Collection,
53
+ docs: Document,
54
+ options: { forceServerObjectId?: boolean }
55
+ ): Document;
56
+ export function maybeAddIdToDocuments(
57
+ coll: Collection,
58
+ docOrDocs: Document[] | Document,
59
+ options: { forceServerObjectId?: boolean }
60
+ ): Document[] | Document {
51
61
  const forceServerObjectId =
52
62
  typeof options.forceServerObjectId === 'boolean'
53
63
  ? options.forceServerObjectId
@@ -55,14 +65,15 @@ export function prepareDocs(
55
65
 
56
66
  // no need to modify the docs if server sets the ObjectId
57
67
  if (forceServerObjectId === true) {
58
- return docs;
68
+ return docOrDocs;
59
69
  }
60
70
 
61
- return docs.map(doc => {
71
+ const transform = (doc: Document): Document => {
62
72
  if (doc._id == null) {
63
73
  doc._id = coll.s.pkFactory.createPk();
64
74
  }
65
75
 
66
76
  return doc;
67
- });
77
+ };
78
+ return Array.isArray(docOrDocs) ? docOrDocs.map(transform) : transform(docOrDocs);
68
79
  }
@@ -9,7 +9,7 @@ import type { MongoDBNamespace } from '../utils';
9
9
  import { WriteConcern } from '../write_concern';
10
10
  import { BulkWriteOperation } from './bulk_write';
11
11
  import { CommandOperation, type CommandOperationOptions } from './command';
12
- import { prepareDocs } from './common_functions';
12
+ import { maybeAddIdToDocuments } from './common_functions';
13
13
  import { AbstractOperation, Aspect, defineAspects } from './operation';
14
14
 
15
15
  /** @internal */
@@ -69,7 +69,7 @@ export interface InsertOneResult<TSchema = Document> {
69
69
 
70
70
  export class InsertOneOperation extends InsertOperation {
71
71
  constructor(collection: Collection, doc: Document, options: InsertOneOptions) {
72
- super(collection.s.namespace, prepareDocs(collection, [doc], options), options);
72
+ super(collection.s.namespace, maybeAddIdToDocuments(collection, [doc], options), options);
73
73
  }
74
74
 
75
75
  override async execute(
@@ -131,7 +131,9 @@ export class InsertManyOperation extends AbstractOperation<InsertManyResult> {
131
131
  const writeConcern = WriteConcern.fromOptions(options);
132
132
  const bulkWriteOperation = new BulkWriteOperation(
133
133
  coll,
134
- prepareDocs(coll, this.docs, options).map(document => ({ insertOne: { document } })),
134
+ this.docs.map(document => ({
135
+ insertOne: { document }
136
+ })),
135
137
  options
136
138
  );
137
139
 
@@ -214,7 +214,7 @@ function resetMonitorState(monitor: Monitor) {
214
214
 
215
215
  monitor[kCancellationToken].emit('cancel');
216
216
 
217
- monitor.connection?.destroy({ force: true });
217
+ monitor.connection?.destroy();
218
218
  monitor.connection = null;
219
219
  }
220
220
 
@@ -247,7 +247,7 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
247
247
  );
248
248
 
249
249
  function onHeartbeatFailed(err: Error) {
250
- monitor.connection?.destroy({ force: true });
250
+ monitor.connection?.destroy();
251
251
  monitor.connection = null;
252
252
 
253
253
  monitor.emitAndLogHeartbeat(
@@ -366,13 +366,13 @@ function checkServer(monitor: Monitor, callback: Callback<Document | null>) {
366
366
  await performInitialHandshake(connection, monitor.connectOptions);
367
367
  return connection;
368
368
  } catch (error) {
369
- connection.destroy({ force: false });
369
+ connection.destroy();
370
370
  throw error;
371
371
  }
372
372
  })().then(
373
373
  connection => {
374
374
  if (isInCloseState(monitor)) {
375
- connection.destroy({ force: true });
375
+ connection.destroy();
376
376
  return;
377
377
  }
378
378
 
@@ -479,7 +479,7 @@ export class RTTPinger {
479
479
  this.closed = true;
480
480
  clearTimeout(this[kMonitorId]);
481
481
 
482
- this.connection?.destroy({ force: true });
482
+ this.connection?.destroy();
483
483
  this.connection = undefined;
484
484
  }
485
485
  }
@@ -495,7 +495,7 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) {
495
495
 
496
496
  function measureAndReschedule(conn?: Connection) {
497
497
  if (rttPinger.closed) {
498
- conn?.destroy({ force: true });
498
+ conn?.destroy();
499
499
  return;
500
500
  }
501
501
 
@@ -529,7 +529,7 @@ function measureRoundTripTime(rttPinger: RTTPinger, options: RTTPingerOptions) {
529
529
  connection.command(ns('admin.$cmd'), { [commandName]: 1 }, undefined).then(
530
530
  () => measureAndReschedule(),
531
531
  () => {
532
- rttPinger.connection?.destroy({ force: true });
532
+ rttPinger.connection?.destroy();
533
533
  rttPinger.connection = undefined;
534
534
  rttPinger[kRoundTripTime] = 0;
535
535
  return;
@@ -1,6 +1,6 @@
1
1
  import type { Document } from '../bson';
2
2
  import { type AutoEncrypter } from '../client-side-encryption/auto_encrypter';
3
- import { type CommandOptions, Connection, type DestroyOptions } from '../cmap/connection';
3
+ import { type CommandOptions, Connection } from '../cmap/connection';
4
4
  import {
5
5
  ConnectionPool,
6
6
  type ConnectionPoolEvents,
@@ -41,7 +41,6 @@ import type { GetMoreOptions } from '../operations/get_more';
41
41
  import type { ClientSession } from '../sessions';
42
42
  import { isTransactionCommand } from '../transactions';
43
43
  import {
44
- type Callback,
45
44
  type EventEmitterWithState,
46
45
  makeStateMachine,
47
46
  maxWireVersion,
@@ -171,7 +170,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
171
170
  this.monitor.on(event, (e: any) => this.emit(event, e));
172
171
  }
173
172
 
174
- this.monitor.on('resetServer', (error: MongoError) => markServerUnknown(this, error));
173
+ this.monitor.on('resetServer', (error: MongoServerError) => markServerUnknown(this, error));
175
174
  this.monitor.on(Server.SERVER_HEARTBEAT_SUCCEEDED, (event: ServerHeartbeatSucceededEvent) => {
176
175
  this.emit(
177
176
  Server.DESCRIPTION_RECEIVED,
@@ -236,18 +235,8 @@ export class Server extends TypedEventEmitter<ServerEvents> {
236
235
  }
237
236
 
238
237
  /** Destroy the server connection */
239
- destroy(options?: DestroyOptions, callback?: Callback): void {
240
- if (typeof options === 'function') {
241
- callback = options;
242
- options = { force: false };
243
- }
244
- options = Object.assign({}, { force: false }, options);
245
-
238
+ destroy(): void {
246
239
  if (this.s.state === STATE_CLOSED) {
247
- if (typeof callback === 'function') {
248
- callback();
249
- }
250
-
251
240
  return;
252
241
  }
253
242
 
@@ -257,13 +246,9 @@ export class Server extends TypedEventEmitter<ServerEvents> {
257
246
  this.monitor?.close();
258
247
  }
259
248
 
260
- this.pool.close(options, err => {
261
- stateTransition(this, STATE_CLOSED);
262
- this.emit('closed');
263
- if (typeof callback === 'function') {
264
- callback(err);
265
- }
266
- });
249
+ this.pool.close();
250
+ stateTransition(this, STATE_CLOSED);
251
+ this.emit('closed');
267
252
  }
268
253
 
269
254
  /**
@@ -290,7 +275,10 @@ export class Server extends TypedEventEmitter<ServerEvents> {
290
275
  }
291
276
 
292
277
  // Clone the options
293
- const finalOptions = Object.assign({}, options, { wireProtocolCommand: false });
278
+ const finalOptions = Object.assign({}, options, {
279
+ wireProtocolCommand: false,
280
+ directConnection: this.topology.s.options.directConnection
281
+ });
294
282
 
295
283
  // There are cases where we need to flag the read preference not to get sent in
296
284
  // the command, such as pre-5.0 servers attempting to perform an aggregate write
@@ -369,7 +357,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
369
357
  // clear for the specific service id.
370
358
  if (!this.loadBalanced) {
371
359
  error.addErrorLabel(MongoErrorLabel.ResetPool);
372
- markServerUnknown(this, error);
360
+ markServerUnknown(this, error as MongoServerError);
373
361
  } else if (connection) {
374
362
  this.pool.clear({ serviceId: connection.serviceId });
375
363
  }
@@ -385,7 +373,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
385
373
  if (shouldClearPool) {
386
374
  error.addErrorLabel(MongoErrorLabel.ResetPool);
387
375
  }
388
- markServerUnknown(this, error);
376
+ markServerUnknown(this, error as MongoServerError);
389
377
  process.nextTick(() => this.requestCheck());
390
378
  }
391
379
  }
@@ -2,8 +2,8 @@ import { promisify } from 'util';
2
2
 
3
3
  import type { BSONSerializeOptions, Document } from '../bson';
4
4
  import type { MongoCredentials } from '../cmap/auth/mongo_credentials';
5
- import type { ConnectionEvents, DestroyOptions } from '../cmap/connection';
6
- import type { CloseOptions, ConnectionPoolEvents } from '../cmap/connection_pool';
5
+ import type { ConnectionEvents } from '../cmap/connection';
6
+ import type { ConnectionPoolEvents } from '../cmap/connection_pool';
7
7
  import type { ClientMetadata } from '../cmap/handshake/client_metadata';
8
8
  import { DEFAULT_OPTIONS, FEATURE_FLAGS } from '../connection_string';
9
9
  import {
@@ -158,6 +158,7 @@ export interface TopologyOptions extends BSONSerializeOptions, ServerOptions {
158
158
  directConnection: boolean;
159
159
  loadBalanced: boolean;
160
160
  metadata: ClientMetadata;
161
+ extendedMetadata: Promise<Document>;
161
162
  serverMonitoringMode: ServerMonitoringMode;
162
163
  /** MongoDB server API version */
163
164
  serverApi?: ServerApi;
@@ -468,7 +469,8 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
468
469
  selectServerOptions,
469
470
  (err, server) => {
470
471
  if (err) {
471
- return this.close({ force: false }, () => exitWithError(err));
472
+ this.close();
473
+ return exitWithError(err);
472
474
  }
473
475
 
474
476
  const skipPingOnConnect = this.s.options[Symbol.for('@@mdb.skipPingOnConnect')] === true;
@@ -494,41 +496,33 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
494
496
  }
495
497
 
496
498
  /** Close this topology */
497
- close(options: CloseOptions): void;
498
- close(options: CloseOptions, callback: Callback): void;
499
- close(options?: CloseOptions, callback?: Callback): void {
500
- options = options ?? { force: false };
501
-
499
+ close(): void {
502
500
  if (this.s.state === STATE_CLOSED || this.s.state === STATE_CLOSING) {
503
- return callback?.();
501
+ return;
504
502
  }
505
503
 
506
- const destroyedServers = Array.from(this.s.servers.values(), server => {
507
- return promisify(destroyServer)(server, this, { force: !!options?.force });
508
- });
504
+ for (const server of this.s.servers.values()) {
505
+ destroyServer(server, this);
506
+ }
509
507
 
510
- Promise.all(destroyedServers)
511
- .then(() => {
512
- this.s.servers.clear();
508
+ this.s.servers.clear();
513
509
 
514
- stateTransition(this, STATE_CLOSING);
510
+ stateTransition(this, STATE_CLOSING);
515
511
 
516
- drainWaitQueue(this[kWaitQueue], new MongoTopologyClosedError());
517
- drainTimerQueue(this.s.connectionTimers);
512
+ drainWaitQueue(this[kWaitQueue], new MongoTopologyClosedError());
513
+ drainTimerQueue(this.s.connectionTimers);
518
514
 
519
- if (this.s.srvPoller) {
520
- this.s.srvPoller.stop();
521
- this.s.srvPoller.removeListener(SrvPoller.SRV_RECORD_DISCOVERY, this.s.detectSrvRecords);
522
- }
515
+ if (this.s.srvPoller) {
516
+ this.s.srvPoller.stop();
517
+ this.s.srvPoller.removeListener(SrvPoller.SRV_RECORD_DISCOVERY, this.s.detectSrvRecords);
518
+ }
523
519
 
524
- this.removeListener(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
520
+ this.removeListener(Topology.TOPOLOGY_DESCRIPTION_CHANGED, this.s.detectShardedTopology);
525
521
 
526
- stateTransition(this, STATE_CLOSED);
522
+ stateTransition(this, STATE_CLOSED);
527
523
 
528
- // emit an event for close
529
- this.emitAndLog(Topology.TOPOLOGY_CLOSED, new TopologyClosedEvent(this.s.id));
530
- })
531
- .finally(() => callback?.());
524
+ // emit an event for close
525
+ this.emitAndLog(Topology.TOPOLOGY_CLOSED, new TopologyClosedEvent(this.s.id));
532
526
  }
533
527
 
534
528
  /**
@@ -772,30 +766,20 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
772
766
  }
773
767
 
774
768
  /** Destroys a server, and removes all event listeners from the instance */
775
- function destroyServer(
776
- server: Server,
777
- topology: Topology,
778
- options?: DestroyOptions,
779
- callback?: Callback
780
- ) {
781
- options = options ?? { force: false };
769
+ function destroyServer(server: Server, topology: Topology) {
782
770
  for (const event of LOCAL_SERVER_EVENTS) {
783
771
  server.removeAllListeners(event);
784
772
  }
785
773
 
786
- server.destroy(options, () => {
787
- topology.emitAndLog(
788
- Topology.SERVER_CLOSED,
789
- new ServerClosedEvent(topology.s.id, server.description.address)
790
- );
774
+ server.destroy();
775
+ topology.emitAndLog(
776
+ Topology.SERVER_CLOSED,
777
+ new ServerClosedEvent(topology.s.id, server.description.address)
778
+ );
791
779
 
792
- for (const event of SERVER_RELAY_EVENTS) {
793
- server.removeAllListeners(event);
794
- }
795
- if (typeof callback === 'function') {
796
- callback();
797
- }
798
- });
780
+ for (const event of SERVER_RELAY_EVENTS) {
781
+ server.removeAllListeners(event);
782
+ }
799
783
  }
800
784
 
801
785
  /** Predicts the TopologyType from options */
@@ -313,7 +313,7 @@ export class TopologyDescription {
313
313
  );
314
314
 
315
315
  if (descriptionsWithError.length > 0) {
316
- return descriptionsWithError[0].error;
316
+ return descriptionsWithError[0].error as MongoServerError;
317
317
  }
318
318
 
319
319
  return null;
package/src/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as crypto from 'crypto';
2
2
  import type { SrvRecord } from 'dns';
3
+ import { type EventEmitter } from 'events';
3
4
  import * as http from 'http';
4
5
  import { clearTimeout, setTimeout } from 'timers';
5
6
  import * as url from 'url';
@@ -385,46 +386,6 @@ export function maxWireVersion(topologyOrServer?: Connection | Topology | Server
385
386
  return 0;
386
387
  }
387
388
 
388
- /**
389
- * Applies the function `eachFn` to each item in `arr`, in parallel.
390
- * @internal
391
- *
392
- * @param arr - An array of items to asynchronously iterate over
393
- * @param eachFn - A function to call on each item of the array. The callback signature is `(item, callback)`, where the callback indicates iteration is complete.
394
- * @param callback - The callback called after every item has been iterated
395
- */
396
- export function eachAsync<T = Document>(
397
- arr: T[],
398
- eachFn: (item: T, callback: (err?: AnyError) => void) => void,
399
- callback: Callback
400
- ): void {
401
- arr = arr || [];
402
-
403
- let idx = 0;
404
- let awaiting = 0;
405
- for (idx = 0; idx < arr.length; ++idx) {
406
- awaiting++;
407
- eachFn(arr[idx], eachCallback);
408
- }
409
-
410
- if (awaiting === 0) {
411
- callback();
412
- return;
413
- }
414
-
415
- function eachCallback(err?: AnyError) {
416
- awaiting--;
417
- if (err) {
418
- callback(err);
419
- return;
420
- }
421
-
422
- if (idx === arr.length && awaiting <= 0) {
423
- callback();
424
- }
425
- }
426
- }
427
-
428
389
  /** @internal */
429
390
  export function arrayStrictEqual(arr: unknown[], arr2: unknown[]): boolean {
430
391
  if (!Array.isArray(arr) || !Array.isArray(arr2)) {
@@ -1295,3 +1256,27 @@ export function promiseWithResolvers<T>() {
1295
1256
  }
1296
1257
 
1297
1258
  export const randomBytes = promisify(crypto.randomBytes);
1259
+
1260
+ /**
1261
+ * Replicates the events.once helper.
1262
+ *
1263
+ * Removes unused signal logic and It **only** supports 0 or 1 argument events.
1264
+ *
1265
+ * @param ee - An event emitter that may emit `ev`
1266
+ * @param name - An event name to wait for
1267
+ */
1268
+ export async function once<T>(ee: EventEmitter, name: string): Promise<T> {
1269
+ const { promise, resolve, reject } = promiseWithResolvers<T>();
1270
+ const onEvent = (data: T) => resolve(data);
1271
+ const onError = (error: Error) => reject(error);
1272
+
1273
+ ee.once(name, onEvent).once('error', onError);
1274
+ try {
1275
+ const res = await promise;
1276
+ ee.off('error', onError);
1277
+ return res;
1278
+ } catch (error) {
1279
+ ee.off(name, onEvent);
1280
+ throw error;
1281
+ }
1282
+ }