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
@@ -1,6 +1,6 @@
1
1
  import { promisify } from 'util';
2
2
 
3
- import { type BSONSerializeOptions, type Document, ObjectId, resolveBSONOptions } from '../bson';
3
+ import { type BSONSerializeOptions, type Document, resolveBSONOptions } from '../bson';
4
4
  import type { Collection } from '../collection';
5
5
  import {
6
6
  type AnyError,
@@ -12,6 +12,7 @@ import {
12
12
  } from '../error';
13
13
  import type { Filter, OneOrMore, OptionalId, UpdateFilter, WithoutId } from '../mongo_types';
14
14
  import type { CollationOptions, CommandOperationOptions } from '../operations/command';
15
+ import { maybeAddIdToDocuments } from '../operations/common_functions';
15
16
  import { DeleteOperation, type DeleteStatement, makeDeleteStatement } from '../operations/delete';
16
17
  import { executeOperation } from '../operations/execute_operation';
17
18
  import { InsertOperation } from '../operations/insert';
@@ -917,7 +918,7 @@ export abstract class BulkOperationBase {
917
918
  * Create a new OrderedBulkOperation or UnorderedBulkOperation instance
918
919
  * @internal
919
920
  */
920
- constructor(collection: Collection, options: BulkWriteOptions, isOrdered: boolean) {
921
+ constructor(private collection: Collection, options: BulkWriteOptions, isOrdered: boolean) {
921
922
  // determine whether bulkOperation is ordered or unordered
922
923
  this.isOrdered = isOrdered;
923
924
 
@@ -1032,9 +1033,9 @@ export abstract class BulkOperationBase {
1032
1033
  * ```
1033
1034
  */
1034
1035
  insert(document: Document): BulkOperationBase {
1035
- if (document._id == null && !shouldForceServerObjectId(this)) {
1036
- document._id = new ObjectId();
1037
- }
1036
+ maybeAddIdToDocuments(this.collection, document, {
1037
+ forceServerObjectId: this.shouldForceServerObjectId()
1038
+ });
1038
1039
 
1039
1040
  return this.addToOperationsList(BatchType.INSERT, document);
1040
1041
  }
@@ -1093,21 +1094,16 @@ export abstract class BulkOperationBase {
1093
1094
  throw new MongoInvalidArgumentError('Operation must be an object with an operation key');
1094
1095
  }
1095
1096
  if ('insertOne' in op) {
1096
- const forceServerObjectId = shouldForceServerObjectId(this);
1097
- if (op.insertOne && op.insertOne.document == null) {
1098
- // NOTE: provided for legacy support, but this is a malformed operation
1099
- if (forceServerObjectId !== true && (op.insertOne as Document)._id == null) {
1100
- (op.insertOne as Document)._id = new ObjectId();
1101
- }
1102
-
1103
- return this.addToOperationsList(BatchType.INSERT, op.insertOne);
1104
- }
1097
+ const forceServerObjectId = this.shouldForceServerObjectId();
1098
+ const document =
1099
+ op.insertOne && op.insertOne.document == null
1100
+ ? // TODO(NODE-6003): remove support for omitting the `documents` subdocument in bulk inserts
1101
+ (op.insertOne as Document)
1102
+ : op.insertOne.document;
1105
1103
 
1106
- if (forceServerObjectId !== true && op.insertOne.document._id == null) {
1107
- op.insertOne.document._id = new ObjectId();
1108
- }
1104
+ maybeAddIdToDocuments(this.collection, document, { forceServerObjectId });
1109
1105
 
1110
- return this.addToOperationsList(BatchType.INSERT, op.insertOne.document);
1106
+ return this.addToOperationsList(BatchType.INSERT, document);
1111
1107
  }
1112
1108
 
1113
1109
  if ('replaceOne' in op || 'updateOne' in op || 'updateMany' in op) {
@@ -1268,6 +1264,13 @@ export abstract class BulkOperationBase {
1268
1264
  batchType: BatchType,
1269
1265
  document: Document | UpdateStatement | DeleteStatement
1270
1266
  ): this;
1267
+
1268
+ private shouldForceServerObjectId(): boolean {
1269
+ return (
1270
+ this.s.options.forceServerObjectId === true ||
1271
+ this.s.collection.s.db.options?.forceServerObjectId === true
1272
+ );
1273
+ }
1271
1274
  }
1272
1275
 
1273
1276
  Object.defineProperty(BulkOperationBase.prototype, 'length', {
@@ -1277,18 +1280,6 @@ Object.defineProperty(BulkOperationBase.prototype, 'length', {
1277
1280
  }
1278
1281
  });
1279
1282
 
1280
- function shouldForceServerObjectId(bulkOperation: BulkOperationBase): boolean {
1281
- if (typeof bulkOperation.s.options.forceServerObjectId === 'boolean') {
1282
- return bulkOperation.s.options.forceServerObjectId;
1283
- }
1284
-
1285
- if (typeof bulkOperation.s.collection.s.db.options?.forceServerObjectId === 'boolean') {
1286
- return bulkOperation.s.collection.s.db.options?.forceServerObjectId;
1287
- }
1288
-
1289
- return false;
1290
- }
1291
-
1292
1283
  function isInsertBatch(batch: Batch): boolean {
1293
1284
  return batch.batchType === BatchType.INSERT;
1294
1285
  }
@@ -1,7 +1,7 @@
1
1
  import type { BSONSerializeOptions, Document, Long } from '../bson';
2
2
  import * as BSON from '../bson';
3
3
  import { MongoInvalidArgumentError, MongoRuntimeError } from '../error';
4
- import { ReadPreference } from '../read_preference';
4
+ import { type ReadPreference } from '../read_preference';
5
5
  import type { ClientSession } from '../sessions';
6
6
  import type { CommandOptions } from './connection';
7
7
  import {
@@ -51,7 +51,6 @@ export interface OpQueryOptions extends CommandOptions {
51
51
  requestId?: number;
52
52
  moreToCome?: boolean;
53
53
  exhaustAllowed?: boolean;
54
- readPreference?: ReadPreference;
55
54
  }
56
55
 
57
56
  /**************************************************************
@@ -77,7 +76,6 @@ export class OpQueryRequest {
77
76
  awaitData: boolean;
78
77
  exhaust: boolean;
79
78
  partial: boolean;
80
- documentsReturnedIn?: string;
81
79
 
82
80
  constructor(public databaseName: string, public query: Document, options: OpQueryOptions) {
83
81
  // Basic options needed to be passed in
@@ -503,10 +501,6 @@ export class OpMsgRequest {
503
501
  // Basic options
504
502
  this.command.$db = databaseName;
505
503
 
506
- if (options.readPreference && options.readPreference.mode !== ReadPreference.PRIMARY) {
507
- this.command.$readPreference = options.readPreference.toJSON();
508
- }
509
-
510
504
  // Ensure empty options
511
505
  this.options = options ?? {};
512
506
 
@@ -25,7 +25,6 @@ import {
25
25
  type ConnectionOptions,
26
26
  CryptoConnection
27
27
  } from './connection';
28
- import type { ClientMetadata } from './handshake/client_metadata';
29
28
  import {
30
29
  MAX_SUPPORTED_SERVER_VERSION,
31
30
  MAX_SUPPORTED_WIRE_VERSION,
@@ -44,7 +43,7 @@ export async function connect(options: ConnectionOptions): Promise<Connection> {
44
43
  await performInitialHandshake(connection, options);
45
44
  return connection;
46
45
  } catch (error) {
47
- connection?.destroy({ force: false });
46
+ connection?.destroy();
48
47
  throw error;
49
48
  }
50
49
  }
@@ -183,7 +182,7 @@ export interface HandshakeDocument extends Document {
183
182
  ismaster?: boolean;
184
183
  hello?: boolean;
185
184
  helloOk?: boolean;
186
- client: ClientMetadata;
185
+ client: Document;
187
186
  compression: string[];
188
187
  saslSupportedMechs?: string;
189
188
  loadBalanced?: boolean;
@@ -200,11 +199,12 @@ export async function prepareHandshakeDocument(
200
199
  const options = authContext.options;
201
200
  const compressors = options.compressors ? options.compressors : [];
202
201
  const { serverApi } = authContext.connection;
202
+ const clientMetadata: Document = await options.extendedMetadata;
203
203
 
204
204
  const handshakeDoc: HandshakeDocument = {
205
205
  [serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
206
206
  helloOk: true,
207
- client: options.metadata,
207
+ client: clientMetadata,
208
208
  compression: compressors
209
209
  };
210
210
 
@@ -319,7 +319,6 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
319
319
  const useTLS = options.tls ?? false;
320
320
  const noDelay = options.noDelay ?? true;
321
321
  const connectTimeoutMS = options.connectTimeoutMS ?? 30000;
322
- const rejectUnauthorized = options.rejectUnauthorized ?? true;
323
322
  const existingSocket = options.existingSocket;
324
323
 
325
324
  let socket: Stream;
@@ -375,10 +374,6 @@ export async function makeSocket(options: MakeConnectionOptions): Promise<Stream
375
374
  return socket;
376
375
  } catch (error) {
377
376
  socket.destroy();
378
- if ('authorizationError' in socket && socket.authorizationError != null && rejectUnauthorized) {
379
- // TODO(NODE-5192): wrap this with a MongoError subclass
380
- throw socket.authorizationError;
381
- }
382
377
  throw error;
383
378
  } finally {
384
379
  socket.setTimeout(0);
@@ -1,6 +1,5 @@
1
1
  import { type Readable, Transform, type TransformCallback } from 'stream';
2
2
  import { clearTimeout, setTimeout } from 'timers';
3
- import { promisify } from 'util';
4
3
 
5
4
  import type { BSONSerializeOptions, Document, ObjectId } from '../bson';
6
5
  import type { AutoEncrypter } from '../client-side-encryption/auto_encrypter';
@@ -27,7 +26,8 @@ import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client'
27
26
  import { type MongoClientAuthProviders } from '../mongo_client_auth_providers';
28
27
  import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger';
29
28
  import { type CancellationToken, TypedEventEmitter } from '../mongo_types';
30
- import type { ReadPreferenceLike } from '../read_preference';
29
+ import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
30
+ import { ServerType } from '../sdam/common';
31
31
  import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
32
32
  import {
33
33
  BufferPool,
@@ -37,7 +37,7 @@ import {
37
37
  maxWireVersion,
38
38
  type MongoDBNamespace,
39
39
  now,
40
- promiseWithResolvers,
40
+ once,
41
41
  uuidV4
42
42
  } from '../utils';
43
43
  import type { WriteConcern } from '../write_concern';
@@ -84,6 +84,8 @@ export interface CommandOptions extends BSONSerializeOptions {
84
84
  willRetryWrite?: boolean;
85
85
 
86
86
  writeConcern?: WriteConcern;
87
+
88
+ directConnection?: boolean;
87
89
  }
88
90
 
89
91
  /** @public */
@@ -119,15 +121,11 @@ export interface ConnectionOptions
119
121
  cancellationToken?: CancellationToken;
120
122
  metadata: ClientMetadata;
121
123
  /** @internal */
124
+ extendedMetadata: Promise<Document>;
125
+ /** @internal */
122
126
  mongoLogger?: MongoLogger | undefined;
123
127
  }
124
128
 
125
- /** @internal */
126
- export interface DestroyOptions {
127
- /** Force the destruction. */
128
- force: boolean;
129
- }
130
-
131
129
  /** @public */
132
130
  export type ConnectionEvents = {
133
131
  commandStarted(event: CommandStartedEvent): void;
@@ -180,18 +178,18 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
180
178
  * Once connection is established, command logging can log events (if enabled)
181
179
  */
182
180
  public established: boolean;
181
+ /** Indicates that the connection (including underlying TCP socket) has been closed. */
182
+ public closed = false;
183
183
 
184
184
  private lastUseTime: number;
185
185
  private clusterTime: Document | null = null;
186
+ private error: Error | null = null;
187
+ private dataEvents: AsyncGenerator<Buffer, void, void> | null = null;
186
188
 
187
189
  private readonly socketTimeoutMS: number;
188
190
  private readonly monitorCommands: boolean;
189
191
  private readonly socket: Stream;
190
- private readonly controller: AbortController;
191
- private readonly signal: AbortSignal;
192
192
  private readonly messageStream: Readable;
193
- private readonly socketWrite: (buffer: Uint8Array) => Promise<void>;
194
- private readonly aborted: Promise<never>;
195
193
 
196
194
  /** @event */
197
195
  static readonly COMMAND_STARTED = COMMAND_STARTED;
@@ -211,6 +209,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
211
209
  constructor(stream: Stream, options: ConnectionOptions) {
212
210
  super();
213
211
 
212
+ this.socket = stream;
214
213
  this.id = options.id;
215
214
  this.address = streamIdentifier(stream, options);
216
215
  this.socketTimeoutMS = options.socketTimeoutMS ?? 0;
@@ -223,39 +222,12 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
223
222
  this.generation = options.generation;
224
223
  this.lastUseTime = now();
225
224
 
226
- this.socket = stream;
227
-
228
- // TODO: Remove signal from connection layer
229
- this.controller = new AbortController();
230
- const { signal } = this.controller;
231
- this.signal = signal;
232
- const { promise: aborted, reject } = promiseWithResolvers<never>();
233
- aborted.then(undefined, () => null); // Prevent unhandled rejection
234
- this.signal.addEventListener(
235
- 'abort',
236
- function onAbort() {
237
- reject(signal.reason);
238
- },
239
- { once: true }
240
- );
241
- this.aborted = aborted;
242
-
243
225
  this.messageStream = this.socket
244
226
  .on('error', this.onError.bind(this))
245
227
  .pipe(new SizedMessageTransform({ connection: this }))
246
228
  .on('error', this.onError.bind(this));
247
229
  this.socket.on('close', this.onClose.bind(this));
248
230
  this.socket.on('timeout', this.onTimeout.bind(this));
249
-
250
- const socketWrite = promisify(this.socket.write.bind(this.socket));
251
- this.socketWrite = async buffer => {
252
- return Promise.race([socketWrite(buffer), this.aborted]);
253
- };
254
- }
255
-
256
- /** Indicates that the connection (including underlying TCP socket) has been closed. */
257
- public get closed(): boolean {
258
- return this.signal.aborted;
259
231
  }
260
232
 
261
233
  public get hello() {
@@ -306,7 +278,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
306
278
  this.lastUseTime = now();
307
279
  }
308
280
 
309
- public onError(error?: Error) {
281
+ public onError(error: Error) {
310
282
  this.cleanup(error);
311
283
  }
312
284
 
@@ -323,14 +295,10 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
323
295
  }, 1).unref(); // No need for this timer to hold the event loop open
324
296
  }
325
297
 
326
- public destroy(options: DestroyOptions, callback?: Callback): void {
298
+ public destroy(): void {
327
299
  if (this.closed) {
328
- if (typeof callback === 'function') process.nextTick(callback);
329
300
  return;
330
301
  }
331
- if (typeof callback === 'function') {
332
- this.once('close', () => process.nextTick(() => callback()));
333
- }
334
302
 
335
303
  // load balanced mode requires that these listeners remain on the connection
336
304
  // after cleanup on timeouts, errors or close so we remove them before calling
@@ -349,13 +317,15 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
349
317
  *
350
318
  * This method does nothing if the connection is already closed.
351
319
  */
352
- private cleanup(error?: Error): void {
320
+ private cleanup(error: Error): void {
353
321
  if (this.closed) {
354
322
  return;
355
323
  }
356
324
 
357
325
  this.socket.destroy();
358
- this.controller.abort(error);
326
+ this.error = error;
327
+ this.dataEvents?.throw(error).then(undefined, () => null); // squash unhandled rejection
328
+ this.closed = true;
359
329
  this.emit(Connection.CLOSE);
360
330
  }
361
331
 
@@ -394,16 +364,34 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
394
364
  cmd.$clusterTime = clusterTime;
395
365
  }
396
366
 
397
- if (
398
- isSharded(this) &&
399
- !this.supportsOpMsg &&
400
- readPreference &&
401
- readPreference.mode !== 'primary'
402
- ) {
403
- cmd = {
404
- $query: cmd,
405
- $readPreference: readPreference.toJSON()
406
- };
367
+ // For standalone, drivers MUST NOT set $readPreference.
368
+ if (this.description.type !== ServerType.Standalone) {
369
+ if (
370
+ !isSharded(this) &&
371
+ !this.description.loadBalanced &&
372
+ this.supportsOpMsg &&
373
+ options.directConnection === true &&
374
+ readPreference?.mode === 'primary'
375
+ ) {
376
+ // For mongos and load balancers with 'primary' mode, drivers MUST NOT set $readPreference.
377
+ // For all other types with a direct connection, if the read preference is 'primary'
378
+ // (driver sets 'primary' as default if no read preference is configured),
379
+ // the $readPreference MUST be set to 'primaryPreferred'
380
+ // to ensure that any server type can handle the request.
381
+ cmd.$readPreference = ReadPreference.primaryPreferred.toJSON();
382
+ } else if (isSharded(this) && !this.supportsOpMsg && readPreference?.mode !== 'primary') {
383
+ // When sending a read operation via OP_QUERY and the $readPreference modifier,
384
+ // the query MUST be provided using the $query modifier.
385
+ cmd = {
386
+ $query: cmd,
387
+ $readPreference: readPreference.toJSON()
388
+ };
389
+ } else if (readPreference?.mode !== 'primary') {
390
+ // For mode 'primary', drivers MUST NOT set $readPreference.
391
+ // For all other read preference modes (i.e. 'secondary', 'primaryPreferred', ...),
392
+ // drivers MUST set $readPreference
393
+ cmd.$readPreference = readPreference.toJSON();
394
+ }
407
395
  }
408
396
 
409
397
  const commandOptions = {
@@ -412,8 +400,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
412
400
  checkKeys: false,
413
401
  // This value is not overridable
414
402
  secondaryOk: readPreference.secondaryOk(),
415
- ...options,
416
- readPreference // ensure we pass in ReadPreference instance
403
+ ...options
417
404
  };
418
405
 
419
406
  const message = this.supportsOpMsg
@@ -596,7 +583,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
596
583
  }
597
584
 
598
585
  private throwIfAborted() {
599
- this.signal.throwIfAborted();
586
+ if (this.error) throw this.error;
600
587
  }
601
588
 
602
589
  /**
@@ -619,7 +606,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
619
606
 
620
607
  const buffer = Buffer.concat(await finalCommand.toBin());
621
608
 
622
- return this.socketWrite(buffer);
609
+ if (this.socket.write(buffer)) return;
610
+ return once(this.socket, 'drain');
623
611
  }
624
612
 
625
613
  /**
@@ -632,13 +620,19 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
632
620
  * Note that `for-await` loops call `return` automatically when the loop is exited.
633
621
  */
634
622
  private async *readMany(): AsyncGenerator<OpMsgResponse | OpQueryResponse> {
635
- for await (const message of onData(this.messageStream, { signal: this.signal })) {
636
- const response = await decompressResponse(message);
637
- yield response;
623
+ try {
624
+ this.dataEvents = onData(this.messageStream);
625
+ for await (const message of this.dataEvents) {
626
+ const response = await decompressResponse(message);
627
+ yield response;
638
628
 
639
- if (!response.moreToCome) {
640
- return;
629
+ if (!response.moreToCome) {
630
+ return;
631
+ }
641
632
  }
633
+ } finally {
634
+ this.dataEvents = null;
635
+ this.throwIfAborted();
642
636
  }
643
637
  }
644
638
  }
@@ -28,7 +28,6 @@ import { CancellationToken, TypedEventEmitter } from '../mongo_types';
28
28
  import type { Server } from '../sdam/server';
29
29
  import {
30
30
  type Callback,
31
- eachAsync,
32
31
  List,
33
32
  makeCounter,
34
33
  promiseWithResolvers,
@@ -119,7 +118,11 @@ export const PoolState = Object.freeze({
119
118
  closed: 'closed'
120
119
  } as const);
121
120
 
122
- /** @public */
121
+ /**
122
+ * @public
123
+ * @deprecated This interface is deprecated and will be removed in a future release as it is not used
124
+ * in the driver
125
+ */
123
126
  export interface CloseOptions {
124
127
  force?: boolean;
125
128
  }
@@ -233,8 +236,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
233
236
  maxIdleTimeMS: options.maxIdleTimeMS ?? 0,
234
237
  waitQueueTimeoutMS: options.waitQueueTimeoutMS ?? 0,
235
238
  minPoolSizeCheckFrequencyMS: options.minPoolSizeCheckFrequencyMS ?? 100,
236
- autoEncrypter: options.autoEncrypter,
237
- metadata: options.metadata
239
+ autoEncrypter: options.autoEncrypter
238
240
  });
239
241
 
240
242
  if (this.options.minPoolSize > this.options.maxPoolSize) {
@@ -494,25 +496,16 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
494
496
  private interruptInUseConnections(minGeneration: number) {
495
497
  for (const connection of this[kCheckedOut]) {
496
498
  if (connection.generation <= minGeneration) {
497
- this.checkIn(connection);
498
499
  connection.onError(new PoolClearedOnNetworkError(this));
500
+ this.checkIn(connection);
499
501
  }
500
502
  }
501
503
  }
502
504
 
503
505
  /** Close the pool */
504
- close(callback: Callback<void>): void;
505
- close(options: CloseOptions, callback: Callback<void>): void;
506
- close(_options?: CloseOptions | Callback<void>, _cb?: Callback<void>): void {
507
- let options = _options as CloseOptions;
508
- const callback = (_cb ?? _options) as Callback<void>;
509
- if (typeof options === 'function') {
510
- options = {};
511
- }
512
-
513
- options = Object.assign({ force: false }, options);
506
+ close(): void {
514
507
  if (this.closed) {
515
- return callback();
508
+ return;
516
509
  }
517
510
 
518
511
  // immediately cancel any in-flight connections
@@ -527,21 +520,15 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
527
520
  this.clearMinPoolSizeTimer();
528
521
  this.processWaitQueue();
529
522
 
530
- eachAsync<Connection>(
531
- this[kConnections].toArray(),
532
- (conn, cb) => {
533
- this.emitAndLog(
534
- ConnectionPool.CONNECTION_CLOSED,
535
- new ConnectionClosedEvent(this, conn, 'poolClosed')
536
- );
537
- conn.destroy({ force: !!options.force }, cb);
538
- },
539
- err => {
540
- this[kConnections].clear();
541
- this.emitAndLog(ConnectionPool.CONNECTION_POOL_CLOSED, new ConnectionPoolClosedEvent(this));
542
- callback(err);
543
- }
544
- );
523
+ for (const conn of this[kConnections]) {
524
+ this.emitAndLog(
525
+ ConnectionPool.CONNECTION_CLOSED,
526
+ new ConnectionClosedEvent(this, conn, 'poolClosed')
527
+ );
528
+ conn.destroy();
529
+ }
530
+ this[kConnections].clear();
531
+ this.emitAndLog(ConnectionPool.CONNECTION_POOL_CLOSED, new ConnectionPoolClosedEvent(this));
545
532
  }
546
533
 
547
534
  /**
@@ -593,7 +580,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
593
580
  new ConnectionClosedEvent(this, connection, reason)
594
581
  );
595
582
  // destroy the connection
596
- process.nextTick(() => connection.destroy({ force: false }));
583
+ connection.destroy();
597
584
  }
598
585
 
599
586
  private connectionIsStale(connection: Connection) {
@@ -649,7 +636,7 @@ export class ConnectionPool extends TypedEventEmitter<ConnectionPoolEvents> {
649
636
  // The pool might have closed since we started trying to create a connection
650
637
  if (this[kPoolState] !== PoolState.ready) {
651
638
  this[kPending]--;
652
- connection.destroy({ force: true });
639
+ connection.destroy();
653
640
  callback(this.closed ? new PoolClosedError(this) : new PoolClearedError(this));
654
641
  return;
655
642
  }
@@ -1,7 +1,8 @@
1
+ import { promises as fs } from 'fs';
1
2
  import * as os from 'os';
2
3
  import * as process from 'process';
3
4
 
4
- import { BSON, Int32 } from '../../bson';
5
+ import { BSON, type Document, Int32 } from '../../bson';
5
6
  import { MongoInvalidArgumentError } from '../../error';
6
7
  import type { MongoOptions } from '../../mongo_client';
7
8
 
@@ -71,13 +72,13 @@ export class LimitedSizeDocument {
71
72
  return true;
72
73
  }
73
74
 
74
- toObject(): ClientMetadata {
75
+ toObject(): Document {
75
76
  return BSON.deserialize(BSON.serialize(this.document), {
76
77
  promoteLongs: false,
77
78
  promoteBuffers: false,
78
79
  promoteValues: false,
79
80
  useBigInt64: false
80
- }) as ClientMetadata;
81
+ });
81
82
  }
82
83
  }
83
84
 
@@ -152,8 +153,57 @@ export function makeClientMetadata(options: MakeClientMetadataOptions): ClientMe
152
153
  }
153
154
  }
154
155
  }
156
+ return metadataDocument.toObject() as ClientMetadata;
157
+ }
158
+
159
+ let dockerPromise: Promise<boolean>;
160
+ /** @internal */
161
+ async function getContainerMetadata() {
162
+ const containerMetadata: Record<string, any> = {};
163
+ dockerPromise ??= fs.access('/.dockerenv').then(
164
+ () => true,
165
+ () => false
166
+ );
167
+ const isDocker = await dockerPromise;
168
+
169
+ const { KUBERNETES_SERVICE_HOST = '' } = process.env;
170
+ const isKubernetes = KUBERNETES_SERVICE_HOST.length > 0 ? true : false;
171
+
172
+ if (isDocker) containerMetadata.runtime = 'docker';
173
+ if (isKubernetes) containerMetadata.orchestrator = 'kubernetes';
174
+
175
+ return containerMetadata;
176
+ }
177
+
178
+ /**
179
+ * @internal
180
+ * Re-add each metadata value.
181
+ * Attempt to add new env container metadata, but keep old data if it does not fit.
182
+ */
183
+ export async function addContainerMetadata(originalMetadata: ClientMetadata) {
184
+ const containerMetadata = await getContainerMetadata();
185
+ if (Object.keys(containerMetadata).length === 0) return originalMetadata;
186
+
187
+ const extendedMetadata = new LimitedSizeDocument(512);
188
+
189
+ const extendedEnvMetadata = { ...originalMetadata?.env, container: containerMetadata };
190
+
191
+ for (const [key, val] of Object.entries(originalMetadata)) {
192
+ if (key !== 'env') {
193
+ extendedMetadata.ifItFitsItSits(key, val);
194
+ } else {
195
+ if (!extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata)) {
196
+ // add in old data if newer / extended metadata does not fit
197
+ extendedMetadata.ifItFitsItSits('env', val);
198
+ }
199
+ }
200
+ }
201
+
202
+ if (!('env' in originalMetadata)) {
203
+ extendedMetadata.ifItFitsItSits('env', extendedEnvMetadata);
204
+ }
155
205
 
156
- return metadataDocument.toObject();
206
+ return extendedMetadata.toObject();
157
207
  }
158
208
 
159
209
  /**
@@ -22,7 +22,7 @@ export interface StreamDescriptionOptions {
22
22
  /** @public */
23
23
  export class StreamDescription {
24
24
  address: string;
25
- type: string;
25
+ type: ServerType;
26
26
  minWireVersion?: number;
27
27
  maxWireVersion?: number;
28
28
  maxBsonObjectSize: number;