mongodb 6.12.0 → 6.13.0-dev.20250201.sha.35c703e3

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 (95) hide show
  1. package/lib/beta.d.ts +176 -108
  2. package/lib/bulk/common.js +5 -7
  3. package/lib/bulk/common.js.map +1 -1
  4. package/lib/change_stream.js +16 -26
  5. package/lib/change_stream.js.map +1 -1
  6. package/lib/client-side-encryption/auto_encrypter.js +4 -2
  7. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  8. package/lib/client-side-encryption/client_encryption.js +4 -4
  9. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  10. package/lib/client-side-encryption/state_machine.js +56 -30
  11. package/lib/client-side-encryption/state_machine.js.map +1 -1
  12. package/lib/cmap/auth/mongodb_aws.js +1 -1
  13. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_oidc.js +1 -1
  15. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  16. package/lib/cmap/command_monitoring_events.js +9 -50
  17. package/lib/cmap/command_monitoring_events.js.map +1 -1
  18. package/lib/cmap/connection.js +28 -22
  19. package/lib/cmap/connection.js.map +1 -1
  20. package/lib/cmap/connection_pool.js +88 -117
  21. package/lib/cmap/connection_pool.js.map +1 -1
  22. package/lib/cmap/wire_protocol/on_data.js +6 -1
  23. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  24. package/lib/collection.js.map +1 -1
  25. package/lib/connection_string.js +68 -86
  26. package/lib/connection_string.js.map +1 -1
  27. package/lib/cursor/abstract_cursor.js +47 -18
  28. package/lib/cursor/abstract_cursor.js.map +1 -1
  29. package/lib/cursor/aggregation_cursor.js +2 -1
  30. package/lib/cursor/aggregation_cursor.js.map +1 -1
  31. package/lib/cursor/find_cursor.js +2 -1
  32. package/lib/cursor/find_cursor.js.map +1 -1
  33. package/lib/cursor/list_collections_cursor.js +2 -1
  34. package/lib/cursor/list_collections_cursor.js.map +1 -1
  35. package/lib/db.js +2 -1
  36. package/lib/db.js.map +1 -1
  37. package/lib/encrypter.js +5 -9
  38. package/lib/encrypter.js.map +1 -1
  39. package/lib/error.js +10 -18
  40. package/lib/error.js.map +1 -1
  41. package/lib/index.js +5 -2
  42. package/lib/index.js.map +1 -1
  43. package/lib/mongo_client.js +46 -26
  44. package/lib/mongo_client.js.map +1 -1
  45. package/lib/mongo_logger.js +102 -3
  46. package/lib/mongo_logger.js.map +1 -1
  47. package/lib/operations/execute_operation.js +9 -5
  48. package/lib/operations/execute_operation.js.map +1 -1
  49. package/lib/operations/list_collections.js.map +1 -1
  50. package/lib/operations/operation.js +4 -5
  51. package/lib/operations/operation.js.map +1 -1
  52. package/lib/sdam/monitor.js +25 -31
  53. package/lib/sdam/monitor.js.map +1 -1
  54. package/lib/sdam/server.js +27 -17
  55. package/lib/sdam/server.js.map +1 -1
  56. package/lib/sdam/topology.js +20 -19
  57. package/lib/sdam/topology.js.map +1 -1
  58. package/lib/sessions.js +24 -48
  59. package/lib/sessions.js.map +1 -1
  60. package/lib/utils.js +64 -44
  61. package/lib/utils.js.map +1 -1
  62. package/mongodb.d.ts +176 -108
  63. package/package.json +2 -2
  64. package/src/bulk/common.ts +6 -9
  65. package/src/change_stream.ts +21 -33
  66. package/src/client-side-encryption/auto_encrypter.ts +12 -8
  67. package/src/client-side-encryption/client_encryption.ts +6 -4
  68. package/src/client-side-encryption/state_machine.ts +80 -36
  69. package/src/cmap/auth/mongodb_aws.ts +1 -1
  70. package/src/cmap/auth/mongodb_oidc.ts +1 -1
  71. package/src/cmap/command_monitoring_events.ts +10 -55
  72. package/src/cmap/connection.ts +37 -29
  73. package/src/cmap/connection_pool.ts +121 -145
  74. package/src/cmap/wire_protocol/on_data.ts +9 -2
  75. package/src/collection.ts +15 -8
  76. package/src/connection_string.ts +74 -99
  77. package/src/cursor/abstract_cursor.ts +71 -23
  78. package/src/cursor/aggregation_cursor.ts +5 -3
  79. package/src/cursor/find_cursor.ts +5 -3
  80. package/src/cursor/list_collections_cursor.ts +5 -3
  81. package/src/db.ts +11 -7
  82. package/src/encrypter.ts +6 -11
  83. package/src/error.ts +11 -23
  84. package/src/index.ts +3 -3
  85. package/src/mongo_client.ts +78 -47
  86. package/src/mongo_logger.ts +158 -11
  87. package/src/mongo_types.ts +38 -0
  88. package/src/operations/execute_operation.ts +11 -6
  89. package/src/operations/list_collections.ts +4 -1
  90. package/src/operations/operation.ts +8 -9
  91. package/src/sdam/monitor.ts +30 -38
  92. package/src/sdam/server.ts +33 -20
  93. package/src/sdam/topology.ts +29 -26
  94. package/src/sessions.ts +37 -58
  95. package/src/utils.ts +79 -43
package/src/collection.ts CHANGED
@@ -14,6 +14,7 @@ import type { Db } from './db';
14
14
  import { MongoInvalidArgumentError, MongoOperationTimeoutError } from './error';
15
15
  import type { MongoClient, PkFactory } from './mongo_client';
16
16
  import type {
17
+ Abortable,
17
18
  Filter,
18
19
  Flatten,
19
20
  OptionalUnlessRequiredId,
@@ -505,7 +506,7 @@ export class Collection<TSchema extends Document = Document> {
505
506
  async findOne(filter: Filter<TSchema>): Promise<WithId<TSchema> | null>;
506
507
  async findOne(
507
508
  filter: Filter<TSchema>,
508
- options: Omit<FindOptions, 'timeoutMode'>
509
+ options: Omit<FindOptions, 'timeoutMode'> & Abortable
509
510
  ): Promise<WithId<TSchema> | null>;
510
511
 
511
512
  // allow an override of the schema.
@@ -513,12 +514,12 @@ export class Collection<TSchema extends Document = Document> {
513
514
  async findOne<T = TSchema>(filter: Filter<TSchema>): Promise<T | null>;
514
515
  async findOne<T = TSchema>(
515
516
  filter: Filter<TSchema>,
516
- options?: Omit<FindOptions, 'timeoutMode'>
517
+ options?: Omit<FindOptions, 'timeoutMode'> & Abortable
517
518
  ): Promise<T | null>;
518
519
 
519
520
  async findOne(
520
521
  filter: Filter<TSchema> = {},
521
- options: FindOptions = {}
522
+ options: FindOptions & Abortable = {}
522
523
  ): Promise<WithId<TSchema> | null> {
523
524
  const cursor = this.find(filter, options).limit(-1).batchSize(1);
524
525
  const res = await cursor.next();
@@ -532,9 +533,15 @@ export class Collection<TSchema extends Document = Document> {
532
533
  * @param filter - The filter predicate. If unspecified, then all documents in the collection will match the predicate
533
534
  */
534
535
  find(): FindCursor<WithId<TSchema>>;
535
- find(filter: Filter<TSchema>, options?: FindOptions): FindCursor<WithId<TSchema>>;
536
- find<T extends Document>(filter: Filter<TSchema>, options?: FindOptions): FindCursor<T>;
537
- find(filter: Filter<TSchema> = {}, options: FindOptions = {}): FindCursor<WithId<TSchema>> {
536
+ find(filter: Filter<TSchema>, options?: FindOptions & Abortable): FindCursor<WithId<TSchema>>;
537
+ find<T extends Document>(
538
+ filter: Filter<TSchema>,
539
+ options?: FindOptions & Abortable
540
+ ): FindCursor<T>;
541
+ find(
542
+ filter: Filter<TSchema> = {},
543
+ options: FindOptions & Abortable = {}
544
+ ): FindCursor<WithId<TSchema>> {
538
545
  return new FindCursor<WithId<TSchema>>(
539
546
  this.client,
540
547
  this.s.namespace,
@@ -792,7 +799,7 @@ export class Collection<TSchema extends Document = Document> {
792
799
  */
793
800
  async countDocuments(
794
801
  filter: Filter<TSchema> = {},
795
- options: CountDocumentsOptions = {}
802
+ options: CountDocumentsOptions & Abortable = {}
796
803
  ): Promise<number> {
797
804
  const pipeline = [];
798
805
  pipeline.push({ $match: filter });
@@ -1006,7 +1013,7 @@ export class Collection<TSchema extends Document = Document> {
1006
1013
  */
1007
1014
  aggregate<T extends Document = Document>(
1008
1015
  pipeline: Document[] = [],
1009
- options?: AggregateOptions
1016
+ options?: AggregateOptions & Abortable
1010
1017
  ): AggregationCursor<T> {
1011
1018
  if (!Array.isArray(pipeline)) {
1012
1019
  throw new MongoInvalidArgumentError(
@@ -22,13 +22,7 @@ import {
22
22
  type ServerApi,
23
23
  ServerApiVersion
24
24
  } from './mongo_client';
25
- import {
26
- MongoLoggableComponent,
27
- MongoLogger,
28
- type MongoLoggerEnvOptions,
29
- type MongoLoggerMongoClientOptions,
30
- SeverityLevel
31
- } from './mongo_logger';
25
+ import { MongoLoggableComponent, MongoLogger, SeverityLevel } from './mongo_logger';
32
26
  import { ReadConcern, type ReadConcernLevel } from './read_concern';
33
27
  import { ReadPreference, type ReadPreferenceMode } from './read_preference';
34
28
  import { ServerMonitoringMode } from './sdam/monitor';
@@ -52,6 +46,27 @@ const LB_REPLICA_SET_ERROR = 'loadBalanced option not supported with a replicaSe
52
46
  const LB_DIRECT_CONNECTION_ERROR =
53
47
  'loadBalanced option not supported when directConnection is provided';
54
48
 
49
+ function retryDNSTimeoutFor(api: 'resolveSrv'): (a: string) => Promise<dns.SrvRecord[]>;
50
+ function retryDNSTimeoutFor(api: 'resolveTxt'): (a: string) => Promise<string[][]>;
51
+ function retryDNSTimeoutFor(
52
+ api: 'resolveSrv' | 'resolveTxt'
53
+ ): (a: string) => Promise<dns.SrvRecord[] | string[][]> {
54
+ return async function dnsReqRetryTimeout(lookupAddress: string) {
55
+ try {
56
+ return await dns.promises[api](lookupAddress);
57
+ } catch (firstDNSError) {
58
+ if (firstDNSError.code === dns.TIMEOUT) {
59
+ return await dns.promises[api](lookupAddress);
60
+ } else {
61
+ throw firstDNSError;
62
+ }
63
+ }
64
+ };
65
+ }
66
+
67
+ const resolveSrv = retryDNSTimeoutFor('resolveSrv');
68
+ const resolveTxt = retryDNSTimeoutFor('resolveTxt');
69
+
55
70
  /**
56
71
  * Lookup a `mongodb+srv` connection string, combine the parts and reparse it as a normal
57
72
  * connection string.
@@ -67,14 +82,13 @@ export async function resolveSRVRecord(options: MongoOptions): Promise<HostAddre
67
82
  // Asynchronously start TXT resolution so that we do not have to wait until
68
83
  // the SRV record is resolved before starting a second DNS query.
69
84
  const lookupAddress = options.srvHost;
70
- const txtResolutionPromise = dns.promises.resolveTxt(lookupAddress);
85
+ const txtResolutionPromise = resolveTxt(lookupAddress);
71
86
 
72
87
  txtResolutionPromise.then(undefined, squashError); // rejections will be handled later
73
88
 
89
+ const hostname = `_${options.srvServiceName}._tcp.${lookupAddress}`;
74
90
  // Resolve the SRV record and use the result as the list of hosts to connect to.
75
- const addresses = await dns.promises.resolveSrv(
76
- `_${options.srvServiceName}._tcp.${lookupAddress}`
77
- );
91
+ const addresses = await resolveSrv(hostname);
78
92
 
79
93
  if (addresses.length === 0) {
80
94
  throw new MongoAPIError('No addresses found at host');
@@ -249,13 +263,6 @@ export function parseOptions(
249
263
 
250
264
  const mongoOptions = Object.create(null);
251
265
 
252
- // Feature flags
253
- for (const flag of Object.getOwnPropertySymbols(options)) {
254
- if (FEATURE_FLAGS.has(flag)) {
255
- mongoOptions[flag] = options[flag];
256
- }
257
- }
258
-
259
266
  mongoOptions.hosts = isSRV ? [] : hosts.map(HostAddress.fromString);
260
267
 
261
268
  const urlOptions = new CaseInsensitiveMap<unknown[]>();
@@ -515,13 +522,8 @@ export function parseOptions(
515
522
  );
516
523
  }
517
524
 
518
- const loggerFeatureFlag = Symbol.for('@@mdb.enableMongoLogger');
519
- mongoOptions[loggerFeatureFlag] = mongoOptions[loggerFeatureFlag] ?? false;
520
-
521
- let loggerEnvOptions: MongoLoggerEnvOptions = {};
522
- let loggerClientOptions: MongoLoggerMongoClientOptions = {};
523
- if (mongoOptions[loggerFeatureFlag]) {
524
- loggerEnvOptions = {
525
+ mongoOptions.mongoLoggerOptions = MongoLogger.resolveOptions(
526
+ {
525
527
  MONGODB_LOG_COMMAND: process.env.MONGODB_LOG_COMMAND,
526
528
  MONGODB_LOG_TOPOLOGY: process.env.MONGODB_LOG_TOPOLOGY,
527
529
  MONGODB_LOG_SERVER_SELECTION: process.env.MONGODB_LOG_SERVER_SELECTION,
@@ -529,18 +531,13 @@ export function parseOptions(
529
531
  MONGODB_LOG_CLIENT: process.env.MONGODB_LOG_CLIENT,
530
532
  MONGODB_LOG_ALL: process.env.MONGODB_LOG_ALL,
531
533
  MONGODB_LOG_MAX_DOCUMENT_LENGTH: process.env.MONGODB_LOG_MAX_DOCUMENT_LENGTH,
532
- MONGODB_LOG_PATH: process.env.MONGODB_LOG_PATH,
533
- ...mongoOptions[Symbol.for('@@mdb.internalLoggerConfig')]
534
- };
535
- loggerClientOptions = {
534
+ MONGODB_LOG_PATH: process.env.MONGODB_LOG_PATH
535
+ },
536
+ {
536
537
  mongodbLogPath: mongoOptions.mongodbLogPath,
537
538
  mongodbLogComponentSeverities: mongoOptions.mongodbLogComponentSeverities,
538
539
  mongodbLogMaxDocumentLength: mongoOptions.mongodbLogMaxDocumentLength
539
- };
540
- }
541
- mongoOptions.mongoLoggerOptions = MongoLogger.resolveOptions(
542
- loggerEnvOptions,
543
- loggerClientOptions
540
+ }
544
541
  );
545
542
 
546
543
  mongoOptions.metadata = makeClientMetadata(mongoOptions);
@@ -1220,52 +1217,6 @@ export const OPTIONS = {
1220
1217
  default: 0,
1221
1218
  type: 'int'
1222
1219
  },
1223
- // Custom types for modifying core behavior
1224
- connectionType: { type: 'any' },
1225
- srvPoller: { type: 'any' },
1226
- // Accepted Node.js Options
1227
- allowPartialTrustChain: { type: 'any' },
1228
- minDHSize: { type: 'any' },
1229
- pskCallback: { type: 'any' },
1230
- secureContext: { type: 'any' },
1231
- enableTrace: { type: 'any' },
1232
- requestCert: { type: 'any' },
1233
- rejectUnauthorized: { type: 'any' },
1234
- checkServerIdentity: { type: 'any' },
1235
- ALPNProtocols: { type: 'any' },
1236
- SNICallback: { type: 'any' },
1237
- session: { type: 'any' },
1238
- requestOCSP: { type: 'any' },
1239
- localAddress: { type: 'any' },
1240
- localPort: { type: 'any' },
1241
- hints: { type: 'any' },
1242
- lookup: { type: 'any' },
1243
- ca: { type: 'any' },
1244
- cert: { type: 'any' },
1245
- ciphers: { type: 'any' },
1246
- crl: { type: 'any' },
1247
- ecdhCurve: { type: 'any' },
1248
- key: { type: 'any' },
1249
- passphrase: { type: 'any' },
1250
- pfx: { type: 'any' },
1251
- secureProtocol: { type: 'any' },
1252
- index: { type: 'any' },
1253
- // Legacy options from v3 era
1254
- useNewUrlParser: {
1255
- type: 'boolean',
1256
- deprecated:
1257
- 'useNewUrlParser has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
1258
- } as OptionDescriptor,
1259
- useUnifiedTopology: {
1260
- type: 'boolean',
1261
- deprecated:
1262
- 'useUnifiedTopology has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
1263
- } as OptionDescriptor,
1264
- // MongoLogger
1265
- /**
1266
- * @internal
1267
- * TODO: NODE-5671 - remove internal flag
1268
- */
1269
1220
  mongodbLogPath: {
1270
1221
  transform({ values: [value] }) {
1271
1222
  if (
@@ -1284,10 +1235,6 @@ export const OPTIONS = {
1284
1235
  return value;
1285
1236
  }
1286
1237
  },
1287
- /**
1288
- * @internal
1289
- * TODO: NODE-5671 - remove internal flag
1290
- */
1291
1238
  mongodbLogComponentSeverities: {
1292
1239
  transform({ values: [value] }) {
1293
1240
  if (typeof value !== 'object' || !value) {
@@ -1313,11 +1260,49 @@ export const OPTIONS = {
1313
1260
  return value;
1314
1261
  }
1315
1262
  },
1316
- /**
1317
- * @internal
1318
- * TODO: NODE-5671 - remove internal flag
1319
- */
1320
- mongodbLogMaxDocumentLength: { type: 'uint' }
1263
+ mongodbLogMaxDocumentLength: { type: 'uint' },
1264
+ // Custom types for modifying core behavior
1265
+ connectionType: { type: 'any' },
1266
+ srvPoller: { type: 'any' },
1267
+ // Accepted Node.js Options
1268
+ allowPartialTrustChain: { type: 'any' },
1269
+ minDHSize: { type: 'any' },
1270
+ pskCallback: { type: 'any' },
1271
+ secureContext: { type: 'any' },
1272
+ enableTrace: { type: 'any' },
1273
+ requestCert: { type: 'any' },
1274
+ rejectUnauthorized: { type: 'any' },
1275
+ checkServerIdentity: { type: 'any' },
1276
+ ALPNProtocols: { type: 'any' },
1277
+ SNICallback: { type: 'any' },
1278
+ session: { type: 'any' },
1279
+ requestOCSP: { type: 'any' },
1280
+ localAddress: { type: 'any' },
1281
+ localPort: { type: 'any' },
1282
+ hints: { type: 'any' },
1283
+ lookup: { type: 'any' },
1284
+ ca: { type: 'any' },
1285
+ cert: { type: 'any' },
1286
+ ciphers: { type: 'any' },
1287
+ crl: { type: 'any' },
1288
+ ecdhCurve: { type: 'any' },
1289
+ key: { type: 'any' },
1290
+ passphrase: { type: 'any' },
1291
+ pfx: { type: 'any' },
1292
+ secureProtocol: { type: 'any' },
1293
+ index: { type: 'any' },
1294
+ // Legacy options from v3 era
1295
+ useNewUrlParser: {
1296
+ type: 'boolean',
1297
+ deprecated:
1298
+ 'useNewUrlParser has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
1299
+ } as OptionDescriptor,
1300
+ useUnifiedTopology: {
1301
+ type: 'boolean',
1302
+ deprecated:
1303
+ 'useUnifiedTopology has no effect since Node.js Driver version 4.0.0 and will be removed in the next major version'
1304
+ } as OptionDescriptor,
1305
+ __skipPingOnConnect: { type: 'boolean' }
1321
1306
  } as Record<keyof MongoClientOptions, OptionDescriptor>;
1322
1307
 
1323
1308
  export const DEFAULT_OPTIONS = new CaseInsensitiveMap(
@@ -1325,13 +1310,3 @@ export const DEFAULT_OPTIONS = new CaseInsensitiveMap(
1325
1310
  .filter(([, descriptor]) => descriptor.default != null)
1326
1311
  .map(([k, d]) => [k, d.default])
1327
1312
  );
1328
-
1329
- /**
1330
- * Set of permitted feature flags
1331
- * @internal
1332
- */
1333
- export const FEATURE_FLAGS = new Set([
1334
- Symbol.for('@@mdb.skipPingOnConnect'),
1335
- Symbol.for('@@mdb.enableMongoLogger'),
1336
- Symbol.for('@@mdb.internalLoggerConfig')
1337
- ]);
@@ -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);