@depup/mongodb 7.5.0-depup.8 → 7.7.0-depup.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 (111) hide show
  1. package/README.md +5 -6
  2. package/changes.json +6 -10
  3. package/lib/bulk/common.js +27 -5
  4. package/lib/bulk/common.js.map +1 -1
  5. package/lib/bulk/ordered.js +25 -7
  6. package/lib/bulk/ordered.js.map +1 -1
  7. package/lib/bulk/unordered.js +25 -7
  8. package/lib/bulk/unordered.js.map +1 -1
  9. package/lib/client-side-encryption/auto_encrypter.js +8 -2
  10. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  11. package/lib/client-side-encryption/client_encryption.js +12 -4
  12. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  13. package/lib/client-side-encryption/kms_options.js +3 -0
  14. package/lib/client-side-encryption/kms_options.js.map +1 -0
  15. package/lib/client-side-encryption/state_machine.js +54 -7
  16. package/lib/client-side-encryption/state_machine.js.map +1 -1
  17. package/lib/cmap/auth/gssapi.js +2 -1
  18. package/lib/cmap/auth/gssapi.js.map +1 -1
  19. package/lib/cmap/auth/mongo_credentials.js +0 -1
  20. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  21. package/lib/cmap/auth/mongodb_aws.js +0 -3
  22. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  23. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +7 -8
  24. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -1
  25. package/lib/cmap/command_monitoring_events.js +6 -7
  26. package/lib/cmap/command_monitoring_events.js.map +1 -1
  27. package/lib/cmap/commands.js +13 -0
  28. package/lib/cmap/commands.js.map +1 -1
  29. package/lib/cmap/connect.js +2 -2
  30. package/lib/cmap/connect.js.map +1 -1
  31. package/lib/cmap/connection.js +3 -1
  32. package/lib/cmap/connection.js.map +1 -1
  33. package/lib/cmap/handshake/client_metadata.js +2 -1
  34. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  35. package/lib/cmap/wire_protocol/constants.js +2 -2
  36. package/lib/connection_string.js +4 -0
  37. package/lib/connection_string.js.map +1 -1
  38. package/lib/cursor/abstract_cursor.js.map +1 -1
  39. package/lib/error.js +7 -11
  40. package/lib/error.js.map +1 -1
  41. package/lib/gridfs/download.js +1 -1
  42. package/lib/gridfs/download.js.map +1 -1
  43. package/lib/index.js.map +1 -1
  44. package/lib/operations/command.js.map +1 -1
  45. package/lib/operations/delete.js +7 -10
  46. package/lib/operations/delete.js.map +1 -1
  47. package/lib/operations/execute_operation.js +29 -2
  48. package/lib/operations/execute_operation.js.map +1 -1
  49. package/lib/operations/find_and_modify.js +1 -5
  50. package/lib/operations/find_and_modify.js.map +1 -1
  51. package/lib/operations/get_more.js +2 -3
  52. package/lib/operations/get_more.js.map +1 -1
  53. package/lib/operations/indexes.js +3 -9
  54. package/lib/operations/indexes.js.map +1 -1
  55. package/lib/operations/insert.js +6 -2
  56. package/lib/operations/insert.js.map +1 -1
  57. package/lib/operations/list_collections.js +2 -3
  58. package/lib/operations/list_collections.js.map +1 -1
  59. package/lib/operations/list_databases.js +2 -2
  60. package/lib/operations/list_databases.js.map +1 -1
  61. package/lib/operations/update.js +6 -2
  62. package/lib/operations/update.js.map +1 -1
  63. package/lib/runtime_adapters.js +15 -16
  64. package/lib/runtime_adapters.js.map +1 -1
  65. package/lib/sdam/server.js +1 -1
  66. package/lib/sdam/server.js.map +1 -1
  67. package/lib/sessions.js +2 -2
  68. package/lib/sessions.js.map +1 -1
  69. package/lib/utils.js +1 -3
  70. package/lib/utils.js.map +1 -1
  71. package/mongodb.d.ts +77 -20
  72. package/package.json +30 -33
  73. package/src/bulk/common.ts +37 -5
  74. package/src/bulk/ordered.ts +23 -7
  75. package/src/bulk/unordered.ts +23 -8
  76. package/src/client-side-encryption/auto_encrypter.ts +19 -3
  77. package/src/client-side-encryption/client_encryption.ts +29 -9
  78. package/src/client-side-encryption/kms_options.ts +93 -0
  79. package/src/client-side-encryption/state_machine.ts +68 -47
  80. package/src/cmap/auth/gssapi.ts +3 -4
  81. package/src/cmap/auth/mongo_credentials.ts +0 -1
  82. package/src/cmap/auth/mongodb_aws.ts +2 -12
  83. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +17 -17
  84. package/src/cmap/command_monitoring_events.ts +9 -10
  85. package/src/cmap/commands.ts +17 -0
  86. package/src/cmap/connect.ts +3 -3
  87. package/src/cmap/connection.ts +4 -2
  88. package/src/cmap/handshake/client_metadata.ts +2 -1
  89. package/src/cmap/wire_protocol/constants.ts +2 -2
  90. package/src/connection_string.ts +4 -0
  91. package/src/cursor/abstract_cursor.ts +0 -5
  92. package/src/error.ts +7 -19
  93. package/src/gridfs/download.ts +1 -1
  94. package/src/index.ts +7 -6
  95. package/src/mongo_client.ts +1 -1
  96. package/src/operations/command.ts +0 -5
  97. package/src/operations/delete.ts +17 -15
  98. package/src/operations/execute_operation.ts +29 -2
  99. package/src/operations/find_and_modify.ts +3 -15
  100. package/src/operations/get_more.ts +3 -5
  101. package/src/operations/indexes.ts +6 -16
  102. package/src/operations/insert.ts +13 -2
  103. package/src/operations/list_collections.ts +5 -6
  104. package/src/operations/list_databases.ts +3 -3
  105. package/src/operations/update.ts +9 -2
  106. package/src/operations/validate_collection.ts +1 -1
  107. package/src/runtime_adapters.ts +19 -18
  108. package/src/sdam/server.ts +1 -1
  109. package/src/sessions.ts +2 -5
  110. package/src/utils.ts +1 -3
  111. package/tsconfig.json +9 -2
@@ -31,6 +31,11 @@ import {
31
31
  MongoCryptCreateEncryptedCollectionError,
32
32
  MongoCryptInvalidArgumentError
33
33
  } from './errors';
34
+ import {
35
+ type ClientEncryptionSocketOptions,
36
+ type CSFLEKMSTlsOptions,
37
+ type KMSConnectCallback
38
+ } from './kms_options';
34
39
  import {
35
40
  type ClientEncryptionDataKeyProvider,
36
41
  type CredentialProviders,
@@ -38,11 +43,7 @@ import {
38
43
  type KMSProviders,
39
44
  refreshKMSCredentials
40
45
  } from './providers/index';
41
- import {
42
- type ClientEncryptionSocketOptions,
43
- type CSFLEKMSTlsOptions,
44
- StateMachine
45
- } from './state_machine';
46
+ import { StateMachine } from './state_machine';
46
47
 
47
48
  /**
48
49
  * @public
@@ -75,6 +76,8 @@ export class ClientEncryption {
75
76
  /** @internal */
76
77
  _tlsOptions: CSFLEKMSTlsOptions;
77
78
  /** @internal */
79
+ _kmsConnectCallback?: KMSConnectCallback;
80
+ /** @internal */
78
81
  _kmsProviders: KMSProviders;
79
82
  /** @internal */
80
83
  _timeoutMS?: number;
@@ -125,7 +128,13 @@ export class ClientEncryption {
125
128
  constructor(client: MongoClient, options: ClientEncryptionOptions) {
126
129
  this._client = client;
127
130
  this._proxyOptions = options.proxyOptions ?? {};
131
+ if (this._proxyOptions.proxyHost && options.kmsConnectCallback) {
132
+ throw new MongoCryptInvalidArgumentError(
133
+ 'Cannot set both proxyOptions and kmsConnectCallback'
134
+ );
135
+ }
128
136
  this._tlsOptions = options.tlsOptions ?? {};
137
+ this._kmsConnectCallback = options.kmsConnectCallback;
129
138
  this._kmsProviders = options.kmsProviders || {};
130
139
  const { timeoutMS } = resolveTimeoutOptions(client, options);
131
140
  this._timeoutMS = timeoutMS;
@@ -226,7 +235,8 @@ export class ClientEncryption {
226
235
  const stateMachine = new StateMachine({
227
236
  proxyOptions: this._proxyOptions,
228
237
  tlsOptions: this._tlsOptions,
229
- socketOptions: autoSelectSocketOptions(this._client.s.options)
238
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
239
+ kmsConnectCallback: this._kmsConnectCallback
230
240
  });
231
241
 
232
242
  const timeoutContext =
@@ -295,7 +305,8 @@ export class ClientEncryption {
295
305
  const stateMachine = new StateMachine({
296
306
  proxyOptions: this._proxyOptions,
297
307
  tlsOptions: this._tlsOptions,
298
- socketOptions: autoSelectSocketOptions(this._client.s.options)
308
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
309
+ kmsConnectCallback: this._kmsConnectCallback
299
310
  });
300
311
 
301
312
  const timeoutContext = TimeoutContext.create(
@@ -699,7 +710,8 @@ export class ClientEncryption {
699
710
  const stateMachine = new StateMachine({
700
711
  proxyOptions: this._proxyOptions,
701
712
  tlsOptions: this._tlsOptions,
702
- socketOptions: autoSelectSocketOptions(this._client.s.options)
713
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
714
+ kmsConnectCallback: this._kmsConnectCallback
703
715
  });
704
716
 
705
717
  const timeoutContext =
@@ -797,7 +809,8 @@ export class ClientEncryption {
797
809
  const stateMachine = new StateMachine({
798
810
  proxyOptions: this._proxyOptions,
799
811
  tlsOptions: this._tlsOptions,
800
- socketOptions: autoSelectSocketOptions(this._client.s.options)
812
+ socketOptions: autoSelectSocketOptions(this._client.s.options),
813
+ kmsConnectCallback: this._kmsConnectCallback
801
814
  });
802
815
  const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
803
816
 
@@ -961,6 +974,13 @@ export interface ClientEncryptionOptions {
961
974
  */
962
975
  tlsOptions?: CSFLEKMSTlsOptions;
963
976
 
977
+ /**
978
+ * A callback that establishes the socket used to connect to a KMS host, e.g. to route KMS
979
+ * requests through an HTTP proxy via the HTTP CONNECT method. Mutually exclusive with `proxyOptions`.
980
+ * See {@link KMSConnectCallback} for a usage example.
981
+ */
982
+ kmsConnectCallback?: KMSConnectCallback;
983
+
964
984
  /**
965
985
  * Sets the expiration time for the DEK in the cache in milliseconds. Defaults to 60000. 0 means no timeout.
966
986
  */
@@ -0,0 +1,93 @@
1
+ import { type Duplex } from 'stream';
2
+
3
+ import { type MongoClientOptions } from '../mongo_client';
4
+
5
+ /**
6
+ * @public
7
+ *
8
+ * TLS options to use when connecting. The spec specifically calls out which insecure
9
+ * tls options are not allowed:
10
+ *
11
+ * - tlsAllowInvalidCertificates
12
+ * - tlsAllowInvalidHostnames
13
+ * - tlsInsecure
14
+ *
15
+ * These options are not included in the type, and are ignored if provided.
16
+ */
17
+ export type ClientEncryptionTlsOptions = Pick<
18
+ MongoClientOptions,
19
+ 'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext'
20
+ >;
21
+
22
+ /** @public */
23
+ export type CSFLEKMSTlsOptions = {
24
+ aws?: ClientEncryptionTlsOptions;
25
+ gcp?: ClientEncryptionTlsOptions;
26
+ kmip?: ClientEncryptionTlsOptions;
27
+ local?: ClientEncryptionTlsOptions;
28
+ azure?: ClientEncryptionTlsOptions;
29
+
30
+ [key: string]: ClientEncryptionTlsOptions | undefined;
31
+ };
32
+
33
+ /**
34
+ * @public
35
+ *
36
+ * Socket options to use for KMS requests.
37
+ */
38
+ export type ClientEncryptionSocketOptions = Pick<
39
+ MongoClientOptions,
40
+ 'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
41
+ >;
42
+
43
+ /**
44
+ * @public
45
+ *
46
+ * A callback that establishes the connection to a KMS host.
47
+ *
48
+ * When provided on `AutoEncryptionOptions` or `ClientEncryptionOptions`, the driver invokes this
49
+ * callback instead of connecting to the KMS host itself, passing the target `host` and `port`. The
50
+ * callback MUST return a `Duplex` stream connected to the KMS host, either directly or tunneled
51
+ * through a proxy; a `net.Socket` satisfies this, as does any other `Duplex`. The returned stream is
52
+ * passed to Node.js' `tls.connect()` as its `socket`, and the driver performs the KMS host's TLS
53
+ * handshake over it using the KMS provider's configured TLS options. The callback therefore MUST NOT
54
+ * perform the KMS host's TLS handshake itself, though it MAY use TLS for its own transport, e.g. when
55
+ * connecting to an HTTPS proxy. This enables routing KMS requests through an HTTP proxy via the HTTP
56
+ * CONNECT method.
57
+ *
58
+ * When the operation has a client-side operation timeout (CSOT) configured, `timeoutMS` is the
59
+ * remaining time budget in milliseconds; it is `undefined` otherwise. The `signal` aborts when the
60
+ * connection attempt exceeds that budget; the callback should stop connecting and reject when it fires.
61
+ *
62
+ * @example <caption>Route KMS requests through an HTTP proxy using the HTTP CONNECT method</caption>
63
+ * ```ts
64
+ * import * as net from 'net';
65
+ *
66
+ * const kmsConnectCallback: KMSConnectCallback = ({ host, port, signal }) =>
67
+ * new Promise((resolve, reject) => {
68
+ * // Open a plain connection to the proxy, not to the KMS host.
69
+ * const socket = net.connect({ host: 'proxy.example.com', port: 8080, signal });
70
+ * socket.once('error', reject);
71
+ * socket.once('connect', () => {
72
+ * // Ask the proxy to tunnel to the KMS host, then hand the socket back for the driver's TLS.
73
+ * socket.write(`CONNECT ${host}:${port} HTTP/1.1\r\nHost: ${host}:${port}\r\n\r\n`);
74
+ * socket.once('data', chunk => {
75
+ * if (chunk.toString('utf8').startsWith('HTTP/1.1 200')) resolve(socket);
76
+ * else reject(new Error('Proxy refused the CONNECT request'));
77
+ * });
78
+ * });
79
+ * });
80
+ *
81
+ * const clientEncryption = new ClientEncryption(keyVaultClient, {
82
+ * keyVaultNamespace,
83
+ * kmsProviders,
84
+ * kmsConnectCallback
85
+ * });
86
+ * ```
87
+ */
88
+ export type KMSConnectCallback = (options: {
89
+ host: string;
90
+ port: number;
91
+ timeoutMS?: number;
92
+ signal: AbortSignal;
93
+ }) => Promise<Duplex>;
@@ -15,7 +15,7 @@ import { type ProxyOptions } from '../cmap/connection';
15
15
  import { CursorTimeoutContext } from '../cursor/abstract_cursor';
16
16
  import { getSocks, type SocksLib } from '../deps';
17
17
  import { MongoOperationTimeoutError } from '../error';
18
- import { type MongoClient, type MongoClientOptions } from '../mongo_client';
18
+ import { type MongoClient } from '../mongo_client';
19
19
  import { type Abortable } from '../mongo_types';
20
20
  import { type CollectionInfo } from '../operations/list_collections';
21
21
  import { Timeout, type TimeoutContext, TimeoutError } from '../timeout';
@@ -28,6 +28,12 @@ import {
28
28
  } from '../utils';
29
29
  import { autoSelectSocketOptions, type DataKey } from './client_encryption';
30
30
  import { MongoCryptError } from './errors';
31
+ import {
32
+ type ClientEncryptionSocketOptions,
33
+ type ClientEncryptionTlsOptions,
34
+ type CSFLEKMSTlsOptions,
35
+ type KMSConnectCallback
36
+ } from './kms_options';
31
37
  import { type MongocryptdManager } from './mongocryptd_manager';
32
38
  import { type KMSProviders } from './providers';
33
39
 
@@ -93,44 +99,6 @@ declare module 'mongodb-client-encryption' {
93
99
  }
94
100
  }
95
101
 
96
- /**
97
- * @public
98
- *
99
- * TLS options to use when connecting. The spec specifically calls out which insecure
100
- * tls options are not allowed:
101
- *
102
- * - tlsAllowInvalidCertificates
103
- * - tlsAllowInvalidHostnames
104
- * - tlsInsecure
105
- *
106
- * These options are not included in the type, and are ignored if provided.
107
- */
108
- export type ClientEncryptionTlsOptions = Pick<
109
- MongoClientOptions,
110
- 'tlsCAFile' | 'tlsCertificateKeyFile' | 'tlsCertificateKeyFilePassword' | 'secureContext'
111
- >;
112
-
113
- /** @public */
114
- export type CSFLEKMSTlsOptions = {
115
- aws?: ClientEncryptionTlsOptions;
116
- gcp?: ClientEncryptionTlsOptions;
117
- kmip?: ClientEncryptionTlsOptions;
118
- local?: ClientEncryptionTlsOptions;
119
- azure?: ClientEncryptionTlsOptions;
120
-
121
- [key: string]: ClientEncryptionTlsOptions | undefined;
122
- };
123
-
124
- /**
125
- * @public
126
- *
127
- * Socket options to use for KMS requests.
128
- */
129
- export type ClientEncryptionSocketOptions = Pick<
130
- MongoClientOptions,
131
- 'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
132
- >;
133
-
134
102
  /**
135
103
  * This is kind of a hack. For `rewrapManyDataKey`, we have tests that
136
104
  * guarantee that when there are no matching keys, `rewrapManyDataKey` returns
@@ -173,6 +141,9 @@ export type StateMachineOptions = {
173
141
 
174
142
  /** Socket specific options we support. */
175
143
  socketOptions: ClientEncryptionSocketOptions;
144
+
145
+ /** Optional callback that establishes the KMS socket, if set. */
146
+ kmsConnectCallback?: KMSConnectCallback;
176
147
  } & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;
177
148
 
178
149
  /**
@@ -388,9 +359,55 @@ export class StateMachine {
388
359
  }
389
360
 
390
361
  let abortListener;
362
+ let kmsRequestTimeout: Timeout | undefined;
391
363
 
392
364
  try {
393
- if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
365
+ options?.signal?.throwIfAborted();
366
+
367
+ if (this.options.kmsConnectCallback) {
368
+ const remainingTimeMS = options?.timeoutContext?.csotEnabled()
369
+ ? options.timeoutContext.getRemainingTimeMSOrThrow(
370
+ `KMS request timed out after ${options.timeoutContext.timeoutMS}ms`
371
+ )
372
+ : undefined;
373
+ // A non-finite budget (timeoutMS: 0) means no timeout; otherwise back the callback with one.
374
+ const timeoutMS = Number.isFinite(remainingTimeMS) ? remainingTimeMS : undefined;
375
+ const controller = new AbortController();
376
+ // Forward the operation's own abort signal so aborting the operation also aborts the callback.
377
+ const forwardAbort = addAbortListener(options?.signal, function () {
378
+ controller.abort(this.reason);
379
+ });
380
+ const connectPromise = this.options.kmsConnectCallback({
381
+ host: socketOptions.host,
382
+ port: socketOptions.port,
383
+ timeoutMS,
384
+ signal: controller.signal
385
+ });
386
+ // If the callback resolves a socket after we already aborted, close it so it does not leak.
387
+ connectPromise.then(
388
+ resolvedSocket => {
389
+ if (controller.signal.aborted) resolvedSocket.destroy();
390
+ },
391
+ () => null
392
+ );
393
+ const timeout = timeoutMS ? Timeout.expires(timeoutMS) : undefined;
394
+ try {
395
+ socketOptions.socket = timeout
396
+ ? await Promise.race([connectPromise, timeout])
397
+ : await connectPromise;
398
+ } catch (err) {
399
+ if (TimeoutError.is(err)) {
400
+ const timeoutError = new MongoOperationTimeoutError('KMS request timed out');
401
+ // Abort with the timeout error as the reason so the callback sees it on `signal.reason`.
402
+ controller.abort(timeoutError);
403
+ throw timeoutError;
404
+ }
405
+ throw onerror(err);
406
+ } finally {
407
+ timeout?.clear();
408
+ forwardAbort?.[kDispose]();
409
+ }
410
+ } else if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
394
411
  netSocket = new net.Socket();
395
412
 
396
413
  const {
@@ -465,20 +482,24 @@ export class StateMachine {
465
482
  resolve();
466
483
  }
467
484
  });
468
- await (options?.timeoutContext?.csotEnabled()
469
- ? Promise.all([
470
- willResolveKmsRequest,
471
- Timeout.expires(options.timeoutContext?.remainingTimeMS)
472
- ])
485
+ const remainingTimeMS = options?.timeoutContext?.csotEnabled()
486
+ ? options.timeoutContext.getRemainingTimeMSOrThrow(
487
+ `KMS request timed out after ${options.timeoutContext.timeoutMS}ms`
488
+ )
489
+ : undefined;
490
+ const timeoutMS = Number.isFinite(remainingTimeMS) ? remainingTimeMS : undefined;
491
+ kmsRequestTimeout = timeoutMS ? Timeout.expires(timeoutMS) : undefined;
492
+ await (kmsRequestTimeout
493
+ ? Promise.race([willResolveKmsRequest, kmsRequestTimeout])
473
494
  : willResolveKmsRequest);
474
495
  } catch (error) {
475
- if (error instanceof TimeoutError)
476
- throw new MongoOperationTimeoutError('KMS request timed out');
496
+ if (TimeoutError.is(error)) throw new MongoOperationTimeoutError('KMS request timed out');
477
497
  throw error;
478
498
  } finally {
479
499
  // There's no need for any more activity on this socket at this point.
480
500
  destroySockets();
481
501
  abortListener?.[kDispose]();
502
+ kmsRequestTimeout?.clear();
482
503
  }
483
504
  }
484
505
 
@@ -69,10 +69,7 @@ export class GSSAPI extends AuthProvider {
69
69
  }
70
70
 
71
71
  async function makeKerberosClient({
72
- options: {
73
- hostAddress,
74
- runtime: { os }
75
- },
72
+ options: { hostAddress, runtime },
76
73
  credentials
77
74
  }: AuthContext): Promise<KerberosClient> {
78
75
  if (!hostAddress || typeof hostAddress.host !== 'string' || !credentials) {
@@ -81,6 +78,8 @@ async function makeKerberosClient({
81
78
  );
82
79
  }
83
80
 
81
+ const { os } = await runtime;
82
+
84
83
  loadKrb();
85
84
  if ('kModuleError' in krb) {
86
85
  throw krb['kModuleError'];
@@ -25,7 +25,6 @@ function getDefaultAuthMechanism(hello: Document | null): AuthMechanism {
25
25
  }
26
26
  }
27
27
 
28
- // Default auth mechanism for 4.0 and higher.
29
28
  return AuthMechanism.MONGODB_SCRAM_SHA256;
30
29
  }
31
30
 
@@ -1,11 +1,7 @@
1
1
  import { type Binary, type BSONSerializeOptions, ByteUtils } from '../../bson';
2
2
  import * as BSON from '../../bson';
3
- import {
4
- MongoCompatibilityError,
5
- MongoMissingCredentialsError,
6
- MongoRuntimeError
7
- } from '../../error';
8
- import { maxWireVersion, ns, randomBytes } from '../../utils';
3
+ import { MongoMissingCredentialsError, MongoRuntimeError } from '../../error';
4
+ import { ns, randomBytes } from '../../utils';
9
5
  import { type AuthContext, AuthProvider } from './auth_provider';
10
6
  import {
11
7
  type AWSCredentialProvider,
@@ -45,12 +41,6 @@ export class MongoDBAWS extends AuthProvider {
45
41
  throw new MongoMissingCredentialsError('AuthContext must provide credentials.');
46
42
  }
47
43
 
48
- if (maxWireVersion(connection) < 9) {
49
- throw new MongoCompatibilityError(
50
- 'MONGODB-AWS authentication requires MongoDB version 4.4 or later'
51
- );
52
- }
53
-
54
44
  authContext.credentials = await makeTempCredentials(
55
45
  authContext.credentials,
56
46
  this.credentialFetcher
@@ -154,24 +154,24 @@ export abstract class CallbackWorkflow implements Workflow {
154
154
  * to every 100ms.
155
155
  */
156
156
  protected withLock(callback: OIDCCallbackFunction): OIDCCallbackFunction {
157
- let lock: Promise<any> = Promise.resolve();
157
+ let queue: Promise<void> = Promise.resolve();
158
158
  return async (params: OIDCCallbackParams): Promise<OIDCResponse> => {
159
- // We do this to ensure that we would never return the result of the
160
- // previous lock, only the current callback's value would get returned.
161
- await lock;
162
- lock = lock
163
-
164
- .catch(() => null)
165
-
166
- .then(async () => {
167
- const difference = Date.now() - this.lastExecutionTime;
168
- if (difference <= THROTTLE_MS) {
169
- await setTimeout(THROTTLE_MS - difference, { signal: params.timeoutContext });
170
- }
171
- this.lastExecutionTime = Date.now();
172
- return await callback(params);
173
- });
174
- return await lock;
159
+ const execution = queue.then(async () => {
160
+ const difference = Date.now() - this.lastExecutionTime;
161
+ if (difference <= THROTTLE_MS) {
162
+ await setTimeout(THROTTLE_MS - difference, { signal: params.timeoutContext });
163
+ }
164
+ this.lastExecutionTime = Date.now();
165
+ return await callback(params);
166
+ });
167
+ // The queue exists only to order callers, so it must carry neither a
168
+ // result nor an error: a caller waiting on it gets its own callback
169
+ // invocation, and a failed callback does not block the callers behind it.
170
+ queue = execution.then(
171
+ () => undefined,
172
+ () => undefined
173
+ );
174
+ return await execution;
175
175
  };
176
176
  }
177
177
  }
@@ -32,7 +32,7 @@ export class CommandStartedEvent {
32
32
  /**
33
33
  * Server generated connection id
34
34
  * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId"
35
- * from the server on 4.2+.
35
+ * from the server.
36
36
  */
37
37
  serverConnectionId: bigint | null;
38
38
  serviceId?: ObjectId;
@@ -88,7 +88,7 @@ export class CommandSucceededEvent {
88
88
  connectionId?: string | number;
89
89
  /**
90
90
  * Server generated connection id
91
- * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server on 4.2+.
91
+ * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server.
92
92
  */
93
93
  serverConnectionId: bigint | null;
94
94
  requestId: number;
@@ -148,7 +148,7 @@ export class CommandFailedEvent {
148
148
  connectionId?: string | number;
149
149
  /**
150
150
  * Server generated connection id
151
- * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server on 4.2+.
151
+ * Distinct from the connection id and is returned by the hello or legacy hello response as "connectionId" from the server.
152
152
  */
153
153
  serverConnectionId: bigint | null;
154
154
  requestId: number;
@@ -249,13 +249,12 @@ const LEGACY_FIND_OPTIONS_MAP = {
249
249
  function extractCommand(command: WriteProtocolMessageType): Document {
250
250
  if (command instanceof OpMsgRequest) {
251
251
  const cmd = { ...command.command };
252
- // For OP_MSG with payload type 1 we need to pull the documents
253
- // array out of the document sequence for monitoring.
254
- if (cmd.ops instanceof DocumentSequence) {
255
- cmd.ops = cmd.ops.documents;
256
- }
257
- if (cmd.nsInfo instanceof DocumentSequence) {
258
- cmd.nsInfo = cmd.nsInfo.documents;
252
+ // For OP_MSG payload type 1, replace any document-sequence field with its
253
+ // documents array for monitoring (ops, nsInfo, documents, updates, deletes).
254
+ for (const key of Object.keys(cmd)) {
255
+ if (cmd[key] instanceof DocumentSequence) {
256
+ cmd[key] = cmd[key].documents;
257
+ }
259
258
  }
260
259
  return cmd;
261
260
  }
@@ -500,6 +500,23 @@ export class DocumentSequence {
500
500
  }
501
501
  }
502
502
 
503
+ /**
504
+ * Build a DocumentSequence from documents that have already been serialized,
505
+ * reusing the provided buffers instead of re-serializing.
506
+ * @internal
507
+ */
508
+ export function makeDocumentSequence(
509
+ field: string,
510
+ documents: Document[],
511
+ serialized: Uint8Array[]
512
+ ): DocumentSequence {
513
+ const sequence = new DocumentSequence(field);
514
+ for (let i = 0; i < documents.length; i++) {
515
+ sequence.push(documents[i], serialized[i]);
516
+ }
517
+ return sequence;
518
+ }
519
+
503
520
  /** @internal */
504
521
  export class OpMsgRequest {
505
522
  requestId: number;
@@ -174,7 +174,7 @@ export async function performInitialHandshake(
174
174
 
175
175
  if (error instanceof MongoError) {
176
176
  error.addErrorLabel(MongoErrorLabel.HandshakeError);
177
- if (needsRetryableWriteLabel(error, response.maxWireVersion, conn.description.type)) {
177
+ if (needsRetryableWriteLabel(error)) {
178
178
  error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
179
179
  }
180
180
  }
@@ -224,7 +224,7 @@ export interface HandshakeDocument extends Document {
224
224
  compression: string[];
225
225
  saslSupportedMechs?: string;
226
226
  loadBalanced?: boolean;
227
- backpressure: true;
227
+ backpressure: '2';
228
228
  }
229
229
 
230
230
  /**
@@ -242,7 +242,7 @@ export async function prepareHandshakeDocument(
242
242
 
243
243
  const handshakeDoc: HandshakeDocument = {
244
244
  [serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
245
- backpressure: true,
245
+ backpressure: '2',
246
246
  helloOk: true,
247
247
  client: clientMetadata,
248
248
  compression: compressors
@@ -146,7 +146,7 @@ export interface ConnectionOptions
146
146
  /** @internal */
147
147
  mongoLogger?: MongoLogger | undefined;
148
148
  /** @internal */
149
- runtime: Runtime;
149
+ runtime: Promise<Runtime>;
150
150
  }
151
151
 
152
152
  /** @public */
@@ -286,7 +286,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
286
286
  private get supportsOpMsg(): boolean {
287
287
  return (
288
288
  this.description != null &&
289
- // TODO(NODE-6672,NODE-6287): This guard is primarily for maxWireVersion = 0
289
+ // OP_MSG was introduced in server 3.6 (wire version 6). This also guards
290
+ // against the pre-handshake case where maxWireVersion() returns 0.
291
+ // TODO(NODE-6672,NODE-6287): remove this helper once OP_QUERY support is dropped
290
292
  maxWireVersion(this) >= 6 &&
291
293
  !this.description.__nodejs_mock_server__
292
294
  );
@@ -107,8 +107,9 @@ type MakeClientMetadataOptions = Pick<MongoOptions, 'appName' | 'runtime'>;
107
107
  */
108
108
  export async function makeClientMetadata(
109
109
  driverInfoList: DriverInfo[],
110
- { appName = '', runtime: { os } }: MakeClientMetadataOptions
110
+ { appName = '', runtime }: MakeClientMetadataOptions
111
111
  ): Promise<ClientMetadata> {
112
+ const { os } = await runtime;
112
113
  const metadataDocument = new LimitedSizeDocument(512);
113
114
 
114
115
  // Add app name first, it must be sent
@@ -1,8 +1,8 @@
1
- export const MIN_SUPPORTED_SERVER_VERSION = '4.2';
1
+ export const MIN_SUPPORTED_SERVER_VERSION = '4.4';
2
2
  export const MAX_SUPPORTED_SERVER_VERSION = '9.0';
3
3
  export const MIN_SUPPORTED_SNAPSHOT_READS_WIRE_VERSION = 13;
4
4
  export const MIN_SUPPORTED_SNAPSHOT_READS_SERVER_VERSION = '5.0';
5
- export const MIN_SUPPORTED_WIRE_VERSION = 8;
5
+ export const MIN_SUPPORTED_WIRE_VERSION = 9;
6
6
  export const MAX_SUPPORTED_WIRE_VERSION = 29;
7
7
  export const MIN_SUPPORTED_QE_WIRE_VERSION = 21;
8
8
  export const MIN_SUPPORTED_QE_SERVER_VERSION = '7.0';
@@ -544,6 +544,10 @@ export function parseOptions(
544
544
  );
545
545
 
546
546
  mongoOptions.runtime = resolveRuntimeAdapters(options);
547
+ // Mark the promise as handled so a rejection before any consumer awaits it (possible only in
548
+ // runtimes where the dynamic import of the default os adapter fails) cannot crash the process
549
+ // as an unhandled rejection; consumers awaiting `runtime` still observe the rejection.
550
+ mongoOptions.runtime.then(undefined, squashError);
547
551
 
548
552
  return mongoOptions;
549
553
  }
@@ -126,11 +126,6 @@ export interface AbstractCursorOptions extends BSONSerializeOptions {
126
126
  maxAwaitTimeMS?: number;
127
127
  /**
128
128
  * Comment to apply to the operation.
129
- *
130
- * In server versions pre-4.4, 'comment' must be string. A server
131
- * error will be thrown if any other type is provided.
132
- *
133
- * In server versions 4.4 and above, 'comment' can be any valid BSON type.
134
129
  */
135
130
  comment?: unknown;
136
131
  /**