mongodb 5.0.1 → 5.2.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 (146) hide show
  1. package/README.md +25 -22
  2. package/lib/bson.js +3 -1
  3. package/lib/bson.js.map +1 -1
  4. package/lib/change_stream.js +3 -2
  5. package/lib/change_stream.js.map +1 -1
  6. package/lib/cmap/auth/auth_provider.js +21 -10
  7. package/lib/cmap/auth/auth_provider.js.map +1 -1
  8. package/lib/cmap/auth/gssapi.js +71 -116
  9. package/lib/cmap/auth/gssapi.js.map +1 -1
  10. package/lib/cmap/auth/mongo_credentials.js +17 -0
  11. package/lib/cmap/auth/mongo_credentials.js.map +1 -1
  12. package/lib/cmap/auth/mongocr.js +20 -29
  13. package/lib/cmap/auth/mongocr.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_aws.js +126 -140
  15. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  16. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +28 -0
  17. package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -0
  18. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +178 -0
  19. package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -0
  20. package/lib/cmap/auth/mongodb_oidc/service_workflow.js +41 -0
  21. package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -0
  22. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +115 -0
  23. package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +1 -0
  24. package/lib/cmap/auth/mongodb_oidc/workflow.js +3 -0
  25. package/lib/cmap/auth/mongodb_oidc/workflow.js.map +1 -0
  26. package/lib/cmap/auth/mongodb_oidc.js +62 -0
  27. package/lib/cmap/auth/mongodb_oidc.js.map +1 -0
  28. package/lib/cmap/auth/plain.js +4 -5
  29. package/lib/cmap/auth/plain.js.map +1 -1
  30. package/lib/cmap/auth/providers.js +4 -1
  31. package/lib/cmap/auth/providers.js.map +1 -1
  32. package/lib/cmap/auth/scram.js +45 -73
  33. package/lib/cmap/auth/scram.js.map +1 -1
  34. package/lib/cmap/auth/x509.js +8 -11
  35. package/lib/cmap/auth/x509.js.map +1 -1
  36. package/lib/cmap/command_monitoring_events.js +8 -5
  37. package/lib/cmap/command_monitoring_events.js.map +1 -1
  38. package/lib/cmap/commands.js +9 -1
  39. package/lib/cmap/commands.js.map +1 -1
  40. package/lib/cmap/connect.js +72 -86
  41. package/lib/cmap/connect.js.map +1 -1
  42. package/lib/cmap/connection.js +68 -74
  43. package/lib/cmap/connection.js.map +1 -1
  44. package/lib/cmap/connection_pool.js +51 -13
  45. package/lib/cmap/connection_pool.js.map +1 -1
  46. package/lib/cmap/message_stream.js.map +1 -1
  47. package/lib/cmap/wire_protocol/shared.js +1 -16
  48. package/lib/cmap/wire_protocol/shared.js.map +1 -1
  49. package/lib/collection.js +10 -10
  50. package/lib/connection_string.js +47 -33
  51. package/lib/connection_string.js.map +1 -1
  52. package/lib/cursor/abstract_cursor.js +13 -7
  53. package/lib/cursor/abstract_cursor.js.map +1 -1
  54. package/lib/cursor/find_cursor.js +1 -1
  55. package/lib/db.js +3 -2
  56. package/lib/db.js.map +1 -1
  57. package/lib/error.js +2 -1
  58. package/lib/error.js.map +1 -1
  59. package/lib/mongo_client.js +22 -2
  60. package/lib/mongo_client.js.map +1 -1
  61. package/lib/mongo_logger.js +17 -1
  62. package/lib/mongo_logger.js.map +1 -1
  63. package/lib/operations/aggregate.js +4 -1
  64. package/lib/operations/aggregate.js.map +1 -1
  65. package/lib/operations/create_collection.js +1 -0
  66. package/lib/operations/create_collection.js.map +1 -1
  67. package/lib/operations/execute_operation.js +8 -27
  68. package/lib/operations/execute_operation.js.map +1 -1
  69. package/lib/operations/find.js +3 -2
  70. package/lib/operations/find.js.map +1 -1
  71. package/lib/operations/indexes.js +2 -1
  72. package/lib/operations/indexes.js.map +1 -1
  73. package/lib/operations/list_collections.js +2 -1
  74. package/lib/operations/list_collections.js.map +1 -1
  75. package/lib/read_concern.js +1 -1
  76. package/lib/read_preference.js +2 -2
  77. package/lib/sdam/monitor.js +1 -0
  78. package/lib/sdam/monitor.js.map +1 -1
  79. package/lib/sdam/server.js +4 -2
  80. package/lib/sdam/server.js.map +1 -1
  81. package/lib/sdam/topology.js +3 -26
  82. package/lib/sdam/topology.js.map +1 -1
  83. package/lib/sessions.js +2 -1
  84. package/lib/sessions.js.map +1 -1
  85. package/lib/utils.js +15 -70
  86. package/lib/utils.js.map +1 -1
  87. package/lib/write_concern.js +1 -1
  88. package/mongodb.d.ts +137 -68
  89. package/package.json +30 -30
  90. package/src/bson.ts +3 -1
  91. package/src/bulk/common.ts +1 -1
  92. package/src/change_stream.ts +16 -8
  93. package/src/cmap/auth/auth_provider.ts +29 -16
  94. package/src/cmap/auth/gssapi.ts +102 -149
  95. package/src/cmap/auth/mongo_credentials.ts +47 -1
  96. package/src/cmap/auth/mongocr.ts +31 -36
  97. package/src/cmap/auth/mongodb_aws.ts +167 -189
  98. package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +26 -0
  99. package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +259 -0
  100. package/src/cmap/auth/mongodb_oidc/service_workflow.ts +47 -0
  101. package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +166 -0
  102. package/src/cmap/auth/mongodb_oidc/workflow.ts +21 -0
  103. package/src/cmap/auth/mongodb_oidc.ts +123 -0
  104. package/src/cmap/auth/plain.ts +6 -6
  105. package/src/cmap/auth/providers.ts +5 -2
  106. package/src/cmap/auth/scram.ts +56 -90
  107. package/src/cmap/auth/x509.ts +12 -18
  108. package/src/cmap/command_monitoring_events.ts +5 -2
  109. package/src/cmap/commands.ts +11 -1
  110. package/src/cmap/connect.ts +90 -114
  111. package/src/cmap/connection.ts +92 -90
  112. package/src/cmap/connection_pool.ts +77 -16
  113. package/src/cmap/message_stream.ts +0 -2
  114. package/src/cmap/wire_protocol/compression.ts +1 -1
  115. package/src/cmap/wire_protocol/shared.ts +1 -23
  116. package/src/collection.ts +11 -11
  117. package/src/connection_string.ts +52 -35
  118. package/src/cursor/abstract_cursor.ts +13 -6
  119. package/src/cursor/change_stream_cursor.ts +5 -5
  120. package/src/cursor/find_cursor.ts +1 -1
  121. package/src/db.ts +3 -2
  122. package/src/deps.ts +56 -38
  123. package/src/error.ts +3 -2
  124. package/src/index.ts +7 -0
  125. package/src/mongo_client.ts +35 -10
  126. package/src/mongo_logger.ts +20 -2
  127. package/src/mongo_types.ts +4 -3
  128. package/src/operations/aggregate.ts +4 -2
  129. package/src/operations/create_collection.ts +2 -1
  130. package/src/operations/execute_operation.ts +8 -25
  131. package/src/operations/find.ts +13 -4
  132. package/src/operations/find_and_modify.ts +4 -4
  133. package/src/operations/indexes.ts +12 -4
  134. package/src/operations/list_collections.ts +11 -3
  135. package/src/operations/set_profiling_level.ts +1 -1
  136. package/src/operations/stats.ts +1 -1
  137. package/src/read_concern.ts +2 -2
  138. package/src/read_preference.ts +3 -3
  139. package/src/sdam/common.ts +2 -2
  140. package/src/sdam/monitor.ts +1 -0
  141. package/src/sdam/server.ts +4 -1
  142. package/src/sdam/topology.ts +4 -33
  143. package/src/sessions.ts +2 -1
  144. package/src/transactions.ts +1 -1
  145. package/src/utils.ts +24 -98
  146. package/src/write_concern.ts +1 -1
@@ -15,19 +15,19 @@ import {
15
15
  MongoNetworkError,
16
16
  MongoNetworkTimeoutError,
17
17
  MongoRuntimeError,
18
- MongoServerError,
19
18
  needsRetryableWriteLabel
20
19
  } from '../error';
21
- import { Callback, ClientMetadata, HostAddress, makeClientMetadata, ns } from '../utils';
20
+ import { Callback, ClientMetadata, HostAddress, ns } from '../utils';
22
21
  import { AuthContext, AuthProvider } from './auth/auth_provider';
23
22
  import { GSSAPI } from './auth/gssapi';
24
23
  import { MongoCR } from './auth/mongocr';
25
24
  import { MongoDBAWS } from './auth/mongodb_aws';
25
+ import { MongoDBOIDC } from './auth/mongodb_oidc';
26
26
  import { Plain } from './auth/plain';
27
27
  import { AuthMechanism } from './auth/providers';
28
28
  import { ScramSHA1, ScramSHA256 } from './auth/scram';
29
29
  import { X509 } from './auth/x509';
30
- import { Connection, ConnectionOptions, CryptoConnection } from './connection';
30
+ import { CommandOptions, Connection, ConnectionOptions, CryptoConnection } from './connection';
31
31
  import {
32
32
  MAX_SUPPORTED_SERVER_VERSION,
33
33
  MAX_SUPPORTED_WIRE_VERSION,
@@ -35,10 +35,12 @@ import {
35
35
  MIN_SUPPORTED_WIRE_VERSION
36
36
  } from './wire_protocol/constants';
37
37
 
38
- const AUTH_PROVIDERS = new Map<AuthMechanism | string, AuthProvider>([
38
+ /** @internal */
39
+ export const AUTH_PROVIDERS = new Map<AuthMechanism | string, AuthProvider>([
39
40
  [AuthMechanism.MONGODB_AWS, new MongoDBAWS()],
40
41
  [AuthMechanism.MONGODB_CR, new MongoCR()],
41
42
  [AuthMechanism.MONGODB_GSSAPI, new GSSAPI()],
43
+ [AuthMechanism.MONGODB_OIDC, new MongoDBOIDC()],
42
44
  [AuthMechanism.MONGODB_PLAIN, new Plain()],
43
45
  [AuthMechanism.MONGODB_SCRAM_SHA1, new ScramSHA1()],
44
46
  [AuthMechanism.MONGODB_SCRAM_SHA256, new ScramSHA256()],
@@ -58,7 +60,16 @@ export function connect(options: ConnectionOptions, callback: Callback<Connectio
58
60
  if (options.autoEncrypter) {
59
61
  ConnectionType = CryptoConnection;
60
62
  }
61
- performInitialHandshake(new ConnectionType(socket, options), options, callback);
63
+
64
+ const connection = new ConnectionType(socket, options);
65
+
66
+ performInitialHandshake(connection, options).then(
67
+ () => callback(undefined, connection),
68
+ error => {
69
+ connection.destroy({ force: false });
70
+ callback(error);
71
+ }
72
+ );
62
73
  });
63
74
  }
64
75
 
@@ -89,119 +100,89 @@ function checkSupportedServer(hello: Document, options: ConnectionOptions) {
89
100
  return new MongoCompatibilityError(message);
90
101
  }
91
102
 
92
- function performInitialHandshake(
103
+ async function performInitialHandshake(
93
104
  conn: Connection,
94
- options: ConnectionOptions,
95
- _callback: Callback
96
- ) {
97
- const callback: Callback<Document> = function (err, ret) {
98
- if (err && conn) {
99
- conn.destroy();
100
- }
101
- _callback(err, ret);
102
- };
103
-
105
+ options: ConnectionOptions
106
+ ): Promise<void> {
104
107
  const credentials = options.credentials;
108
+
105
109
  if (credentials) {
106
110
  if (
107
111
  !(credentials.mechanism === AuthMechanism.MONGODB_DEFAULT) &&
108
112
  !AUTH_PROVIDERS.get(credentials.mechanism)
109
113
  ) {
110
- callback(
111
- new MongoInvalidArgumentError(`AuthMechanism '${credentials.mechanism}' not supported`)
112
- );
113
- return;
114
+ throw new MongoInvalidArgumentError(`AuthMechanism '${credentials.mechanism}' not supported`);
114
115
  }
115
116
  }
116
117
 
117
118
  const authContext = new AuthContext(conn, credentials, options);
118
- prepareHandshakeDocument(authContext, (err, handshakeDoc) => {
119
- if (err || !handshakeDoc) {
120
- return callback(err);
121
- }
119
+ conn.authContext = authContext;
122
120
 
123
- const handshakeOptions: Document = Object.assign({}, options);
124
- if (typeof options.connectTimeoutMS === 'number') {
125
- // The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
126
- handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;
127
- }
121
+ const handshakeDoc = await prepareHandshakeDocument(authContext);
128
122
 
129
- const start = new Date().getTime();
130
- conn.command(ns('admin.$cmd'), handshakeDoc, handshakeOptions, (err, response) => {
131
- if (err) {
132
- callback(err);
133
- return;
134
- }
123
+ // @ts-expect-error: TODO(NODE-5141): The options need to be filtered properly, Connection options differ from Command options
124
+ const handshakeOptions: CommandOptions = { ...options };
125
+ if (typeof options.connectTimeoutMS === 'number') {
126
+ // The handshake technically is a monitoring check, so its socket timeout should be connectTimeoutMS
127
+ handshakeOptions.socketTimeoutMS = options.connectTimeoutMS;
128
+ }
135
129
 
136
- if (response?.ok === 0) {
137
- callback(new MongoServerError(response));
138
- return;
139
- }
130
+ const start = new Date().getTime();
131
+ const response = await conn.commandAsync(ns('admin.$cmd'), handshakeDoc, handshakeOptions);
140
132
 
141
- if (!('isWritablePrimary' in response)) {
142
- // Provide hello-style response document.
143
- response.isWritablePrimary = response[LEGACY_HELLO_COMMAND];
144
- }
133
+ if (!('isWritablePrimary' in response)) {
134
+ // Provide hello-style response document.
135
+ response.isWritablePrimary = response[LEGACY_HELLO_COMMAND];
136
+ }
145
137
 
146
- if (response.helloOk) {
147
- conn.helloOk = true;
148
- }
138
+ if (response.helloOk) {
139
+ conn.helloOk = true;
140
+ }
149
141
 
150
- const supportedServerErr = checkSupportedServer(response, options);
151
- if (supportedServerErr) {
152
- callback(supportedServerErr);
153
- return;
154
- }
142
+ const supportedServerErr = checkSupportedServer(response, options);
143
+ if (supportedServerErr) {
144
+ throw supportedServerErr;
145
+ }
155
146
 
156
- if (options.loadBalanced) {
157
- if (!response.serviceId) {
158
- return callback(
159
- new MongoCompatibilityError(
160
- 'Driver attempted to initialize in load balancing mode, ' +
161
- 'but the server does not support this mode.'
162
- )
163
- );
164
- }
165
- }
147
+ if (options.loadBalanced) {
148
+ if (!response.serviceId) {
149
+ throw new MongoCompatibilityError(
150
+ 'Driver attempted to initialize in load balancing mode, ' +
151
+ 'but the server does not support this mode.'
152
+ );
153
+ }
154
+ }
166
155
 
167
- // NOTE: This is metadata attached to the connection while porting away from
168
- // handshake being done in the `Server` class. Likely, it should be
169
- // relocated, or at very least restructured.
170
- conn.hello = response;
171
- conn.lastHelloMS = new Date().getTime() - start;
172
-
173
- if (!response.arbiterOnly && credentials) {
174
- // store the response on auth context
175
- authContext.response = response;
176
-
177
- const resolvedCredentials = credentials.resolveAuthMechanism(response);
178
- const provider = AUTH_PROVIDERS.get(resolvedCredentials.mechanism);
179
- if (!provider) {
180
- return callback(
181
- new MongoInvalidArgumentError(
182
- `No AuthProvider for ${resolvedCredentials.mechanism} defined.`
183
- )
184
- );
156
+ // NOTE: This is metadata attached to the connection while porting away from
157
+ // handshake being done in the `Server` class. Likely, it should be
158
+ // relocated, or at very least restructured.
159
+ conn.hello = response;
160
+ conn.lastHelloMS = new Date().getTime() - start;
161
+
162
+ if (!response.arbiterOnly && credentials) {
163
+ // store the response on auth context
164
+ authContext.response = response;
165
+
166
+ const resolvedCredentials = credentials.resolveAuthMechanism(response);
167
+ const provider = AUTH_PROVIDERS.get(resolvedCredentials.mechanism);
168
+ if (!provider) {
169
+ throw new MongoInvalidArgumentError(
170
+ `No AuthProvider for ${resolvedCredentials.mechanism} defined.`
171
+ );
172
+ }
173
+
174
+ try {
175
+ await provider.auth(authContext);
176
+ } catch (error) {
177
+ if (error instanceof MongoError) {
178
+ error.addErrorLabel(MongoErrorLabel.HandshakeError);
179
+ if (needsRetryableWriteLabel(error, response.maxWireVersion)) {
180
+ error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
185
181
  }
186
- provider.auth(authContext, err => {
187
- if (err) {
188
- if (err instanceof MongoError) {
189
- err.addErrorLabel(MongoErrorLabel.HandshakeError);
190
- if (needsRetryableWriteLabel(err, response.maxWireVersion)) {
191
- err.addErrorLabel(MongoErrorLabel.RetryableWriteError);
192
- }
193
- }
194
- return callback(err);
195
- }
196
- callback(undefined, conn);
197
- });
198
-
199
- return;
200
182
  }
201
-
202
- callback(undefined, conn);
203
- });
204
- });
183
+ throw error;
184
+ }
185
+ }
205
186
  }
206
187
 
207
188
  export interface HandshakeDocument extends Document {
@@ -222,10 +203,9 @@ export interface HandshakeDocument extends Document {
222
203
  *
223
204
  * This function is only exposed for testing purposes.
224
205
  */
225
- export function prepareHandshakeDocument(
226
- authContext: AuthContext,
227
- callback: Callback<HandshakeDocument>
228
- ) {
206
+ export async function prepareHandshakeDocument(
207
+ authContext: AuthContext
208
+ ): Promise<HandshakeDocument> {
229
209
  const options = authContext.options;
230
210
  const compressors = options.compressors ? options.compressors : [];
231
211
  const { serverApi } = authContext.connection;
@@ -233,7 +213,7 @@ export function prepareHandshakeDocument(
233
213
  const handshakeDoc: HandshakeDocument = {
234
214
  [serverApi?.version ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
235
215
  helloOk: true,
236
- client: options.metadata || makeClientMetadata(options),
216
+ client: options.metadata,
237
217
  compression: compressors
238
218
  };
239
219
 
@@ -249,23 +229,19 @@ export function prepareHandshakeDocument(
249
229
  const provider = AUTH_PROVIDERS.get(AuthMechanism.MONGODB_SCRAM_SHA256);
250
230
  if (!provider) {
251
231
  // This auth mechanism is always present.
252
- return callback(
253
- new MongoInvalidArgumentError(
254
- `No AuthProvider for ${AuthMechanism.MONGODB_SCRAM_SHA256} defined.`
255
- )
232
+ throw new MongoInvalidArgumentError(
233
+ `No AuthProvider for ${AuthMechanism.MONGODB_SCRAM_SHA256} defined.`
256
234
  );
257
235
  }
258
- return provider.prepare(handshakeDoc, authContext, callback);
236
+ return provider.prepare(handshakeDoc, authContext);
259
237
  }
260
238
  const provider = AUTH_PROVIDERS.get(credentials.mechanism);
261
239
  if (!provider) {
262
- return callback(
263
- new MongoInvalidArgumentError(`No AuthProvider for ${credentials.mechanism} defined.`)
264
- );
240
+ throw new MongoInvalidArgumentError(`No AuthProvider for ${credentials.mechanism} defined.`);
265
241
  }
266
- return provider.prepare(handshakeDoc, authContext, callback);
242
+ return provider.prepare(handshakeDoc, authContext);
267
243
  }
268
- callback(undefined, handshakeDoc);
244
+ return handshakeDoc;
269
245
  }
270
246
 
271
247
  /** @public */
@@ -347,7 +323,7 @@ function parseSslOptions(options: MakeConnectionOptions): TLSConnectionOpts {
347
323
  }
348
324
 
349
325
  const SOCKET_ERROR_EVENT_LIST = ['error', 'close', 'timeout', 'parseError'] as const;
350
- type ErrorHandlerEventName = typeof SOCKET_ERROR_EVENT_LIST[number] | 'cancel';
326
+ type ErrorHandlerEventName = (typeof SOCKET_ERROR_EVENT_LIST)[number] | 'cancel';
351
327
  const SOCKET_ERROR_EVENTS = new Set(SOCKET_ERROR_EVENT_LIST);
352
328
 
353
329
  function makeConnection(options: MakeConnectionOptions, _callback: Callback<Stream>) {
@@ -428,7 +404,7 @@ function makeConnection(options: MakeConnectionOptions, _callback: Callback<Stre
428
404
  }
429
405
  }
430
406
 
431
- socket.setTimeout(socketTimeoutMS);
407
+ socket.setTimeout(0);
432
408
  callback(undefined, socket);
433
409
  }
434
410
 
@@ -1,4 +1,5 @@
1
1
  import { clearTimeout, setTimeout } from 'timers';
2
+ import { promisify } from 'util';
2
3
 
3
4
  import type { BSONSerializeOptions, Document, ObjectId } from '../bson';
4
5
  import {
@@ -36,6 +37,7 @@ import {
36
37
  uuidV4
37
38
  } from '../utils';
38
39
  import type { WriteConcern } from '../write_concern';
40
+ import type { AuthContext } from './auth/auth_provider';
39
41
  import type { MongoCredentials } from './auth/mongo_credentials';
40
42
  import {
41
43
  CommandFailedEvent,
@@ -73,7 +75,6 @@ const INVALID_QUEUE_SIZE = 'Connection internal queue contains more than 1 opera
73
75
 
74
76
  /** @internal */
75
77
  export interface CommandOptions extends BSONSerializeOptions {
76
- command?: boolean;
77
78
  secondaryOk?: boolean;
78
79
  /** Specify read preference if command supports it */
79
80
  readPreference?: ReadPreferenceLike;
@@ -126,14 +127,13 @@ export interface ConnectionOptions
126
127
  noDelay?: boolean;
127
128
  socketTimeoutMS?: number;
128
129
  cancellationToken?: CancellationToken;
129
-
130
130
  metadata: ClientMetadata;
131
131
  }
132
132
 
133
133
  /** @internal */
134
134
  export interface DestroyOptions {
135
135
  /** Force the destruction. */
136
- force?: boolean;
136
+ force: boolean;
137
137
  }
138
138
 
139
139
  /** @public */
@@ -154,11 +154,18 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
154
154
  address: string;
155
155
  socketTimeoutMS: number;
156
156
  monitorCommands: boolean;
157
+ /** Indicates that the connection (including underlying TCP socket) has been closed. */
157
158
  closed: boolean;
158
- destroyed: boolean;
159
159
  lastHelloMS?: number;
160
160
  serverApi?: ServerApi;
161
161
  helloOk?: boolean;
162
+ commandAsync: (
163
+ ns: MongoDBNamespace,
164
+ cmd: Document,
165
+ options: CommandOptions | undefined
166
+ ) => Promise<Document>;
167
+ /** @internal */
168
+ authContext?: AuthContext;
162
169
 
163
170
  /**@internal */
164
171
  [kDelayedTimeoutId]: NodeJS.Timeout | null;
@@ -198,13 +205,22 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
198
205
 
199
206
  constructor(stream: Stream, options: ConnectionOptions) {
200
207
  super();
208
+
209
+ this.commandAsync = promisify(
210
+ (
211
+ ns: MongoDBNamespace,
212
+ cmd: Document,
213
+ options: CommandOptions | undefined,
214
+ callback: Callback
215
+ ) => this.command(ns, cmd, options, callback as any)
216
+ );
217
+
201
218
  this.id = options.id;
202
219
  this.address = streamIdentifier(stream, options);
203
220
  this.socketTimeoutMS = options.socketTimeoutMS ?? 0;
204
221
  this.monitorCommands = options.monitorCommands;
205
222
  this.serverApi = options.serverApi;
206
223
  this.closed = false;
207
- this.destroyed = false;
208
224
  this[kHello] = null;
209
225
  this[kClusterTime] = null;
210
226
 
@@ -294,56 +310,19 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
294
310
  }
295
311
 
296
312
  onError(error: Error) {
297
- if (this.closed) {
298
- return;
299
- }
300
-
301
- this[kStream].destroy(error);
302
-
303
- this.closed = true;
304
-
305
- for (const op of this[kQueue].values()) {
306
- op.cb(error);
307
- }
308
-
309
- this[kQueue].clear();
310
- this.emit(Connection.CLOSE);
313
+ this.cleanup(true, error);
311
314
  }
312
315
 
313
316
  onClose() {
314
- if (this.closed) {
315
- return;
316
- }
317
-
318
- this.closed = true;
319
-
320
317
  const message = `connection ${this.id} to ${this.address} closed`;
321
- for (const op of this[kQueue].values()) {
322
- op.cb(new MongoNetworkError(message));
323
- }
324
-
325
- this[kQueue].clear();
326
- this.emit(Connection.CLOSE);
318
+ this.cleanup(true, new MongoNetworkError(message));
327
319
  }
328
320
 
329
321
  onTimeout() {
330
- if (this.closed) {
331
- return;
332
- }
333
-
334
322
  this[kDelayedTimeoutId] = setTimeout(() => {
335
- this[kStream].destroy();
336
-
337
- this.closed = true;
338
-
339
323
  const message = `connection ${this.id} to ${this.address} timed out`;
340
324
  const beforeHandshake = this.hello == null;
341
- for (const op of this[kQueue].values()) {
342
- op.cb(new MongoNetworkTimeoutError(message, { beforeHandshake }));
343
- }
344
-
345
- this[kQueue].clear();
346
- this.emit(Connection.CLOSE);
325
+ this.cleanup(true, new MongoNetworkTimeoutError(message, { beforeHandshake }));
347
326
  }, 1).unref(); // No need for this timer to hold the event loop open
348
327
  }
349
328
 
@@ -354,6 +333,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
354
333
  this[kDelayedTimeoutId] = null;
355
334
  }
356
335
 
336
+ const socketTimeoutMS = this[kStream].timeout ?? 0;
337
+ this[kStream].setTimeout(0);
338
+
357
339
  // always emit the message, in case we are streaming
358
340
  this.emit('message', message);
359
341
  let operationDescription = this[kQueue].get(message.responseTo);
@@ -364,7 +346,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
364
346
 
365
347
  // First check if the map is of invalid size
366
348
  if (this[kQueue].size > 1) {
367
- this.onError(new MongoRuntimeError(INVALID_QUEUE_SIZE));
349
+ this.cleanup(true, new MongoRuntimeError(INVALID_QUEUE_SIZE));
368
350
  } else {
369
351
  // Get the first orphaned operation description.
370
352
  const entry = this[kQueue].entries().next();
@@ -394,8 +376,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
394
376
  // back in the queue with the correct requestId and will resolve not being able
395
377
  // to find the next one via the responseTo of the next streaming hello.
396
378
  this[kQueue].set(message.requestId, operationDescription);
397
- } else if (operationDescription.socketTimeoutOverride) {
398
- this[kStream].setTimeout(this.socketTimeoutMS);
379
+ this[kStream].setTimeout(socketTimeoutMS);
399
380
  }
400
381
 
401
382
  try {
@@ -421,63 +402,82 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
421
402
  this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
422
403
  }
423
404
 
424
- if (operationDescription.command) {
425
- if (document.writeConcernError) {
426
- callback(new MongoWriteConcernError(document.writeConcernError, document), document);
427
- return;
428
- }
405
+ if (document.writeConcernError) {
406
+ callback(new MongoWriteConcernError(document.writeConcernError, document), document);
407
+ return;
408
+ }
429
409
 
430
- if (document.ok === 0 || document.$err || document.errmsg || document.code) {
431
- callback(new MongoServerError(document));
432
- return;
433
- }
434
- } else {
435
- // Pre 3.2 support
436
- if (document.ok === 0 || document.$err || document.errmsg) {
437
- callback(new MongoServerError(document));
438
- return;
439
- }
410
+ if (document.ok === 0 || document.$err || document.errmsg || document.code) {
411
+ callback(new MongoServerError(document));
412
+ return;
440
413
  }
441
414
  }
442
415
 
443
416
  callback(undefined, message.documents[0]);
444
417
  }
445
418
 
446
- destroy(options?: DestroyOptions, callback?: Callback): void {
447
- if (typeof options === 'function') {
448
- callback = options;
449
- options = { force: false };
419
+ destroy(options: DestroyOptions, callback?: Callback): void {
420
+ if (this.closed) {
421
+ process.nextTick(() => callback?.());
422
+ return;
423
+ }
424
+ if (typeof callback === 'function') {
425
+ this.once('close', () => process.nextTick(() => callback()));
450
426
  }
451
427
 
428
+ // load balanced mode requires that these listeners remain on the connection
429
+ // after cleanup on timeouts, errors or close so we remove them before calling
430
+ // cleanup.
452
431
  this.removeAllListeners(Connection.PINNED);
453
432
  this.removeAllListeners(Connection.UNPINNED);
433
+ const message = `connection ${this.id} to ${this.address} closed`;
434
+ this.cleanup(options.force, new MongoNetworkError(message));
435
+ }
454
436
 
455
- options = Object.assign({ force: false }, options);
456
- if (this[kStream] == null || this.destroyed) {
457
- this.destroyed = true;
458
- if (typeof callback === 'function') {
459
- callback();
460
- }
461
-
437
+ /**
438
+ * A method that cleans up the connection. When `force` is true, this method
439
+ * forcibly destroys the socket.
440
+ *
441
+ * If an error is provided, any in-flight operations will be closed with the error.
442
+ *
443
+ * This method does nothing if the connection is already closed.
444
+ */
445
+ private cleanup(force: boolean, error?: Error): void {
446
+ if (this.closed) {
462
447
  return;
463
448
  }
464
449
 
465
- if (options.force) {
466
- this[kStream].destroy();
467
- this.destroyed = true;
468
- if (typeof callback === 'function') {
469
- callback();
450
+ this.closed = true;
451
+
452
+ const completeCleanup = () => {
453
+ for (const op of this[kQueue].values()) {
454
+ op.cb(error);
470
455
  }
471
456
 
457
+ this[kQueue].clear();
458
+
459
+ this.emit(Connection.CLOSE);
460
+ };
461
+
462
+ this[kStream].removeAllListeners();
463
+ this[kMessageStream].removeAllListeners();
464
+
465
+ this[kMessageStream].destroy();
466
+
467
+ if (force) {
468
+ this[kStream].destroy();
469
+ completeCleanup();
472
470
  return;
473
471
  }
474
472
 
475
- this[kStream].end(() => {
476
- this.destroyed = true;
477
- if (typeof callback === 'function') {
478
- callback();
479
- }
480
- });
473
+ if (!this[kStream].writableEnded) {
474
+ this[kStream].end(() => {
475
+ this[kStream].destroy();
476
+ completeCleanup();
477
+ });
478
+ } else {
479
+ completeCleanup();
480
+ }
481
481
  }
482
482
 
483
483
  command(
@@ -513,6 +513,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
513
513
  if (err) {
514
514
  return callback(err);
515
515
  }
516
+ } else if (session?.explicit) {
517
+ return callback(new MongoCompatibilityError('Current topology does not support sessions'));
516
518
  }
517
519
 
518
520
  // if we have a known cluster time, gossip it
@@ -529,7 +531,6 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
529
531
 
530
532
  const commandOptions: Document = Object.assign(
531
533
  {
532
- command: true,
533
534
  numberToSkip: 0,
534
535
  numberToReturn: -1,
535
536
  checkKeys: false,
@@ -629,7 +630,7 @@ export class CryptoConnection extends Connection {
629
630
  /** @internal */
630
631
  export function hasSessionSupport(conn: Connection): boolean {
631
632
  const description = conn.description;
632
- return description.logicalSessionTimeoutMinutes != null || !!description.loadBalanced;
633
+ return description.logicalSessionTimeoutMinutes != null;
633
634
  }
634
635
 
635
636
  function supportsOpMsg(conn: Connection) {
@@ -669,9 +670,9 @@ function write(
669
670
  session: options.session,
670
671
  noResponse: typeof options.noResponse === 'boolean' ? options.noResponse : false,
671
672
  documentsReturnedIn: options.documentsReturnedIn,
672
- command: !!options.command,
673
673
 
674
674
  // for BSON parsing
675
+ useBigInt64: typeof options.useBigInt64 === 'boolean' ? options.useBigInt64 : false,
675
676
  promoteLongs: typeof options.promoteLongs === 'boolean' ? options.promoteLongs : true,
676
677
  promoteValues: typeof options.promoteValues === 'boolean' ? options.promoteValues : true,
677
678
  promoteBuffers: typeof options.promoteBuffers === 'boolean' ? options.promoteBuffers : false,
@@ -691,8 +692,9 @@ function write(
691
692
  }
692
693
 
693
694
  if (typeof options.socketTimeoutMS === 'number') {
694
- operationDescription.socketTimeoutOverride = true;
695
695
  conn[kStream].setTimeout(options.socketTimeoutMS);
696
+ } else if (conn.socketTimeoutMS !== 0) {
697
+ conn[kStream].setTimeout(conn.socketTimeoutMS);
696
698
  }
697
699
 
698
700
  // if command monitoring is enabled we need to modify the callback here
@@ -702,7 +704,7 @@ function write(
702
704
  operationDescription.started = now();
703
705
  operationDescription.cb = (err, reply) => {
704
706
  // Command monitoring spec states that if ok is 1, then we must always emit
705
- // a command suceeded event, even if there's an error. Write concern errors
707
+ // a command succeeded event, even if there's an error. Write concern errors
706
708
  // will have an ok: 1 in their reply.
707
709
  if (err && reply?.ok !== 1) {
708
710
  conn.emit(