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.
- package/README.md +25 -22
- package/lib/bson.js +3 -1
- package/lib/bson.js.map +1 -1
- package/lib/change_stream.js +3 -2
- package/lib/change_stream.js.map +1 -1
- package/lib/cmap/auth/auth_provider.js +21 -10
- package/lib/cmap/auth/auth_provider.js.map +1 -1
- package/lib/cmap/auth/gssapi.js +71 -116
- package/lib/cmap/auth/gssapi.js.map +1 -1
- package/lib/cmap/auth/mongo_credentials.js +17 -0
- package/lib/cmap/auth/mongo_credentials.js.map +1 -1
- package/lib/cmap/auth/mongocr.js +20 -29
- package/lib/cmap/auth/mongocr.js.map +1 -1
- package/lib/cmap/auth/mongodb_aws.js +126 -140
- package/lib/cmap/auth/mongodb_aws.js.map +1 -1
- package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js +28 -0
- package/lib/cmap/auth/mongodb_oidc/aws_service_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/callback_workflow.js +178 -0
- package/lib/cmap/auth/mongodb_oidc/callback_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/service_workflow.js +41 -0
- package/lib/cmap/auth/mongodb_oidc/service_workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js +115 -0
- package/lib/cmap/auth/mongodb_oidc/token_entry_cache.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc/workflow.js +3 -0
- package/lib/cmap/auth/mongodb_oidc/workflow.js.map +1 -0
- package/lib/cmap/auth/mongodb_oidc.js +62 -0
- package/lib/cmap/auth/mongodb_oidc.js.map +1 -0
- package/lib/cmap/auth/plain.js +4 -5
- package/lib/cmap/auth/plain.js.map +1 -1
- package/lib/cmap/auth/providers.js +4 -1
- package/lib/cmap/auth/providers.js.map +1 -1
- package/lib/cmap/auth/scram.js +45 -73
- package/lib/cmap/auth/scram.js.map +1 -1
- package/lib/cmap/auth/x509.js +8 -11
- package/lib/cmap/auth/x509.js.map +1 -1
- package/lib/cmap/command_monitoring_events.js +8 -5
- package/lib/cmap/command_monitoring_events.js.map +1 -1
- package/lib/cmap/commands.js +9 -1
- package/lib/cmap/commands.js.map +1 -1
- package/lib/cmap/connect.js +72 -86
- package/lib/cmap/connect.js.map +1 -1
- package/lib/cmap/connection.js +68 -74
- package/lib/cmap/connection.js.map +1 -1
- package/lib/cmap/connection_pool.js +51 -13
- package/lib/cmap/connection_pool.js.map +1 -1
- package/lib/cmap/message_stream.js.map +1 -1
- package/lib/cmap/wire_protocol/shared.js +1 -16
- package/lib/cmap/wire_protocol/shared.js.map +1 -1
- package/lib/collection.js +10 -10
- package/lib/connection_string.js +47 -33
- package/lib/connection_string.js.map +1 -1
- package/lib/cursor/abstract_cursor.js +13 -7
- package/lib/cursor/abstract_cursor.js.map +1 -1
- package/lib/cursor/find_cursor.js +1 -1
- package/lib/db.js +3 -2
- package/lib/db.js.map +1 -1
- package/lib/error.js +2 -1
- package/lib/error.js.map +1 -1
- package/lib/mongo_client.js +22 -2
- package/lib/mongo_client.js.map +1 -1
- package/lib/mongo_logger.js +17 -1
- package/lib/mongo_logger.js.map +1 -1
- package/lib/operations/aggregate.js +4 -1
- package/lib/operations/aggregate.js.map +1 -1
- package/lib/operations/create_collection.js +1 -0
- package/lib/operations/create_collection.js.map +1 -1
- package/lib/operations/execute_operation.js +8 -27
- package/lib/operations/execute_operation.js.map +1 -1
- package/lib/operations/find.js +3 -2
- package/lib/operations/find.js.map +1 -1
- package/lib/operations/indexes.js +2 -1
- package/lib/operations/indexes.js.map +1 -1
- package/lib/operations/list_collections.js +2 -1
- package/lib/operations/list_collections.js.map +1 -1
- package/lib/read_concern.js +1 -1
- package/lib/read_preference.js +2 -2
- package/lib/sdam/monitor.js +1 -0
- package/lib/sdam/monitor.js.map +1 -1
- package/lib/sdam/server.js +4 -2
- package/lib/sdam/server.js.map +1 -1
- package/lib/sdam/topology.js +3 -26
- package/lib/sdam/topology.js.map +1 -1
- package/lib/sessions.js +2 -1
- package/lib/sessions.js.map +1 -1
- package/lib/utils.js +15 -70
- package/lib/utils.js.map +1 -1
- package/lib/write_concern.js +1 -1
- package/mongodb.d.ts +137 -68
- package/package.json +30 -30
- package/src/bson.ts +3 -1
- package/src/bulk/common.ts +1 -1
- package/src/change_stream.ts +16 -8
- package/src/cmap/auth/auth_provider.ts +29 -16
- package/src/cmap/auth/gssapi.ts +102 -149
- package/src/cmap/auth/mongo_credentials.ts +47 -1
- package/src/cmap/auth/mongocr.ts +31 -36
- package/src/cmap/auth/mongodb_aws.ts +167 -189
- package/src/cmap/auth/mongodb_oidc/aws_service_workflow.ts +26 -0
- package/src/cmap/auth/mongodb_oidc/callback_workflow.ts +259 -0
- package/src/cmap/auth/mongodb_oidc/service_workflow.ts +47 -0
- package/src/cmap/auth/mongodb_oidc/token_entry_cache.ts +166 -0
- package/src/cmap/auth/mongodb_oidc/workflow.ts +21 -0
- package/src/cmap/auth/mongodb_oidc.ts +123 -0
- package/src/cmap/auth/plain.ts +6 -6
- package/src/cmap/auth/providers.ts +5 -2
- package/src/cmap/auth/scram.ts +56 -90
- package/src/cmap/auth/x509.ts +12 -18
- package/src/cmap/command_monitoring_events.ts +5 -2
- package/src/cmap/commands.ts +11 -1
- package/src/cmap/connect.ts +90 -114
- package/src/cmap/connection.ts +92 -90
- package/src/cmap/connection_pool.ts +77 -16
- package/src/cmap/message_stream.ts +0 -2
- package/src/cmap/wire_protocol/compression.ts +1 -1
- package/src/cmap/wire_protocol/shared.ts +1 -23
- package/src/collection.ts +11 -11
- package/src/connection_string.ts +52 -35
- package/src/cursor/abstract_cursor.ts +13 -6
- package/src/cursor/change_stream_cursor.ts +5 -5
- package/src/cursor/find_cursor.ts +1 -1
- package/src/db.ts +3 -2
- package/src/deps.ts +56 -38
- package/src/error.ts +3 -2
- package/src/index.ts +7 -0
- package/src/mongo_client.ts +35 -10
- package/src/mongo_logger.ts +20 -2
- package/src/mongo_types.ts +4 -3
- package/src/operations/aggregate.ts +4 -2
- package/src/operations/create_collection.ts +2 -1
- package/src/operations/execute_operation.ts +8 -25
- package/src/operations/find.ts +13 -4
- package/src/operations/find_and_modify.ts +4 -4
- package/src/operations/indexes.ts +12 -4
- package/src/operations/list_collections.ts +11 -3
- package/src/operations/set_profiling_level.ts +1 -1
- package/src/operations/stats.ts +1 -1
- package/src/read_concern.ts +2 -2
- package/src/read_preference.ts +3 -3
- package/src/sdam/common.ts +2 -2
- package/src/sdam/monitor.ts +1 -0
- package/src/sdam/server.ts +4 -1
- package/src/sdam/topology.ts +4 -33
- package/src/sessions.ts +2 -1
- package/src/transactions.ts +1 -1
- package/src/utils.ts +24 -98
- package/src/write_concern.ts +1 -1
package/src/cmap/connect.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
if (err || !handshakeDoc) {
|
|
120
|
-
return callback(err);
|
|
121
|
-
}
|
|
119
|
+
conn.authContext = authContext;
|
|
122
120
|
|
|
123
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
130
|
+
const start = new Date().getTime();
|
|
131
|
+
const response = await conn.commandAsync(ns('admin.$cmd'), handshakeDoc, handshakeOptions);
|
|
140
132
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
133
|
+
if (!('isWritablePrimary' in response)) {
|
|
134
|
+
// Provide hello-style response document.
|
|
135
|
+
response.isWritablePrimary = response[LEGACY_HELLO_COMMAND];
|
|
136
|
+
}
|
|
145
137
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
138
|
+
if (response.helloOk) {
|
|
139
|
+
conn.helloOk = true;
|
|
140
|
+
}
|
|
149
141
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}
|
|
142
|
+
const supportedServerErr = checkSupportedServer(response, options);
|
|
143
|
+
if (supportedServerErr) {
|
|
144
|
+
throw supportedServerErr;
|
|
145
|
+
}
|
|
155
146
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
253
|
-
|
|
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
|
|
236
|
+
return provider.prepare(handshakeDoc, authContext);
|
|
259
237
|
}
|
|
260
238
|
const provider = AUTH_PROVIDERS.get(credentials.mechanism);
|
|
261
239
|
if (!provider) {
|
|
262
|
-
|
|
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
|
|
242
|
+
return provider.prepare(handshakeDoc, authContext);
|
|
267
243
|
}
|
|
268
|
-
|
|
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(
|
|
407
|
+
socket.setTimeout(0);
|
|
432
408
|
callback(undefined, socket);
|
|
433
409
|
}
|
|
434
410
|
|
package/src/cmap/connection.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
}
|
|
405
|
+
if (document.writeConcernError) {
|
|
406
|
+
callback(new MongoWriteConcernError(document.writeConcernError, document), document);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
429
409
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
|
447
|
-
if (
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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].
|
|
476
|
-
this.
|
|
477
|
-
|
|
478
|
-
|
|
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
|
|
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
|
|
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(
|