mongodb 6.12.0-dev.20250125.sha.c1bcf0de → 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 (59) hide show
  1. package/lib/beta.d.ts +55 -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/cursor/abstract_cursor.js +47 -18
  16. package/lib/cursor/abstract_cursor.js.map +1 -1
  17. package/lib/cursor/aggregation_cursor.js +2 -1
  18. package/lib/cursor/aggregation_cursor.js.map +1 -1
  19. package/lib/cursor/find_cursor.js +2 -1
  20. package/lib/cursor/find_cursor.js.map +1 -1
  21. package/lib/cursor/list_collections_cursor.js +2 -1
  22. package/lib/cursor/list_collections_cursor.js.map +1 -1
  23. package/lib/db.js +2 -1
  24. package/lib/db.js.map +1 -1
  25. package/lib/mongo_client.js +4 -0
  26. package/lib/mongo_client.js.map +1 -1
  27. package/lib/operations/execute_operation.js +7 -3
  28. package/lib/operations/execute_operation.js.map +1 -1
  29. package/lib/operations/list_collections.js.map +1 -1
  30. package/lib/operations/operation.js.map +1 -1
  31. package/lib/sdam/server.js +26 -16
  32. package/lib/sdam/server.js.map +1 -1
  33. package/lib/sdam/topology.js +5 -0
  34. package/lib/sdam/topology.js.map +1 -1
  35. package/lib/utils.js +64 -7
  36. package/lib/utils.js.map +1 -1
  37. package/mongodb.d.ts +55 -13
  38. package/package.json +1 -1
  39. package/src/client-side-encryption/auto_encrypter.ts +12 -8
  40. package/src/client-side-encryption/client_encryption.ts +6 -4
  41. package/src/client-side-encryption/state_machine.ts +80 -36
  42. package/src/cmap/connection.ts +37 -29
  43. package/src/cmap/connection_pool.ts +17 -3
  44. package/src/cmap/wire_protocol/on_data.ts +9 -2
  45. package/src/collection.ts +15 -8
  46. package/src/cursor/abstract_cursor.ts +71 -23
  47. package/src/cursor/aggregation_cursor.ts +5 -3
  48. package/src/cursor/find_cursor.ts +5 -3
  49. package/src/cursor/list_collections_cursor.ts +5 -3
  50. package/src/db.ts +11 -7
  51. package/src/index.ts +1 -0
  52. package/src/mongo_client.ts +13 -0
  53. package/src/mongo_types.ts +38 -0
  54. package/src/operations/execute_operation.ts +9 -4
  55. package/src/operations/list_collections.ts +4 -1
  56. package/src/operations/operation.ts +3 -2
  57. package/src/sdam/server.ts +31 -18
  58. package/src/sdam/topology.ts +10 -2
  59. package/src/utils.ts +79 -6
@@ -36,12 +36,13 @@ import {
36
36
  needsRetryableWriteLabel
37
37
  } from '../error';
38
38
  import type { ServerApi } from '../mongo_client';
39
- import { TypedEventEmitter } from '../mongo_types';
39
+ import { type Abortable, TypedEventEmitter } from '../mongo_types';
40
40
  import type { GetMoreOptions } from '../operations/get_more';
41
41
  import type { ClientSession } from '../sessions';
42
42
  import { type TimeoutContext } from '../timeout';
43
43
  import { isTransactionCommand } from '../transactions';
44
44
  import {
45
+ abortable,
45
46
  type EventEmitterWithState,
46
47
  makeStateMachine,
47
48
  maxWireVersion,
@@ -107,7 +108,7 @@ export type ServerEvents = {
107
108
  /** @internal */
108
109
  export type ServerCommandOptions = Omit<CommandOptions, 'timeoutContext' | 'socketTimeoutMS'> & {
109
110
  timeoutContext: TimeoutContext;
110
- };
111
+ } & Abortable;
111
112
 
112
113
  /** @internal */
113
114
  export class Server extends TypedEventEmitter<ServerEvents> {
@@ -285,7 +286,7 @@ export class Server extends TypedEventEmitter<ServerEvents> {
285
286
  public async command(
286
287
  ns: MongoDBNamespace,
287
288
  cmd: Document,
288
- options: ServerCommandOptions,
289
+ { ...options }: ServerCommandOptions,
289
290
  responseType?: MongoDBResponseConstructor
290
291
  ): Promise<Document> {
291
292
  if (ns.db == null || typeof ns === 'string') {
@@ -296,25 +297,21 @@ export class Server extends TypedEventEmitter<ServerEvents> {
296
297
  throw new MongoServerClosedError();
297
298
  }
298
299
 
299
- // Clone the options
300
- const finalOptions = Object.assign({}, options, {
301
- wireProtocolCommand: false,
302
- directConnection: this.topology.s.options.directConnection
303
- });
300
+ options.directConnection = this.topology.s.options.directConnection;
304
301
 
305
302
  // There are cases where we need to flag the read preference not to get sent in
306
303
  // the command, such as pre-5.0 servers attempting to perform an aggregate write
307
304
  // with a non-primary read preference. In this case the effective read preference
308
305
  // (primary) is not the same as the provided and must be removed completely.
309
- if (finalOptions.omitReadPreference) {
310
- delete finalOptions.readPreference;
306
+ if (options.omitReadPreference) {
307
+ delete options.readPreference;
311
308
  }
312
309
 
313
310
  if (this.description.iscryptd) {
314
- finalOptions.omitMaxTimeMS = true;
311
+ options.omitMaxTimeMS = true;
315
312
  }
316
313
 
317
- const session = finalOptions.session;
314
+ const session = options.session;
318
315
  let conn = session?.pinnedConnection;
319
316
 
320
317
  this.incrementOperationCount();
@@ -331,26 +328,35 @@ export class Server extends TypedEventEmitter<ServerEvents> {
331
328
  }
332
329
  }
333
330
 
331
+ let reauthPromise: Promise<void> | null = null;
332
+
334
333
  try {
335
334
  try {
336
- const res = await conn.command(ns, cmd, finalOptions, responseType);
335
+ const res = await conn.command(ns, cmd, options, responseType);
337
336
  throwIfWriteConcernError(res);
338
337
  return res;
339
338
  } catch (commandError) {
340
- throw this.decorateCommandError(conn, cmd, finalOptions, commandError);
339
+ throw this.decorateCommandError(conn, cmd, options, commandError);
341
340
  }
342
341
  } catch (operationError) {
343
342
  if (
344
343
  operationError instanceof MongoError &&
345
344
  operationError.code === MONGODB_ERROR_CODES.Reauthenticate
346
345
  ) {
347
- await this.pool.reauthenticate(conn);
346
+ reauthPromise = this.pool.reauthenticate(conn).catch(error => {
347
+ reauthPromise = null;
348
+ throw error;
349
+ });
350
+
351
+ await abortable(reauthPromise, options);
352
+ reauthPromise = null; // only reachable if reauth succeeds
353
+
348
354
  try {
349
- const res = await conn.command(ns, cmd, finalOptions, responseType);
355
+ const res = await conn.command(ns, cmd, options, responseType);
350
356
  throwIfWriteConcernError(res);
351
357
  return res;
352
358
  } catch (commandError) {
353
- throw this.decorateCommandError(conn, cmd, finalOptions, commandError);
359
+ throw this.decorateCommandError(conn, cmd, options, commandError);
354
360
  }
355
361
  } else {
356
362
  throw operationError;
@@ -358,7 +364,14 @@ export class Server extends TypedEventEmitter<ServerEvents> {
358
364
  } finally {
359
365
  this.decrementOperationCount();
360
366
  if (session?.pinnedConnection !== conn) {
361
- this.pool.checkIn(conn);
367
+ if (reauthPromise != null) {
368
+ // The reauth promise only exists if it hasn't thrown.
369
+ void reauthPromise.finally(() => {
370
+ this.pool.checkIn(conn);
371
+ });
372
+ } else {
373
+ this.pool.checkIn(conn);
374
+ }
362
375
  }
363
376
  }
364
377
  }
@@ -31,15 +31,17 @@ import {
31
31
  } from '../error';
32
32
  import type { MongoClient, ServerApi } from '../mongo_client';
33
33
  import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger';
34
- import { TypedEventEmitter } from '../mongo_types';
34
+ import { type Abortable, TypedEventEmitter } from '../mongo_types';
35
35
  import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
36
36
  import type { ClientSession } from '../sessions';
37
37
  import { Timeout, TimeoutContext, TimeoutError } from '../timeout';
38
38
  import type { Transaction } from '../transactions';
39
39
  import {
40
+ addAbortListener,
40
41
  type Callback,
41
42
  type EventEmitterWithState,
42
43
  HostAddress,
44
+ kDispose,
43
45
  List,
44
46
  makeStateMachine,
45
47
  now,
@@ -525,7 +527,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
525
527
  */
526
528
  async selectServer(
527
529
  selector: string | ReadPreference | ServerSelector,
528
- options: SelectServerOptions
530
+ options: SelectServerOptions & Abortable
529
531
  ): Promise<Server> {
530
532
  let serverSelector;
531
533
  if (typeof selector !== 'function') {
@@ -602,6 +604,11 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
602
604
  previousServer: options.previousServer
603
605
  };
604
606
 
607
+ const abortListener = addAbortListener(options.signal, function () {
608
+ waitQueueMember.cancelled = true;
609
+ reject(this.reason);
610
+ });
611
+
605
612
  this.waitQueue.push(waitQueueMember);
606
613
  processWaitQueue(this);
607
614
 
@@ -647,6 +654,7 @@ export class Topology extends TypedEventEmitter<TopologyEvents> {
647
654
  // Other server selection error
648
655
  throw error;
649
656
  } finally {
657
+ abortListener?.[kDispose]();
650
658
  if (options.timeoutContext?.clearServerSelectionTimeout) timeout?.clear();
651
659
  }
652
660
  }
package/src/utils.ts CHANGED
@@ -27,6 +27,7 @@ import {
27
27
  MongoRuntimeError
28
28
  } from './error';
29
29
  import type { MongoClient } from './mongo_client';
30
+ import { type Abortable } from './mongo_types';
30
31
  import type { CommandOperationOptions, OperationParent } from './operations/command';
31
32
  import type { Hint, OperationOptions } from './operations/operation';
32
33
  import { ReadConcern } from './read_concern';
@@ -1312,19 +1313,24 @@ export const randomBytes = promisify(crypto.randomBytes);
1312
1313
  * @param ee - An event emitter that may emit `ev`
1313
1314
  * @param name - An event name to wait for
1314
1315
  */
1315
- export async function once<T>(ee: EventEmitter, name: string): Promise<T> {
1316
+ export async function once<T>(ee: EventEmitter, name: string, options?: Abortable): Promise<T> {
1317
+ options?.signal?.throwIfAborted();
1318
+
1316
1319
  const { promise, resolve, reject } = promiseWithResolvers<T>();
1317
1320
  const onEvent = (data: T) => resolve(data);
1318
1321
  const onError = (error: Error) => reject(error);
1322
+ const abortListener = addAbortListener(options?.signal, function () {
1323
+ reject(this.reason);
1324
+ });
1319
1325
 
1320
1326
  ee.once(name, onEvent).once('error', onError);
1327
+
1321
1328
  try {
1322
- const res = await promise;
1323
- ee.off('error', onError);
1324
- return res;
1325
- } catch (error) {
1329
+ return await promise;
1330
+ } finally {
1326
1331
  ee.off(name, onEvent);
1327
- throw error;
1332
+ ee.off('error', onError);
1333
+ abortListener?.[kDispose]();
1328
1334
  }
1329
1335
  }
1330
1336
 
@@ -1431,3 +1437,70 @@ export function decorateDecryptionResult(
1431
1437
  decorateDecryptionResult(decrypted[k], originalValue, false);
1432
1438
  }
1433
1439
  }
1440
+
1441
+ /** @internal */
1442
+ export const kDispose: unique symbol = (Symbol.dispose as any) ?? Symbol('dispose');
1443
+
1444
+ /** @internal */
1445
+ export interface Disposable {
1446
+ [kDispose](): void;
1447
+ }
1448
+
1449
+ /**
1450
+ * A utility that helps with writing listener code idiomatically
1451
+ *
1452
+ * @example
1453
+ * ```js
1454
+ * using listener = addAbortListener(signal, function () {
1455
+ * console.log('aborted', this.reason);
1456
+ * });
1457
+ * ```
1458
+ *
1459
+ * @param signal - if exists adds an abort listener
1460
+ * @param listener - the listener to be added to signal
1461
+ * @returns A disposable that will remove the abort listener
1462
+ */
1463
+ export function addAbortListener(
1464
+ signal: AbortSignal | undefined | null,
1465
+ listener: (this: AbortSignal, event: Event) => void
1466
+ ): Disposable | undefined {
1467
+ if (signal == null) return;
1468
+ signal.addEventListener('abort', listener, { once: true });
1469
+ return { [kDispose]: () => signal.removeEventListener('abort', listener) };
1470
+ }
1471
+
1472
+ /**
1473
+ * Takes a promise and races it with a promise wrapping the abort event of the optionally provided signal.
1474
+ * The given promise is _always_ ordered before the signal's abort promise.
1475
+ * When given an already rejected promise and an already aborted signal, the promise's rejection takes precedence.
1476
+ *
1477
+ * Any asynchronous processing in `promise` will continue even after the abort signal has fired,
1478
+ * but control will be returned to the caller
1479
+ *
1480
+ * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
1481
+ *
1482
+ * @param promise - A promise to discard if the signal aborts
1483
+ * @param options - An options object carrying an optional signal
1484
+ */
1485
+ export async function abortable<T>(
1486
+ promise: Promise<T>,
1487
+ { signal }: { signal?: AbortSignal }
1488
+ ): Promise<T> {
1489
+ if (signal == null) {
1490
+ return await promise;
1491
+ }
1492
+
1493
+ const { promise: aborted, reject } = promiseWithResolvers<never>();
1494
+
1495
+ const abortListener = signal.aborted
1496
+ ? reject(signal.reason)
1497
+ : addAbortListener(signal, function () {
1498
+ reject(this.reason);
1499
+ });
1500
+
1501
+ try {
1502
+ return await Promise.race([promise, aborted]);
1503
+ } finally {
1504
+ abortListener?.[kDispose]();
1505
+ }
1506
+ }