mongodb 6.12.0 → 6.13.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 (92) 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_oidc.js +1 -1
  13. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  14. package/lib/cmap/command_monitoring_events.js +9 -50
  15. package/lib/cmap/command_monitoring_events.js.map +1 -1
  16. package/lib/cmap/connection.js +28 -22
  17. package/lib/cmap/connection.js.map +1 -1
  18. package/lib/cmap/connection_pool.js +88 -117
  19. package/lib/cmap/connection_pool.js.map +1 -1
  20. package/lib/cmap/wire_protocol/on_data.js +6 -1
  21. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  22. package/lib/collection.js.map +1 -1
  23. package/lib/connection_string.js +68 -86
  24. package/lib/connection_string.js.map +1 -1
  25. package/lib/cursor/abstract_cursor.js +47 -18
  26. package/lib/cursor/abstract_cursor.js.map +1 -1
  27. package/lib/cursor/aggregation_cursor.js +2 -1
  28. package/lib/cursor/aggregation_cursor.js.map +1 -1
  29. package/lib/cursor/find_cursor.js +2 -1
  30. package/lib/cursor/find_cursor.js.map +1 -1
  31. package/lib/cursor/list_collections_cursor.js +2 -1
  32. package/lib/cursor/list_collections_cursor.js.map +1 -1
  33. package/lib/db.js +2 -1
  34. package/lib/db.js.map +1 -1
  35. package/lib/encrypter.js +5 -9
  36. package/lib/encrypter.js.map +1 -1
  37. package/lib/error.js +10 -18
  38. package/lib/error.js.map +1 -1
  39. package/lib/index.js +5 -2
  40. package/lib/index.js.map +1 -1
  41. package/lib/mongo_client.js +46 -26
  42. package/lib/mongo_client.js.map +1 -1
  43. package/lib/mongo_logger.js +102 -3
  44. package/lib/mongo_logger.js.map +1 -1
  45. package/lib/operations/execute_operation.js +9 -5
  46. package/lib/operations/execute_operation.js.map +1 -1
  47. package/lib/operations/list_collections.js.map +1 -1
  48. package/lib/operations/operation.js +4 -5
  49. package/lib/operations/operation.js.map +1 -1
  50. package/lib/sdam/monitor.js +25 -31
  51. package/lib/sdam/monitor.js.map +1 -1
  52. package/lib/sdam/server.js +27 -17
  53. package/lib/sdam/server.js.map +1 -1
  54. package/lib/sdam/topology.js +20 -19
  55. package/lib/sdam/topology.js.map +1 -1
  56. package/lib/sessions.js +24 -48
  57. package/lib/sessions.js.map +1 -1
  58. package/lib/utils.js +64 -44
  59. package/lib/utils.js.map +1 -1
  60. package/mongodb.d.ts +176 -108
  61. package/package.json +1 -1
  62. package/src/bulk/common.ts +6 -9
  63. package/src/change_stream.ts +21 -33
  64. package/src/client-side-encryption/auto_encrypter.ts +12 -8
  65. package/src/client-side-encryption/client_encryption.ts +6 -4
  66. package/src/client-side-encryption/state_machine.ts +80 -36
  67. package/src/cmap/auth/mongodb_oidc.ts +1 -1
  68. package/src/cmap/command_monitoring_events.ts +10 -55
  69. package/src/cmap/connection.ts +37 -29
  70. package/src/cmap/connection_pool.ts +121 -145
  71. package/src/cmap/wire_protocol/on_data.ts +9 -2
  72. package/src/collection.ts +15 -8
  73. package/src/connection_string.ts +74 -99
  74. package/src/cursor/abstract_cursor.ts +71 -23
  75. package/src/cursor/aggregation_cursor.ts +5 -3
  76. package/src/cursor/find_cursor.ts +5 -3
  77. package/src/cursor/list_collections_cursor.ts +5 -3
  78. package/src/db.ts +11 -7
  79. package/src/encrypter.ts +6 -11
  80. package/src/error.ts +11 -23
  81. package/src/index.ts +3 -3
  82. package/src/mongo_client.ts +78 -47
  83. package/src/mongo_logger.ts +158 -11
  84. package/src/mongo_types.ts +38 -0
  85. package/src/operations/execute_operation.ts +11 -6
  86. package/src/operations/list_collections.ts +4 -1
  87. package/src/operations/operation.ts +8 -9
  88. package/src/sdam/monitor.ts +30 -38
  89. package/src/sdam/server.ts +33 -20
  90. package/src/sdam/topology.ts +29 -26
  91. package/src/sessions.ts +37 -58
  92. package/src/utils.ts +79 -43
@@ -24,13 +24,6 @@ import type { ServerSessionId } from './sessions';
24
24
  import { CSOTTimeoutContext, type TimeoutContext } from './timeout';
25
25
  import { filterOptions, getTopology, type MongoDBNamespace, squashError } from './utils';
26
26
 
27
- /** @internal */
28
- const kCursorStream = Symbol('cursorStream');
29
- /** @internal */
30
- const kClosed = Symbol('closed');
31
- /** @internal */
32
- const kMode = Symbol('mode');
33
-
34
27
  const CHANGE_STREAM_OPTIONS = [
35
28
  'resumeAfter',
36
29
  'startAfter',
@@ -584,14 +577,14 @@ export class ChangeStream<
584
577
  namespace: MongoDBNamespace;
585
578
  type: symbol;
586
579
  /** @internal */
587
- cursor: ChangeStreamCursor<TSchema, TChange>;
580
+ private cursor: ChangeStreamCursor<TSchema, TChange>;
588
581
  streamOptions?: CursorStreamOptions;
589
582
  /** @internal */
590
- [kCursorStream]?: Readable & AsyncIterable<TChange>;
583
+ private cursorStream?: Readable & AsyncIterable<TChange>;
591
584
  /** @internal */
592
- [kClosed]: boolean;
585
+ private isClosed: boolean;
593
586
  /** @internal */
594
- [kMode]: false | 'iterator' | 'emitter';
587
+ private mode: false | 'iterator' | 'emitter';
595
588
 
596
589
  /** @event */
597
590
  static readonly RESPONSE = RESPONSE;
@@ -668,8 +661,8 @@ export class ChangeStream<
668
661
  // Create contained Change Stream cursor
669
662
  this.cursor = this._createChangeStreamCursor(options);
670
663
 
671
- this[kClosed] = false;
672
- this[kMode] = false;
664
+ this.isClosed = false;
665
+ this.mode = false;
673
666
 
674
667
  // Listen for any `change` listeners being added to ChangeStream
675
668
  this.on('newListener', eventName => {
@@ -680,7 +673,7 @@ export class ChangeStream<
680
673
 
681
674
  this.on('removeListener', eventName => {
682
675
  if (eventName === 'change' && this.listenerCount('change') === 0 && this.cursor) {
683
- this[kCursorStream]?.removeAllListeners('data');
676
+ this.cursorStream?.removeAllListeners('data');
684
677
  }
685
678
  });
686
679
 
@@ -692,11 +685,6 @@ export class ChangeStream<
692
685
  }
693
686
  }
694
687
 
695
- /** @internal */
696
- get cursorStream(): (Readable & AsyncIterable<TChange>) | undefined {
697
- return this[kCursorStream];
698
- }
699
-
700
688
  /** The cached resume token that is used to resume after the most recently returned change. */
701
689
  get resumeToken(): ResumeToken {
702
690
  return this.cursor?.resumeToken;
@@ -826,8 +814,8 @@ export class ChangeStream<
826
814
  }
827
815
 
828
816
  /** Is the cursor closed */
829
- get closed(): boolean {
830
- return this[kClosed] || this.cursor.closed;
817
+ public get closed(): boolean {
818
+ return this.isClosed || this.cursor.closed;
831
819
  }
832
820
 
833
821
  /**
@@ -836,7 +824,7 @@ export class ChangeStream<
836
824
  async close(): Promise<void> {
837
825
  this.timeoutContext?.clear();
838
826
  this.timeoutContext = undefined;
839
- this[kClosed] = true;
827
+ this.isClosed = true;
840
828
 
841
829
  const cursor = this.cursor;
842
830
  try {
@@ -865,24 +853,24 @@ export class ChangeStream<
865
853
 
866
854
  /** @internal */
867
855
  private _setIsEmitter(): void {
868
- if (this[kMode] === 'iterator') {
856
+ if (this.mode === 'iterator') {
869
857
  // TODO(NODE-3485): Replace with MongoChangeStreamModeError
870
858
  throw new MongoAPIError(
871
859
  'ChangeStream cannot be used as an EventEmitter after being used as an iterator'
872
860
  );
873
861
  }
874
- this[kMode] = 'emitter';
862
+ this.mode = 'emitter';
875
863
  }
876
864
 
877
865
  /** @internal */
878
866
  private _setIsIterator(): void {
879
- if (this[kMode] === 'emitter') {
867
+ if (this.mode === 'emitter') {
880
868
  // TODO(NODE-3485): Replace with MongoChangeStreamModeError
881
869
  throw new MongoAPIError(
882
870
  'ChangeStream cannot be used as an iterator after being used as an EventEmitter'
883
871
  );
884
872
  }
885
- this[kMode] = 'iterator';
873
+ this.mode = 'iterator';
886
874
  }
887
875
 
888
876
  /**
@@ -947,8 +935,8 @@ export class ChangeStream<
947
935
  /** @internal */
948
936
  private _streamEvents(cursor: ChangeStreamCursor<TSchema, TChange>): void {
949
937
  this._setIsEmitter();
950
- const stream = this[kCursorStream] ?? cursor.stream();
951
- this[kCursorStream] = stream;
938
+ const stream = this.cursorStream ?? cursor.stream();
939
+ this.cursorStream = stream;
952
940
  stream.on('data', change => {
953
941
  try {
954
942
  const processedChange = this._processChange(change);
@@ -963,18 +951,18 @@ export class ChangeStream<
963
951
 
964
952
  /** @internal */
965
953
  private _endStream(): void {
966
- const cursorStream = this[kCursorStream];
954
+ const cursorStream = this.cursorStream;
967
955
  if (cursorStream) {
968
956
  ['data', 'close', 'end', 'error'].forEach(event => cursorStream.removeAllListeners(event));
969
957
  cursorStream.destroy();
970
958
  }
971
959
 
972
- this[kCursorStream] = undefined;
960
+ this.cursorStream = undefined;
973
961
  }
974
962
 
975
963
  /** @internal */
976
964
  private _processChange(change: TChange | null): TChange {
977
- if (this[kClosed]) {
965
+ if (this.isClosed) {
978
966
  // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
979
967
  throw new MongoAPIError(CHANGESTREAM_CLOSED_ERROR);
980
968
  }
@@ -1002,7 +990,7 @@ export class ChangeStream<
1002
990
  /** @internal */
1003
991
  private _processErrorStreamMode(changeStreamError: AnyError, cursorInitialized: boolean) {
1004
992
  // If the change stream has been closed explicitly, do not process error.
1005
- if (this[kClosed]) return;
993
+ if (this.isClosed) return;
1006
994
 
1007
995
  if (
1008
996
  cursorInitialized &&
@@ -1034,7 +1022,7 @@ export class ChangeStream<
1034
1022
 
1035
1023
  /** @internal */
1036
1024
  private async _processErrorIteratorMode(changeStreamError: AnyError, cursorInitialized: boolean) {
1037
- if (this[kClosed]) {
1025
+ if (this.isClosed) {
1038
1026
  // TODO(NODE-3485): Replace with MongoChangeStreamClosedError
1039
1027
  throw new MongoAPIError(CHANGESTREAM_CLOSED_ERROR);
1040
1028
  }
@@ -11,6 +11,7 @@ import { kDecorateResult } from '../constants';
11
11
  import { getMongoDBClientEncryption } from '../deps';
12
12
  import { MongoRuntimeError } from '../error';
13
13
  import { MongoClient, type MongoClientOptions } from '../mongo_client';
14
+ import { type Abortable } from '../mongo_types';
14
15
  import { MongoDBCollectionNamespace } from '../utils';
15
16
  import { autoSelectSocketOptions } from './client_encryption';
16
17
  import * as cryptoCallbacks from './crypto_callbacks';
@@ -372,8 +373,10 @@ export class AutoEncrypter {
372
373
  async encrypt(
373
374
  ns: string,
374
375
  cmd: Document,
375
- options: CommandOptions = {}
376
+ options: CommandOptions & Abortable = {}
376
377
  ): Promise<Document | Uint8Array> {
378
+ options.signal?.throwIfAborted();
379
+
377
380
  if (this._bypassEncryption) {
378
381
  // If `bypassAutoEncryption` has been specified, don't encrypt
379
382
  return cmd;
@@ -398,7 +401,7 @@ export class AutoEncrypter {
398
401
  socketOptions: autoSelectSocketOptions(this._client.s.options)
399
402
  });
400
403
 
401
- return deserialize(await stateMachine.execute(this, context, options.timeoutContext), {
404
+ return deserialize(await stateMachine.execute(this, context, options), {
402
405
  promoteValues: false,
403
406
  promoteLongs: false
404
407
  });
@@ -407,7 +410,12 @@ export class AutoEncrypter {
407
410
  /**
408
411
  * Decrypt a command response
409
412
  */
410
- async decrypt(response: Uint8Array, options: CommandOptions = {}): Promise<Uint8Array> {
413
+ async decrypt(
414
+ response: Uint8Array,
415
+ options: CommandOptions & Abortable = {}
416
+ ): Promise<Uint8Array> {
417
+ options.signal?.throwIfAborted();
418
+
411
419
  const context = this._mongocrypt.makeDecryptionContext(response);
412
420
 
413
421
  context.id = this._contextCounter++;
@@ -419,11 +427,7 @@ export class AutoEncrypter {
419
427
  socketOptions: autoSelectSocketOptions(this._client.s.options)
420
428
  });
421
429
 
422
- return await stateMachine.execute(
423
- this,
424
- context,
425
- options.timeoutContext?.csotEnabled() ? options.timeoutContext : undefined
426
- );
430
+ return await stateMachine.execute(this, context, options);
427
431
  }
428
432
 
429
433
  /**
@@ -225,7 +225,7 @@ export class ClientEncryption {
225
225
  TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }));
226
226
 
227
227
  const dataKey = deserialize(
228
- await stateMachine.execute(this, context, timeoutContext)
228
+ await stateMachine.execute(this, context, { timeoutContext })
229
229
  ) as DataKey;
230
230
 
231
231
  const { db: dbName, collection: collectionName } = MongoDBCollectionNamespace.fromString(
@@ -293,7 +293,9 @@ export class ClientEncryption {
293
293
  resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS })
294
294
  );
295
295
 
296
- const { v: dataKeys } = deserialize(await stateMachine.execute(this, context, timeoutContext));
296
+ const { v: dataKeys } = deserialize(
297
+ await stateMachine.execute(this, context, { timeoutContext })
298
+ );
297
299
  if (dataKeys.length === 0) {
298
300
  return {};
299
301
  }
@@ -696,7 +698,7 @@ export class ClientEncryption {
696
698
  ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))
697
699
  : undefined;
698
700
 
699
- const { v } = deserialize(await stateMachine.execute(this, context, timeoutContext));
701
+ const { v } = deserialize(await stateMachine.execute(this, context, { timeoutContext }));
700
702
 
701
703
  return v;
702
704
  }
@@ -780,7 +782,7 @@ export class ClientEncryption {
780
782
  this._timeoutMS != null
781
783
  ? TimeoutContext.create(resolveTimeoutOptions(this._client, { timeoutMS: this._timeoutMS }))
782
784
  : undefined;
783
- const { v } = deserialize(await stateMachine.execute(this, context, timeoutContext));
785
+ const { v } = deserialize(await stateMachine.execute(this, context, { timeoutContext }));
784
786
  return v;
785
787
  }
786
788
  }
@@ -15,8 +15,15 @@ import { CursorTimeoutContext } from '../cursor/abstract_cursor';
15
15
  import { getSocks, type SocksLib } from '../deps';
16
16
  import { MongoOperationTimeoutError } from '../error';
17
17
  import { type MongoClient, type MongoClientOptions } from '../mongo_client';
18
+ import { type Abortable } from '../mongo_types';
18
19
  import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
19
- import { BufferPool, MongoDBCollectionNamespace, promiseWithResolvers } from '../utils';
20
+ import {
21
+ addAbortListener,
22
+ BufferPool,
23
+ kDispose,
24
+ MongoDBCollectionNamespace,
25
+ promiseWithResolvers
26
+ } from '../utils';
20
27
  import { autoSelectSocketOptions, type DataKey } from './client_encryption';
21
28
  import { MongoCryptError } from './errors';
22
29
  import { type MongocryptdManager } from './mongocryptd_manager';
@@ -189,7 +196,7 @@ export class StateMachine {
189
196
  async execute(
190
197
  executor: StateMachineExecutable,
191
198
  context: MongoCryptContext,
192
- timeoutContext?: TimeoutContext
199
+ options: { timeoutContext?: TimeoutContext } & Abortable
193
200
  ): Promise<Uint8Array> {
194
201
  const keyVaultNamespace = executor._keyVaultNamespace;
195
202
  const keyVaultClient = executor._keyVaultClient;
@@ -199,6 +206,7 @@ export class StateMachine {
199
206
  let result: Uint8Array | null = null;
200
207
 
201
208
  while (context.state !== MONGOCRYPT_CTX_DONE && context.state !== MONGOCRYPT_CTX_ERROR) {
209
+ options.signal?.throwIfAborted();
202
210
  debug(`[context#${context.id}] ${stateToString.get(context.state) || context.state}`);
203
211
 
204
212
  switch (context.state) {
@@ -214,7 +222,7 @@ export class StateMachine {
214
222
  metaDataClient,
215
223
  context.ns,
216
224
  filter,
217
- timeoutContext
225
+ options
218
226
  );
219
227
  if (collInfo) {
220
228
  context.addMongoOperationResponse(collInfo);
@@ -235,9 +243,9 @@ export class StateMachine {
235
243
  // When we are using the shared library, we don't have a mongocryptd manager.
236
244
  const markedCommand: Uint8Array = mongocryptdManager
237
245
  ? await mongocryptdManager.withRespawn(
238
- this.markCommand.bind(this, mongocryptdClient, context.ns, command, timeoutContext)
246
+ this.markCommand.bind(this, mongocryptdClient, context.ns, command, options)
239
247
  )
240
- : await this.markCommand(mongocryptdClient, context.ns, command, timeoutContext);
248
+ : await this.markCommand(mongocryptdClient, context.ns, command, options);
241
249
 
242
250
  context.addMongoOperationResponse(markedCommand);
243
251
  context.finishMongoOperation();
@@ -246,12 +254,7 @@ export class StateMachine {
246
254
 
247
255
  case MONGOCRYPT_CTX_NEED_MONGO_KEYS: {
248
256
  const filter = context.nextMongoOperation();
249
- const keys = await this.fetchKeys(
250
- keyVaultClient,
251
- keyVaultNamespace,
252
- filter,
253
- timeoutContext
254
- );
257
+ const keys = await this.fetchKeys(keyVaultClient, keyVaultNamespace, filter, options);
255
258
 
256
259
  if (keys.length === 0) {
257
260
  // See docs on EMPTY_V
@@ -273,7 +276,7 @@ export class StateMachine {
273
276
  }
274
277
 
275
278
  case MONGOCRYPT_CTX_NEED_KMS: {
276
- await Promise.all(this.requests(context, timeoutContext));
279
+ await Promise.all(this.requests(context, options));
277
280
  context.finishKMSRequests();
278
281
  break;
279
282
  }
@@ -315,11 +318,13 @@ export class StateMachine {
315
318
  * @param kmsContext - A C++ KMS context returned from the bindings
316
319
  * @returns A promise that resolves when the KMS reply has be fully parsed
317
320
  */
318
- async kmsRequest(request: MongoCryptKMSRequest, timeoutContext?: TimeoutContext): Promise<void> {
321
+ async kmsRequest(
322
+ request: MongoCryptKMSRequest,
323
+ options?: { timeoutContext?: TimeoutContext } & Abortable
324
+ ): Promise<void> {
319
325
  const parsedUrl = request.endpoint.split(':');
320
326
  const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
321
- const socketOptions = autoSelectSocketOptions(this.options.socketOptions || {});
322
- const options: tls.ConnectionOptions & {
327
+ const socketOptions: tls.ConnectionOptions & {
323
328
  host: string;
324
329
  port: number;
325
330
  autoSelectFamily?: boolean;
@@ -328,7 +333,7 @@ export class StateMachine {
328
333
  host: parsedUrl[0],
329
334
  servername: parsedUrl[0],
330
335
  port,
331
- ...socketOptions
336
+ ...autoSelectSocketOptions(this.options.socketOptions || {})
332
337
  };
333
338
  const message = request.message;
334
339
  const buffer = new BufferPool();
@@ -363,7 +368,7 @@ export class StateMachine {
363
368
  throw error;
364
369
  }
365
370
  try {
366
- await this.setTlsOptions(providerTlsOptions, options);
371
+ await this.setTlsOptions(providerTlsOptions, socketOptions);
367
372
  } catch (err) {
368
373
  throw onerror(err);
369
374
  }
@@ -380,23 +385,25 @@ export class StateMachine {
380
385
  .once('close', () => rejectOnNetSocketError(onclose()))
381
386
  .once('connect', () => resolveOnNetSocketConnect());
382
387
 
388
+ let abortListener;
389
+
383
390
  try {
384
391
  if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
385
392
  const netSocketOptions = {
393
+ ...socketOptions,
386
394
  host: this.options.proxyOptions.proxyHost,
387
- port: this.options.proxyOptions.proxyPort || 1080,
388
- ...socketOptions
395
+ port: this.options.proxyOptions.proxyPort || 1080
389
396
  };
390
397
  netSocket.connect(netSocketOptions);
391
398
  await willConnect;
392
399
 
393
400
  try {
394
401
  socks ??= loadSocks();
395
- options.socket = (
402
+ socketOptions.socket = (
396
403
  await socks.SocksClient.createConnection({
397
404
  existing_socket: netSocket,
398
405
  command: 'connect',
399
- destination: { host: options.host, port: options.port },
406
+ destination: { host: socketOptions.host, port: socketOptions.port },
400
407
  proxy: {
401
408
  // host and port are ignored because we pass existing_socket
402
409
  host: 'iLoveJavaScript',
@@ -412,7 +419,7 @@ export class StateMachine {
412
419
  }
413
420
  }
414
421
 
415
- socket = tls.connect(options, () => {
422
+ socket = tls.connect(socketOptions, () => {
416
423
  socket.write(message);
417
424
  });
418
425
 
@@ -422,6 +429,11 @@ export class StateMachine {
422
429
  resolve
423
430
  } = promiseWithResolvers<void>();
424
431
 
432
+ abortListener = addAbortListener(options?.signal, function () {
433
+ destroySockets();
434
+ rejectOnTlsSocketError(this.reason);
435
+ });
436
+
425
437
  socket
426
438
  .once('error', err => rejectOnTlsSocketError(onerror(err)))
427
439
  .once('close', () => rejectOnTlsSocketError(onclose()))
@@ -436,8 +448,11 @@ export class StateMachine {
436
448
  resolve();
437
449
  }
438
450
  });
439
- await (timeoutContext?.csotEnabled()
440
- ? Promise.all([willResolveKmsRequest, Timeout.expires(timeoutContext?.remainingTimeMS)])
451
+ await (options?.timeoutContext?.csotEnabled()
452
+ ? Promise.all([
453
+ willResolveKmsRequest,
454
+ Timeout.expires(options.timeoutContext?.remainingTimeMS)
455
+ ])
441
456
  : willResolveKmsRequest);
442
457
  } catch (error) {
443
458
  if (error instanceof TimeoutError)
@@ -446,16 +461,17 @@ export class StateMachine {
446
461
  } finally {
447
462
  // There's no need for any more activity on this socket at this point.
448
463
  destroySockets();
464
+ abortListener?.[kDispose]();
449
465
  }
450
466
  }
451
467
 
452
- *requests(context: MongoCryptContext, timeoutContext?: TimeoutContext) {
468
+ *requests(context: MongoCryptContext, options?: { timeoutContext?: TimeoutContext } & Abortable) {
453
469
  for (
454
470
  let request = context.nextKMSRequest();
455
471
  request != null;
456
472
  request = context.nextKMSRequest()
457
473
  ) {
458
- yield this.kmsRequest(request, timeoutContext);
474
+ yield this.kmsRequest(request, options);
459
475
  }
460
476
  }
461
477
 
@@ -516,14 +532,16 @@ export class StateMachine {
516
532
  client: MongoClient,
517
533
  ns: string,
518
534
  filter: Document,
519
- timeoutContext?: TimeoutContext
535
+ options?: { timeoutContext?: TimeoutContext } & Abortable
520
536
  ): Promise<Uint8Array | null> {
521
537
  const { db } = MongoDBCollectionNamespace.fromString(ns);
522
538
 
523
539
  const cursor = client.db(db).listCollections(filter, {
524
540
  promoteLongs: false,
525
541
  promoteValues: false,
526
- timeoutContext: timeoutContext && new CursorTimeoutContext(timeoutContext, Symbol())
542
+ timeoutContext:
543
+ options?.timeoutContext && new CursorTimeoutContext(options?.timeoutContext, Symbol()),
544
+ signal: options?.signal
527
545
  });
528
546
 
529
547
  // There is always exactly zero or one matching documents, so this should always exhaust the cursor
@@ -547,17 +565,30 @@ export class StateMachine {
547
565
  client: MongoClient,
548
566
  ns: string,
549
567
  command: Uint8Array,
550
- timeoutContext?: TimeoutContext
568
+ options?: { timeoutContext?: TimeoutContext } & Abortable
551
569
  ): Promise<Uint8Array> {
552
570
  const { db } = MongoDBCollectionNamespace.fromString(ns);
553
571
  const bsonOptions = { promoteLongs: false, promoteValues: false };
554
572
  const rawCommand = deserialize(command, bsonOptions);
555
573
 
574
+ const commandOptions: {
575
+ timeoutMS?: number;
576
+ signal?: AbortSignal;
577
+ } = {
578
+ timeoutMS: undefined,
579
+ signal: undefined
580
+ };
581
+
582
+ if (options?.timeoutContext?.csotEnabled()) {
583
+ commandOptions.timeoutMS = options.timeoutContext.remainingTimeMS;
584
+ }
585
+ if (options?.signal) {
586
+ commandOptions.signal = options.signal;
587
+ }
588
+
556
589
  const response = await client.db(db).command(rawCommand, {
557
590
  ...bsonOptions,
558
- ...(timeoutContext?.csotEnabled()
559
- ? { timeoutMS: timeoutContext?.remainingTimeMS }
560
- : undefined)
591
+ ...commandOptions
561
592
  });
562
593
 
563
594
  return serialize(response, this.bsonOptions);
@@ -575,17 +606,30 @@ export class StateMachine {
575
606
  client: MongoClient,
576
607
  keyVaultNamespace: string,
577
608
  filter: Uint8Array,
578
- timeoutContext?: TimeoutContext
609
+ options?: { timeoutContext?: TimeoutContext } & Abortable
579
610
  ): Promise<Array<DataKey>> {
580
611
  const { db: dbName, collection: collectionName } =
581
612
  MongoDBCollectionNamespace.fromString(keyVaultNamespace);
582
613
 
614
+ const commandOptions: {
615
+ timeoutContext?: CursorTimeoutContext;
616
+ signal?: AbortSignal;
617
+ } = {
618
+ timeoutContext: undefined,
619
+ signal: undefined
620
+ };
621
+
622
+ if (options?.timeoutContext != null) {
623
+ commandOptions.timeoutContext = new CursorTimeoutContext(options.timeoutContext, Symbol());
624
+ }
625
+ if (options?.signal != null) {
626
+ commandOptions.signal = options.signal;
627
+ }
628
+
583
629
  return client
584
630
  .db(dbName)
585
631
  .collection<DataKey>(collectionName, { readConcern: { level: 'majority' } })
586
- .find(deserialize(filter), {
587
- timeoutContext: timeoutContext && new CursorTimeoutContext(timeoutContext, Symbol())
588
- })
632
+ .find(deserialize(filter), commandOptions)
589
633
  .toArray();
590
634
  }
591
635
  }
@@ -143,7 +143,7 @@ export class MongoDBOIDC extends AuthProvider {
143
143
  */
144
144
  override async auth(authContext: AuthContext): Promise<void> {
145
145
  const { connection, reauthenticating, response } = authContext;
146
- if (response?.speculativeAuthenticate?.done) {
146
+ if (response?.speculativeAuthenticate?.done && !reauthenticating) {
147
147
  return;
148
148
  }
149
149
  const credentials = getCredentials(authContext);
@@ -6,7 +6,7 @@ import {
6
6
  LEGACY_HELLO_COMMAND,
7
7
  LEGACY_HELLO_COMMAND_CAMEL_CASE
8
8
  } from '../constants';
9
- import { calculateDurationInMs, deepCopy } from '../utils';
9
+ import { calculateDurationInMs } from '../utils';
10
10
  import {
11
11
  DocumentSequence,
12
12
  OpMsgRequest,
@@ -125,7 +125,7 @@ export class CommandSucceededEvent {
125
125
  this.requestId = command.requestId;
126
126
  this.commandName = commandName;
127
127
  this.duration = calculateDurationInMs(started);
128
- this.reply = maybeRedact(commandName, cmd, extractReply(command, reply));
128
+ this.reply = maybeRedact(commandName, cmd, extractReply(reply));
129
129
  this.serverConnectionId = serverConnectionId;
130
130
  }
131
131
 
@@ -214,7 +214,6 @@ const HELLO_COMMANDS = new Set(['hello', LEGACY_HELLO_COMMAND, LEGACY_HELLO_COMM
214
214
 
215
215
  // helper methods
216
216
  const extractCommandName = (commandDoc: Document) => Object.keys(commandDoc)[0];
217
- const namespace = (command: OpQueryRequest) => command.ns;
218
217
  const collectionName = (command: OpQueryRequest) => command.ns.split('.')[1];
219
218
  const maybeRedact = (commandName: string, commandDoc: Document, result: Error | Document) =>
220
219
  SENSITIVE_COMMANDS.has(commandName) ||
@@ -242,19 +241,10 @@ const LEGACY_FIND_OPTIONS_MAP = {
242
241
  returnFieldSelector: 'projection'
243
242
  } as const;
244
243
 
245
- const OP_QUERY_KEYS = [
246
- 'tailable',
247
- 'oplogReplay',
248
- 'noCursorTimeout',
249
- 'awaitData',
250
- 'partial',
251
- 'exhaust'
252
- ] as const;
253
-
254
244
  /** Extract the actual command from the query, possibly up-converting if it's a legacy format */
255
245
  function extractCommand(command: WriteProtocolMessageType): Document {
256
246
  if (command instanceof OpMsgRequest) {
257
- const cmd = deepCopy(command.command);
247
+ const cmd = { ...command.command };
258
248
  // For OP_MSG with payload type 1 we need to pull the documents
259
249
  // array out of the document sequence for monitoring.
260
250
  if (cmd.ops instanceof DocumentSequence) {
@@ -276,7 +266,7 @@ function extractCommand(command: WriteProtocolMessageType): Document {
276
266
  result = { find: collectionName(command) };
277
267
  Object.keys(LEGACY_FIND_QUERY_MAP).forEach(key => {
278
268
  if (command.query[key] != null) {
279
- result[LEGACY_FIND_QUERY_MAP[key]] = deepCopy(command.query[key]);
269
+ result[LEGACY_FIND_QUERY_MAP[key]] = { ...command.query[key] };
280
270
  }
281
271
  });
282
272
  }
@@ -284,64 +274,29 @@ function extractCommand(command: WriteProtocolMessageType): Document {
284
274
  Object.keys(LEGACY_FIND_OPTIONS_MAP).forEach(key => {
285
275
  const legacyKey = key as keyof typeof LEGACY_FIND_OPTIONS_MAP;
286
276
  if (command[legacyKey] != null) {
287
- result[LEGACY_FIND_OPTIONS_MAP[legacyKey]] = deepCopy(command[legacyKey]);
288
- }
289
- });
290
-
291
- OP_QUERY_KEYS.forEach(key => {
292
- if (command[key]) {
293
- result[key] = command[key];
277
+ result[LEGACY_FIND_OPTIONS_MAP[legacyKey]] = command[legacyKey];
294
278
  }
295
279
  });
296
280
 
297
- if (command.pre32Limit != null) {
298
- result.limit = command.pre32Limit;
299
- }
300
-
301
- if (command.query.$explain) {
302
- return { explain: result };
303
- }
304
281
  return result;
305
282
  }
306
283
 
307
- const clonedQuery: Record<string, unknown> = {};
308
- const clonedCommand: Record<string, unknown> = {};
284
+ let clonedQuery: Record<string, unknown> = {};
285
+ const clonedCommand: Record<string, unknown> = { ...command };
309
286
  if (command.query) {
310
- for (const k in command.query) {
311
- clonedQuery[k] = deepCopy(command.query[k]);
312
- }
287
+ clonedQuery = { ...command.query };
313
288
  clonedCommand.query = clonedQuery;
314
289
  }
315
290
 
316
- for (const k in command) {
317
- if (k === 'query') continue;
318
- clonedCommand[k] = deepCopy((command as unknown as Record<string, unknown>)[k]);
319
- }
320
291
  return command.query ? clonedQuery : clonedCommand;
321
292
  }
322
293
 
323
- function extractReply(command: WriteProtocolMessageType, reply?: Document) {
294
+ function extractReply(reply?: Document) {
324
295
  if (!reply) {
325
296
  return reply;
326
297
  }
327
298
 
328
- if (command instanceof OpMsgRequest) {
329
- return deepCopy(reply.result ? reply.result : reply);
330
- }
331
-
332
- // is this a legacy find command?
333
- if (command.query && command.query.$query != null) {
334
- return {
335
- ok: 1,
336
- cursor: {
337
- id: deepCopy(reply.cursorId),
338
- ns: namespace(command),
339
- firstBatch: deepCopy(reply.documents)
340
- }
341
- };
342
- }
343
-
344
- return deepCopy(reply.result ? reply.result : reply);
299
+ return reply.result ? reply.result : reply;
345
300
  }
346
301
 
347
302
  function extractConnectionDetails(connection: Connection) {