mongodb 6.12.0-dev.20250124.sha.70d476aa → 6.12.0-dev.20250128.sha.654069fc

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 (65) hide show
  1. package/lib/beta.d.ts +63 -13
  2. package/lib/client-side-encryption/auto_encrypter.js +4 -2
  3. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  4. package/lib/client-side-encryption/client_encryption.js +4 -4
  5. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  6. package/lib/client-side-encryption/state_machine.js +56 -30
  7. package/lib/client-side-encryption/state_machine.js.map +1 -1
  8. package/lib/cmap/connection.js +28 -22
  9. package/lib/cmap/connection.js.map +1 -1
  10. package/lib/cmap/connection_pool.js +5 -0
  11. package/lib/cmap/connection_pool.js.map +1 -1
  12. package/lib/cmap/wire_protocol/on_data.js +6 -1
  13. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  14. package/lib/collection.js.map +1 -1
  15. package/lib/connection_string.js +20 -2
  16. package/lib/connection_string.js.map +1 -1
  17. package/lib/cursor/abstract_cursor.js +47 -18
  18. package/lib/cursor/abstract_cursor.js.map +1 -1
  19. package/lib/cursor/aggregation_cursor.js +2 -1
  20. package/lib/cursor/aggregation_cursor.js.map +1 -1
  21. package/lib/cursor/find_cursor.js +2 -1
  22. package/lib/cursor/find_cursor.js.map +1 -1
  23. package/lib/cursor/list_collections_cursor.js +2 -1
  24. package/lib/cursor/list_collections_cursor.js.map +1 -1
  25. package/lib/db.js +2 -1
  26. package/lib/db.js.map +1 -1
  27. package/lib/mongo_client.js +12 -0
  28. package/lib/mongo_client.js.map +1 -1
  29. package/lib/mongo_logger.js +96 -1
  30. package/lib/mongo_logger.js.map +1 -1
  31. package/lib/operations/execute_operation.js +7 -3
  32. package/lib/operations/execute_operation.js.map +1 -1
  33. package/lib/operations/list_collections.js.map +1 -1
  34. package/lib/operations/operation.js.map +1 -1
  35. package/lib/sdam/server.js +26 -16
  36. package/lib/sdam/server.js.map +1 -1
  37. package/lib/sdam/topology.js +5 -0
  38. package/lib/sdam/topology.js.map +1 -1
  39. package/lib/utils.js +64 -7
  40. package/lib/utils.js.map +1 -1
  41. package/mongodb.d.ts +63 -13
  42. package/package.json +1 -1
  43. package/src/client-side-encryption/auto_encrypter.ts +12 -8
  44. package/src/client-side-encryption/client_encryption.ts +6 -4
  45. package/src/client-side-encryption/state_machine.ts +80 -36
  46. package/src/cmap/connection.ts +37 -29
  47. package/src/cmap/connection_pool.ts +17 -3
  48. package/src/cmap/wire_protocol/on_data.ts +9 -2
  49. package/src/collection.ts +15 -8
  50. package/src/connection_string.ts +24 -4
  51. package/src/cursor/abstract_cursor.ts +71 -23
  52. package/src/cursor/aggregation_cursor.ts +5 -3
  53. package/src/cursor/find_cursor.ts +5 -3
  54. package/src/cursor/list_collections_cursor.ts +5 -3
  55. package/src/db.ts +11 -7
  56. package/src/index.ts +1 -0
  57. package/src/mongo_client.ts +21 -0
  58. package/src/mongo_logger.ts +128 -2
  59. package/src/mongo_types.ts +38 -0
  60. package/src/operations/execute_operation.ts +9 -4
  61. package/src/operations/list_collections.ts +4 -1
  62. package/src/operations/operation.ts +3 -2
  63. package/src/sdam/server.ts +31 -18
  64. package/src/sdam/topology.ts +10 -2
  65. package/src/utils.ts +79 -6
@@ -12,7 +12,7 @@ import {
12
12
  MongoTailableCursorError
13
13
  } from '../error';
14
14
  import type { MongoClient } from '../mongo_client';
15
- import { TypedEventEmitter } from '../mongo_types';
15
+ import { type Abortable, TypedEventEmitter } from '../mongo_types';
16
16
  import { executeOperation } from '../operations/execute_operation';
17
17
  import { GetMoreOperation } from '../operations/get_more';
18
18
  import { KillCursorsOperation } from '../operations/kill_cursors';
@@ -22,7 +22,13 @@ import { type AsyncDisposable, configureResourceManagement } from '../resource_m
22
22
  import type { Server } from '../sdam/server';
23
23
  import { ClientSession, maybeClearPinnedConnection } from '../sessions';
24
24
  import { type CSOTTimeoutContext, type Timeout, TimeoutContext } from '../timeout';
25
- import { type MongoDBNamespace, squashError } from '../utils';
25
+ import {
26
+ addAbortListener,
27
+ type Disposable,
28
+ kDispose,
29
+ type MongoDBNamespace,
30
+ squashError
31
+ } from '../utils';
26
32
 
27
33
  /**
28
34
  * @internal
@@ -61,6 +67,10 @@ export interface CursorStreamOptions {
61
67
  /** @public */
62
68
  export type CursorFlag = (typeof CURSOR_FLAGS)[number];
63
69
 
70
+ function removeActiveCursor(this: AbstractCursor) {
71
+ this.client.s.activeCursors.delete(this);
72
+ }
73
+
64
74
  /**
65
75
  * @public
66
76
  * @experimental
@@ -247,12 +257,14 @@ export abstract class AbstractCursor<
247
257
 
248
258
  /** @internal */
249
259
  protected deserializationOptions: OnDemandDocumentDeserializeOptions;
260
+ protected signal: AbortSignal | undefined;
261
+ private abortListener: Disposable | undefined;
250
262
 
251
263
  /** @internal */
252
264
  protected constructor(
253
265
  client: MongoClient,
254
266
  namespace: MongoDBNamespace,
255
- options: AbstractCursorOptions = {}
267
+ options: AbstractCursorOptions & Abortable = {}
256
268
  ) {
257
269
  super();
258
270
 
@@ -352,6 +364,12 @@ export abstract class AbstractCursor<
352
364
  };
353
365
 
354
366
  this.timeoutContext = options.timeoutContext;
367
+ this.signal = options.signal;
368
+ this.abortListener = addAbortListener(
369
+ this.signal,
370
+ () => void this.close().then(undefined, squashError)
371
+ );
372
+ this.trackCursor();
355
373
  }
356
374
 
357
375
  /**
@@ -431,6 +449,14 @@ export abstract class AbstractCursor<
431
449
  await this.close();
432
450
  }
433
451
 
452
+ /** Adds cursor to client's tracking so it will be closed by MongoClient.close() */
453
+ private trackCursor() {
454
+ this.cursorClient.s.activeCursors.add(this);
455
+ if (!this.listeners('close').includes(removeActiveCursor)) {
456
+ this.once('close', removeActiveCursor);
457
+ }
458
+ }
459
+
434
460
  /** Returns current buffered documents length */
435
461
  bufferedCount(): number {
436
462
  return this.documents?.length ?? 0;
@@ -455,6 +481,8 @@ export abstract class AbstractCursor<
455
481
  }
456
482
 
457
483
  async *[Symbol.asyncIterator](): AsyncGenerator<TSchema, void, void> {
484
+ this.signal?.throwIfAborted();
485
+
458
486
  if (this.closed) {
459
487
  return;
460
488
  }
@@ -481,6 +509,8 @@ export abstract class AbstractCursor<
481
509
  }
482
510
 
483
511
  yield document;
512
+
513
+ this.signal?.throwIfAborted();
484
514
  }
485
515
  } finally {
486
516
  // Only close the cursor if it has not already been closed. This finally clause handles
@@ -496,9 +526,16 @@ export abstract class AbstractCursor<
496
526
  }
497
527
 
498
528
  stream(options?: CursorStreamOptions): Readable & AsyncIterable<TSchema> {
529
+ const readable = new ReadableCursorStream(this);
530
+ const abortListener = addAbortListener(this.signal, function () {
531
+ readable.destroy(this.reason);
532
+ });
533
+ readable.once('end', () => {
534
+ abortListener?.[kDispose]();
535
+ });
536
+
499
537
  if (options?.transform) {
500
538
  const transform = options.transform;
501
- const readable = new ReadableCursorStream(this);
502
539
 
503
540
  const transformedStream = readable.pipe(
504
541
  new Transform({
@@ -522,10 +559,12 @@ export abstract class AbstractCursor<
522
559
  return transformedStream;
523
560
  }
524
561
 
525
- return new ReadableCursorStream(this);
562
+ return readable;
526
563
  }
527
564
 
528
565
  async hasNext(): Promise<boolean> {
566
+ this.signal?.throwIfAborted();
567
+
529
568
  if (this.cursorId === Long.ZERO) {
530
569
  return false;
531
570
  }
@@ -551,6 +590,8 @@ export abstract class AbstractCursor<
551
590
 
552
591
  /** Get the next available document from the cursor, returns null if no more documents are available. */
553
592
  async next(): Promise<TSchema | null> {
593
+ this.signal?.throwIfAborted();
594
+
554
595
  if (this.cursorId === Long.ZERO) {
555
596
  throw new MongoCursorExhaustedError();
556
597
  }
@@ -581,6 +622,8 @@ export abstract class AbstractCursor<
581
622
  * Try to get the next available document from the cursor or `null` if an empty batch is returned
582
623
  */
583
624
  async tryNext(): Promise<TSchema | null> {
625
+ this.signal?.throwIfAborted();
626
+
584
627
  if (this.cursorId === Long.ZERO) {
585
628
  throw new MongoCursorExhaustedError();
586
629
  }
@@ -620,6 +663,8 @@ export abstract class AbstractCursor<
620
663
  * @deprecated - Will be removed in a future release. Use for await...of instead.
621
664
  */
622
665
  async forEach(iterator: (doc: TSchema) => boolean | void): Promise<void> {
666
+ this.signal?.throwIfAborted();
667
+
623
668
  if (typeof iterator !== 'function') {
624
669
  throw new MongoInvalidArgumentError('Argument "iterator" must be a function');
625
670
  }
@@ -645,6 +690,8 @@ export abstract class AbstractCursor<
645
690
  * cursor.rewind() can be used to reset the cursor.
646
691
  */
647
692
  async toArray(): Promise<TSchema[]> {
693
+ this.signal?.throwIfAborted();
694
+
648
695
  const array: TSchema[] = [];
649
696
  // at the end of the loop (since readBufferedDocuments is called) the buffer will be empty
650
697
  // then, the 'await of' syntax will run a getMore call
@@ -824,16 +871,15 @@ export abstract class AbstractCursor<
824
871
  this.isClosed = false;
825
872
  this.isKilled = false;
826
873
  this.initialized = false;
874
+ this.hasEmittedClose = false;
875
+ this.trackCursor();
827
876
 
828
- const session = this.cursorSession;
829
- if (session) {
830
- // We only want to end this session if we created it, and it hasn't ended yet
831
- if (session.explicit === false) {
832
- if (!session.hasEnded) {
833
- session.endSession().then(undefined, squashError);
834
- }
835
- this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
877
+ // We only want to end this session if we created it, and it hasn't ended yet
878
+ if (this.cursorSession.explicit === false) {
879
+ if (!this.cursorSession.hasEnded) {
880
+ this.cursorSession.endSession().then(undefined, squashError);
836
881
  }
882
+ this.cursorSession = this.cursorClient.startSession({ owner: this, explicit: false });
837
883
  }
838
884
  }
839
885
 
@@ -968,8 +1014,8 @@ export abstract class AbstractCursor<
968
1014
 
969
1015
  /** @internal */
970
1016
  private async cleanup(timeoutMS?: number, error?: Error) {
1017
+ this.abortListener?.[kDispose]();
971
1018
  this.isClosed = true;
972
- const session = this.cursorSession;
973
1019
  const timeoutContextForKillCursors = (): CursorTimeoutContext | undefined => {
974
1020
  if (timeoutMS != null) {
975
1021
  this.timeoutContext?.clear();
@@ -991,7 +1037,7 @@ export abstract class AbstractCursor<
991
1037
  !this.cursorId.isZero() &&
992
1038
  this.cursorNamespace &&
993
1039
  this.selectedServer &&
994
- !session.hasEnded
1040
+ !this.cursorSession.hasEnded
995
1041
  ) {
996
1042
  this.isKilled = true;
997
1043
  const cursorId = this.cursorId;
@@ -1000,7 +1046,7 @@ export abstract class AbstractCursor<
1000
1046
  await executeOperation(
1001
1047
  this.cursorClient,
1002
1048
  new KillCursorsOperation(cursorId, this.cursorNamespace, this.selectedServer, {
1003
- session
1049
+ session: this.cursorSession
1004
1050
  }),
1005
1051
  timeoutContextForKillCursors()
1006
1052
  );
@@ -1008,14 +1054,16 @@ export abstract class AbstractCursor<
1008
1054
  } catch (error) {
1009
1055
  squashError(error);
1010
1056
  } finally {
1011
- if (session?.owner === this) {
1012
- await session.endSession({ error });
1013
- }
1014
- if (!session?.inTransaction()) {
1015
- maybeClearPinnedConnection(session, { error });
1057
+ try {
1058
+ if (this.cursorSession?.owner === this) {
1059
+ await this.cursorSession.endSession({ error });
1060
+ }
1061
+ if (!this.cursorSession?.inTransaction()) {
1062
+ maybeClearPinnedConnection(this.cursorSession, { error });
1063
+ }
1064
+ } finally {
1065
+ this.emitClose();
1016
1066
  }
1017
-
1018
- this.emitClose();
1019
1067
  }
1020
1068
  }
1021
1069
 
@@ -8,6 +8,7 @@ import {
8
8
  validateExplainTimeoutOptions
9
9
  } from '../explain';
10
10
  import type { MongoClient } from '../mongo_client';
11
+ import { type Abortable } from '../mongo_types';
11
12
  import { AggregateOperation, type AggregateOptions } from '../operations/aggregate';
12
13
  import { executeOperation } from '../operations/execute_operation';
13
14
  import type { ClientSession } from '../sessions';
@@ -32,14 +33,14 @@ export interface AggregationCursorOptions extends AbstractCursorOptions, Aggrega
32
33
  export class AggregationCursor<TSchema = any> extends ExplainableCursor<TSchema> {
33
34
  public readonly pipeline: Document[];
34
35
  /** @internal */
35
- private aggregateOptions: AggregateOptions;
36
+ private aggregateOptions: AggregateOptions & Abortable;
36
37
 
37
38
  /** @internal */
38
39
  constructor(
39
40
  client: MongoClient,
40
41
  namespace: MongoDBNamespace,
41
42
  pipeline: Document[] = [],
42
- options: AggregateOptions = {}
43
+ options: AggregateOptions & Abortable = {}
43
44
  ) {
44
45
  super(client, namespace, options);
45
46
 
@@ -73,7 +74,8 @@ export class AggregationCursor<TSchema = any> extends ExplainableCursor<TSchema>
73
74
  const options = {
74
75
  ...this.aggregateOptions,
75
76
  ...this.cursorOptions,
76
- session
77
+ session,
78
+ signal: this.signal
77
79
  };
78
80
  if (options.explain) {
79
81
  try {
@@ -9,6 +9,7 @@ import {
9
9
  validateExplainTimeoutOptions
10
10
  } from '../explain';
11
11
  import type { MongoClient } from '../mongo_client';
12
+ import { type Abortable } from '../mongo_types';
12
13
  import type { CollationOptions } from '../operations/command';
13
14
  import { CountOperation, type CountOptions } from '../operations/count';
14
15
  import { executeOperation } from '../operations/execute_operation';
@@ -36,14 +37,14 @@ export class FindCursor<TSchema = any> extends ExplainableCursor<TSchema> {
36
37
  /** @internal */
37
38
  private numReturned = 0;
38
39
  /** @internal */
39
- private readonly findOptions: FindOptions;
40
+ private readonly findOptions: FindOptions & Abortable;
40
41
 
41
42
  /** @internal */
42
43
  constructor(
43
44
  client: MongoClient,
44
45
  namespace: MongoDBNamespace,
45
46
  filter: Document = {},
46
- options: FindOptions = {}
47
+ options: FindOptions & Abortable = {}
47
48
  ) {
48
49
  super(client, namespace, options);
49
50
 
@@ -72,7 +73,8 @@ export class FindCursor<TSchema = any> extends ExplainableCursor<TSchema> {
72
73
  const options = {
73
74
  ...this.findOptions, // NOTE: order matters here, we may need to refine this
74
75
  ...this.cursorOptions,
75
- session
76
+ session,
77
+ signal: this.signal
76
78
  };
77
79
 
78
80
  if (options.explain) {
@@ -1,5 +1,6 @@
1
1
  import type { Document } from '../bson';
2
2
  import type { Db } from '../db';
3
+ import { type Abortable } from '../mongo_types';
3
4
  import { executeOperation } from '../operations/execute_operation';
4
5
  import {
5
6
  type CollectionInfo,
@@ -17,9 +18,9 @@ export class ListCollectionsCursor<
17
18
  > extends AbstractCursor<T> {
18
19
  parent: Db;
19
20
  filter: Document;
20
- options?: ListCollectionsOptions;
21
+ options?: ListCollectionsOptions & Abortable;
21
22
 
22
- constructor(db: Db, filter: Document, options?: ListCollectionsOptions) {
23
+ constructor(db: Db, filter: Document, options?: ListCollectionsOptions & Abortable) {
23
24
  super(db.client, db.s.namespace, options);
24
25
  this.parent = db;
25
26
  this.filter = filter;
@@ -38,7 +39,8 @@ export class ListCollectionsCursor<
38
39
  const operation = new ListCollectionsOperation(this.parent, this.filter, {
39
40
  ...this.cursorOptions,
40
41
  ...this.options,
41
- session
42
+ session,
43
+ signal: this.signal
42
44
  });
43
45
 
44
46
  const response = await executeOperation(this.parent.client, operation, this.timeoutContext);
package/src/db.ts CHANGED
@@ -8,7 +8,7 @@ import { ListCollectionsCursor } from './cursor/list_collections_cursor';
8
8
  import { RunCommandCursor, type RunCursorCommandOptions } from './cursor/run_command_cursor';
9
9
  import { MongoInvalidArgumentError } from './error';
10
10
  import type { MongoClient, PkFactory } from './mongo_client';
11
- import type { TODO_NODE_3286 } from './mongo_types';
11
+ import type { Abortable, TODO_NODE_3286 } from './mongo_types';
12
12
  import type { AggregateOptions } from './operations/aggregate';
13
13
  import { CollectionsOperation } from './operations/collections';
14
14
  import {
@@ -273,7 +273,7 @@ export class Db {
273
273
  * @param command - The command to run
274
274
  * @param options - Optional settings for the command
275
275
  */
276
- async command(command: Document, options?: RunCommandOptions): Promise<Document> {
276
+ async command(command: Document, options?: RunCommandOptions & Abortable): Promise<Document> {
277
277
  // Intentionally, we do not inherit options from parent for this operation.
278
278
  return await executeOperation(
279
279
  this.client,
@@ -284,7 +284,8 @@ export class Db {
284
284
  ...resolveBSONOptions(options),
285
285
  timeoutMS: options?.timeoutMS ?? this.timeoutMS,
286
286
  session: options?.session,
287
- readPreference: options?.readPreference
287
+ readPreference: options?.readPreference,
288
+ signal: options?.signal
288
289
  })
289
290
  )
290
291
  );
@@ -351,22 +352,25 @@ export class Db {
351
352
  */
352
353
  listCollections(
353
354
  filter: Document,
354
- options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: true }
355
+ options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: true } & Abortable
355
356
  ): ListCollectionsCursor<Pick<CollectionInfo, 'name' | 'type'>>;
356
357
  listCollections(
357
358
  filter: Document,
358
- options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: false }
359
+ options: Exclude<ListCollectionsOptions, 'nameOnly'> & { nameOnly: false } & Abortable
359
360
  ): ListCollectionsCursor<CollectionInfo>;
360
361
  listCollections<
361
362
  T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
362
363
  | Pick<CollectionInfo, 'name' | 'type'>
363
364
  | CollectionInfo
364
- >(filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor<T>;
365
+ >(filter?: Document, options?: ListCollectionsOptions & Abortable): ListCollectionsCursor<T>;
365
366
  listCollections<
366
367
  T extends Pick<CollectionInfo, 'name' | 'type'> | CollectionInfo =
367
368
  | Pick<CollectionInfo, 'name' | 'type'>
368
369
  | CollectionInfo
369
- >(filter: Document = {}, options: ListCollectionsOptions = {}): ListCollectionsCursor<T> {
370
+ >(
371
+ filter: Document = {},
372
+ options: ListCollectionsOptions & Abortable = {}
373
+ ): ListCollectionsCursor<T> {
370
374
  return new ListCollectionsCursor<T>(this, filter, resolveOptions(this, options));
371
375
  }
372
376
 
package/src/index.ts CHANGED
@@ -430,6 +430,7 @@ export type {
430
430
  SeverityLevel
431
431
  } from './mongo_logger';
432
432
  export type {
433
+ Abortable,
433
434
  CommonEvents,
434
435
  EventsDescription,
435
436
  GenericListener,
@@ -18,6 +18,7 @@ import type { ClientMetadata } from './cmap/handshake/client_metadata';
18
18
  import type { CompressorName } from './cmap/wire_protocol/compression';
19
19
  import { parseOptions, resolveSRVRecord } from './connection_string';
20
20
  import { MONGO_CLIENT_EVENTS } from './constants';
21
+ import { type AbstractCursor } from './cursor/abstract_cursor';
21
22
  import { Db, type DbOptions } from './db';
22
23
  import type { Encrypter } from './encrypter';
23
24
  import { MongoInvalidArgumentError } from './error';
@@ -323,6 +324,12 @@ export interface MongoClientPrivate {
323
324
  * - used to notify the leak checker in our tests if test author forgot to clean up explicit sessions
324
325
  */
325
326
  readonly activeSessions: Set<ClientSession>;
327
+ /**
328
+ * We keep a reference to the cursors that are created from this client.
329
+ * - used to track and close all cursors in client.close().
330
+ * Cursors in this set are ones that still need to have their close method invoked (no other conditions are considered)
331
+ */
332
+ readonly activeCursors: Set<AbstractCursor>;
326
333
  readonly sessionPool: ServerSessionPool;
327
334
  readonly options: MongoOptions;
328
335
  readonly readConcern?: ReadConcern;
@@ -398,6 +405,7 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
398
405
  hasBeenClosed: false,
399
406
  sessionPool: new ServerSessionPool(this),
400
407
  activeSessions: new Set(),
408
+ activeCursors: new Set(),
401
409
  authProviders: new MongoClientAuthProviders(),
402
410
 
403
411
  get options() {
@@ -521,6 +529,10 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
521
529
  * This means the time to setup the `MongoClient` does not count against `timeoutMS`.
522
530
  * If you are using `timeoutMS` we recommend connecting your client explicitly in advance of any operation to avoid this inconsistent execution time.
523
531
  *
532
+ * @remarks
533
+ * The driver will look up corresponding SRV and TXT records if the connection string starts with `mongodb+srv://`.
534
+ * If those look ups throw a DNS Timeout error, the driver will retry the look up once.
535
+ *
524
536
  * @see docs.mongodb.org/manual/reference/connection-string/
525
537
  */
526
538
  async connect(): Promise<this> {
@@ -646,6 +658,11 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
646
658
  writable: false
647
659
  });
648
660
 
661
+ const activeCursorCloses = Array.from(this.s.activeCursors, cursor => cursor.close());
662
+ this.s.activeCursors.clear();
663
+
664
+ await Promise.all(activeCursorCloses);
665
+
649
666
  const activeSessionEnds = Array.from(this.s.activeSessions, session => session.endSession());
650
667
  this.s.activeSessions.clear();
651
668
 
@@ -727,6 +744,10 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
727
744
  * @remarks
728
745
  * The programmatically provided options take precedence over the URI options.
729
746
  *
747
+ * @remarks
748
+ * The driver will look up corresponding SRV and TXT records if the connection string starts with `mongodb+srv://`.
749
+ * If those look ups throw a DNS Timeout error, the driver will retry the look up once.
750
+ *
730
751
  * @see https://www.mongodb.com/docs/manual/reference/connection-string/
731
752
  */
732
753
  static async connect(url: string, options?: MongoClientOptions): Promise<MongoClient> {
@@ -1,6 +1,24 @@
1
1
  import { inspect, promisify } from 'util';
2
+ import { isUint8Array } from 'util/types';
2
3
 
3
- import { type Document, EJSON, type EJSONOptions, type ObjectId } from './bson';
4
+ import {
5
+ type Binary,
6
+ type BSONRegExp,
7
+ type BSONSymbol,
8
+ type Code,
9
+ type DBRef,
10
+ type Decimal128,
11
+ type Document,
12
+ type Double,
13
+ EJSON,
14
+ type EJSONOptions,
15
+ type Int32,
16
+ type Long,
17
+ type MaxKey,
18
+ type MinKey,
19
+ type ObjectId,
20
+ type Timestamp
21
+ } from './bson';
4
22
  import type { CommandStartedEvent } from './cmap/command_monitoring_events';
5
23
  import type {
6
24
  ConnectionCheckedInEvent,
@@ -413,6 +431,20 @@ export interface LogConvertible extends Record<string, any> {
413
431
  toLog(): Record<string, any>;
414
432
  }
415
433
 
434
+ type BSONObject =
435
+ | BSONRegExp
436
+ | BSONSymbol
437
+ | Code
438
+ | DBRef
439
+ | Decimal128
440
+ | Double
441
+ | Int32
442
+ | Long
443
+ | MaxKey
444
+ | MinKey
445
+ | ObjectId
446
+ | Timestamp
447
+ | Binary;
416
448
  /** @internal */
417
449
  export function stringifyWithMaxLen(
418
450
  value: any,
@@ -421,13 +453,107 @@ export function stringifyWithMaxLen(
421
453
  ): string {
422
454
  let strToTruncate = '';
423
455
 
456
+ let currentLength = 0;
457
+ const maxDocumentLengthEnsurer = function maxDocumentLengthEnsurer(key: string, value: any) {
458
+ if (currentLength >= maxDocumentLength) {
459
+ return undefined;
460
+ }
461
+ // Account for root document
462
+ if (key === '') {
463
+ // Account for starting brace
464
+ currentLength += 1;
465
+ return value;
466
+ }
467
+
468
+ // +4 accounts for 2 quotation marks, colon and comma after value
469
+ // Note that this potentially undercounts since it does not account for escape sequences which
470
+ // will have an additional backslash added to them once passed through JSON.stringify.
471
+ currentLength += key.length + 4;
472
+
473
+ if (value == null) return value;
474
+
475
+ switch (typeof value) {
476
+ case 'string':
477
+ // +2 accounts for quotes
478
+ // Note that this potentially undercounts similarly to the key length calculation
479
+ currentLength += value.length + 2;
480
+ break;
481
+ case 'number':
482
+ case 'bigint':
483
+ currentLength += String(value).length;
484
+ break;
485
+ case 'boolean':
486
+ currentLength += value ? 4 : 5;
487
+ break;
488
+ case 'object':
489
+ if (isUint8Array(value)) {
490
+ // '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'
491
+ // This is an estimate based on the fact that the base64 is approximately 1.33x the length of
492
+ // the actual binary sequence https://en.wikipedia.org/wiki/Base64
493
+ currentLength += (22 + value.byteLength + value.byteLength * 0.33 + 18) | 0;
494
+ } else if ('_bsontype' in value) {
495
+ const v = value as BSONObject;
496
+ switch (v._bsontype) {
497
+ case 'Int32':
498
+ currentLength += String(v.value).length;
499
+ break;
500
+ case 'Double':
501
+ // Account for representing integers as <value>.0
502
+ currentLength +=
503
+ (v.value | 0) === v.value ? String(v.value).length + 2 : String(v.value).length;
504
+ break;
505
+ case 'Long':
506
+ currentLength += v.toString().length;
507
+ break;
508
+ case 'ObjectId':
509
+ // '{"$oid":"XXXXXXXXXXXXXXXXXXXXXXXX"}'
510
+ currentLength += 35;
511
+ break;
512
+ case 'MaxKey':
513
+ case 'MinKey':
514
+ // '{"$maxKey":1}' or '{"$minKey":1}'
515
+ currentLength += 13;
516
+ break;
517
+ case 'Binary':
518
+ // '{"$binary":{"base64":"<base64 string>","subType":"XX"}}'
519
+ // This is an estimate based on the fact that the base64 is approximately 1.33x the length of
520
+ // the actual binary sequence https://en.wikipedia.org/wiki/Base64
521
+ currentLength += (22 + value.position + value.position * 0.33 + 18) | 0;
522
+ break;
523
+ case 'Timestamp':
524
+ // '{"$timestamp":{"t":<t>,"i":<i>}}'
525
+ currentLength += 19 + String(v.t).length + 5 + String(v.i).length + 2;
526
+ break;
527
+ case 'Code':
528
+ // '{"$code":"<code>"}' or '{"$code":"<code>","$scope":<scope>}'
529
+ if (v.scope == null) {
530
+ currentLength += v.code.length + 10 + 2;
531
+ } else {
532
+ // Ignoring actual scope object, so this undercounts by a significant amount
533
+ currentLength += v.code.length + 10 + 11;
534
+ }
535
+ break;
536
+ case 'BSONRegExp':
537
+ // '{"$regularExpression":{"pattern":"<pattern>","options":"<options>"}}'
538
+ currentLength += 34 + v.pattern.length + 13 + v.options.length + 3;
539
+ break;
540
+ }
541
+ }
542
+ }
543
+ return value;
544
+ };
545
+
424
546
  if (typeof value === 'string') {
425
547
  strToTruncate = value;
426
548
  } else if (typeof value === 'function') {
427
549
  strToTruncate = value.name;
428
550
  } else {
429
551
  try {
430
- strToTruncate = EJSON.stringify(value, options);
552
+ if (maxDocumentLength !== 0) {
553
+ strToTruncate = EJSON.stringify(value, maxDocumentLengthEnsurer, 0, options);
554
+ } else {
555
+ strToTruncate = EJSON.stringify(value, options);
556
+ }
431
557
  } catch (e) {
432
558
  strToTruncate = `Extended JSON serialization failed with: ${e.message}`;
433
559
  }
@@ -474,6 +474,44 @@ export class TypedEventEmitter<Events extends EventsDescription> extends EventEm
474
474
  /** @public */
475
475
  export class CancellationToken extends TypedEventEmitter<{ cancel(): void }> {}
476
476
 
477
+ /** @public */
478
+ export type Abortable = {
479
+ /**
480
+ * @experimental
481
+ * When provided, the corresponding `AbortController` can be used to abort an asynchronous action.
482
+ *
483
+ * The `signal.reason` value is used as the error thrown.
484
+ *
485
+ * @remarks
486
+ * **NOTE:** If an abort signal aborts an operation while the driver is writing to the underlying
487
+ * socket or reading the response from the server, the socket will be closed.
488
+ * If signals are aborted at a high rate during socket read/writes this can lead to a high rate of connection reestablishment.
489
+ *
490
+ * We plan to mitigate this in a future release, please follow NODE-6062 (`timeoutMS` expiration suffers the same limitation).
491
+ *
492
+ * AbortSignals are likely a best fit for human interactive interruption (ex. ctrl-C) where the frequency
493
+ * of cancellation is reasonably low. If a signal is programmatically aborted for 100s of operations you can empty
494
+ * the driver's connection pool.
495
+ *
496
+ * @example
497
+ * ```js
498
+ * const controller = new AbortController();
499
+ * const { signal } = controller;
500
+ * process.on('SIGINT', () => controller.abort(new Error('^C pressed')));
501
+ *
502
+ * try {
503
+ * const res = await fetch('...', { signal });
504
+ * await collection.findOne(await res.json(), { signal });
505
+ * catch (error) {
506
+ * if (error === signal.reason) {
507
+ * // signal abort error handling
508
+ * }
509
+ * }
510
+ * ```
511
+ */
512
+ signal?: AbortSignal | undefined;
513
+ };
514
+
477
515
  /**
478
516
  * Helper types for dot-notation filter attributes
479
517
  */