Skip to content

Commit 1d4ae58

Browse files
authored
Merge branch 'main' into beta-in-package-poc
2 parents f1da72b + 54efb7d commit 1d4ae58

File tree

652 files changed

+79328
-44948
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

652 files changed

+79328
-44948
lines changed

.evergreen/run-typescript.sh

100644100755
File mode changed.

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,11 @@ Change history can be found in [`HISTORY.md`](https://github.com/mongodb/node-mo
7070

7171
### Compatibility
7272

73-
For server and runtime version compatibility matrices, please refer to the following links:
73+
The driver currently supports 3.6+ servers.
74+
75+
** 3.6 support is deprecated and support will be removed in a future version **
76+
77+
For exhaustive server and runtime version compatibility matrices, please refer to the following links:
7478

7579
- [MongoDB](https://www.mongodb.com/docs/drivers/node/current/compatibility/#mongodb-compatibility)
7680
- [NodeJS](https://www.mongodb.com/docs/drivers/node/current/compatibility/#language-compatibility)

src/bulk/common.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,8 @@ function handleMongoWriteConcernError(
616616
callback(
617617
new MongoBulkWriteError(
618618
{
619-
message: err.result?.writeConcernError.errmsg,
620-
code: err.result?.writeConcernError.result
619+
message: err.result.writeConcernError.errmsg,
620+
code: err.result.writeConcernError.code
621621
},
622622
new BulkWriteResult(bulkResult, isOrdered)
623623
)

src/client-side-encryption/auto_encrypter.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
type MongoCryptConstructor,
44
type MongoCryptOptions
55
} from 'mongodb-client-encryption';
6+
import * as net from 'net';
67

78
import { deserialize, type Document, serialize } from '../bson';
89
import { type CommandOptions, type ProxyOptions } from '../cmap/connection';
@@ -11,6 +12,7 @@ import { getMongoDBClientEncryption } from '../deps';
1112
import { MongoRuntimeError } from '../error';
1213
import { MongoClient, type MongoClientOptions } from '../mongo_client';
1314
import { MongoDBCollectionNamespace } from '../utils';
15+
import { autoSelectSocketOptions } from './client_encryption';
1416
import * as cryptoCallbacks from './crypto_callbacks';
1517
import { MongoCryptInvalidArgumentError } from './errors';
1618
import { MongocryptdManager } from './mongocryptd_manager';
@@ -297,10 +299,20 @@ export class AutoEncrypter {
297299
serverSelectionTimeoutMS: 10000
298300
};
299301

300-
if (options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') {
302+
if (
303+
(options.extraOptions == null || typeof options.extraOptions.mongocryptdURI !== 'string') &&
304+
!net.getDefaultAutoSelectFamily
305+
) {
306+
// Only set family if autoSelectFamily options are not supported.
301307
clientOptions.family = 4;
302308
}
303309

310+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
311+
// @ts-ignore: TS complains as this always returns true on versions where it is present.
312+
if (net.getDefaultAutoSelectFamily) {
313+
Object.assign(clientOptions, autoSelectSocketOptions(this._client.options));
314+
}
315+
304316
this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions);
305317
}
306318
}
@@ -379,7 +391,8 @@ export class AutoEncrypter {
379391
promoteValues: false,
380392
promoteLongs: false,
381393
proxyOptions: this._proxyOptions,
382-
tlsOptions: this._tlsOptions
394+
tlsOptions: this._tlsOptions,
395+
socketOptions: autoSelectSocketOptions(this._client.options)
383396
});
384397

385398
return deserialize(await stateMachine.execute(this, context), {
@@ -399,7 +412,8 @@ export class AutoEncrypter {
399412
const stateMachine = new StateMachine({
400413
...options,
401414
proxyOptions: this._proxyOptions,
402-
tlsOptions: this._tlsOptions
415+
tlsOptions: this._tlsOptions,
416+
socketOptions: autoSelectSocketOptions(this._client.options)
403417
});
404418

405419
return await stateMachine.execute(this, context);

src/client-side-encryption/client_encryption.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { type Collection } from '../collection';
1212
import { type FindCursor } from '../cursor/find_cursor';
1313
import { type Db } from '../db';
1414
import { getMongoDBClientEncryption } from '../deps';
15-
import { type MongoClient } from '../mongo_client';
15+
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
1616
import { type Filter, type WithId } from '../mongo_types';
1717
import { type CreateCollectionOptions } from '../operations/create_collection';
1818
import { type DeleteResult } from '../operations/delete';
@@ -28,7 +28,11 @@ import {
2828
type KMSProviders,
2929
refreshKMSCredentials
3030
} from './providers/index';
31-
import { type CSFLEKMSTlsOptions, StateMachine } from './state_machine';
31+
import {
32+
type ClientEncryptionSocketOptions,
33+
type CSFLEKMSTlsOptions,
34+
StateMachine
35+
} from './state_machine';
3236

3337
/**
3438
* @public
@@ -199,7 +203,8 @@ export class ClientEncryption {
199203

200204
const stateMachine = new StateMachine({
201205
proxyOptions: this._proxyOptions,
202-
tlsOptions: this._tlsOptions
206+
tlsOptions: this._tlsOptions,
207+
socketOptions: autoSelectSocketOptions(this._client.options)
203208
});
204209

205210
const dataKey = deserialize(await stateMachine.execute(this, context)) as DataKey;
@@ -256,7 +261,8 @@ export class ClientEncryption {
256261
const context = this._mongoCrypt.makeRewrapManyDataKeyContext(filterBson, keyEncryptionKeyBson);
257262
const stateMachine = new StateMachine({
258263
proxyOptions: this._proxyOptions,
259-
tlsOptions: this._tlsOptions
264+
tlsOptions: this._tlsOptions,
265+
socketOptions: autoSelectSocketOptions(this._client.options)
260266
});
261267

262268
const { v: dataKeys } = deserialize(await stateMachine.execute(this, context));
@@ -637,7 +643,8 @@ export class ClientEncryption {
637643

638644
const stateMachine = new StateMachine({
639645
proxyOptions: this._proxyOptions,
640-
tlsOptions: this._tlsOptions
646+
tlsOptions: this._tlsOptions,
647+
socketOptions: autoSelectSocketOptions(this._client.options)
641648
});
642649

643650
const { v } = deserialize(await stateMachine.execute(this, context));
@@ -715,7 +722,8 @@ export class ClientEncryption {
715722
const valueBuffer = serialize({ v: value });
716723
const stateMachine = new StateMachine({
717724
proxyOptions: this._proxyOptions,
718-
tlsOptions: this._tlsOptions
725+
tlsOptions: this._tlsOptions,
726+
socketOptions: autoSelectSocketOptions(this._client.options)
719727
});
720728
const context = this._mongoCrypt.makeExplicitEncryptionContext(valueBuffer, contextOptions);
721729

@@ -957,3 +965,21 @@ export interface RangeOptions {
957965
sparsity: Long;
958966
precision?: number;
959967
}
968+
969+
/**
970+
* Get the socket options from the client.
971+
* @param baseOptions - The mongo client options.
972+
* @returns ClientEncryptionSocketOptions
973+
*/
974+
export function autoSelectSocketOptions(
975+
baseOptions: MongoClientOptions
976+
): ClientEncryptionSocketOptions {
977+
const options: ClientEncryptionSocketOptions = { autoSelectFamily: true };
978+
if ('autoSelectFamily' in baseOptions) {
979+
options.autoSelectFamily = baseOptions.autoSelectFamily;
980+
}
981+
if ('autoSelectFamilyAttemptTimeout' in baseOptions) {
982+
options.autoSelectFamilyAttemptTimeout = baseOptions.autoSelectFamilyAttemptTimeout;
983+
}
984+
return options;
985+
}

src/client-side-encryption/state_machine.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { type ProxyOptions } from '../cmap/connection';
1414
import { getSocks, type SocksLib } from '../deps';
1515
import { type MongoClient, type MongoClientOptions } from '../mongo_client';
1616
import { BufferPool, MongoDBCollectionNamespace, promiseWithResolvers } from '../utils';
17-
import { type DataKey } from './client_encryption';
17+
import { autoSelectSocketOptions, type DataKey } from './client_encryption';
1818
import { MongoCryptError } from './errors';
1919
import { type MongocryptdManager } from './mongocryptd_manager';
2020
import { type KMSProviders } from './providers';
@@ -114,6 +114,16 @@ export type CSFLEKMSTlsOptions = {
114114
[key: string]: ClientEncryptionTlsOptions | undefined;
115115
};
116116

117+
/**
118+
* @public
119+
*
120+
* Socket options to use for KMS requests.
121+
*/
122+
export type ClientEncryptionSocketOptions = Pick<
123+
MongoClientOptions,
124+
'autoSelectFamily' | 'autoSelectFamilyAttemptTimeout'
125+
>;
126+
117127
/**
118128
* This is kind of a hack. For `rewrapManyDataKey`, we have tests that
119129
* guarantee that when there are no matching keys, `rewrapManyDataKey` returns
@@ -153,6 +163,9 @@ export type StateMachineOptions = {
153163

154164
/** TLS options for KMS requests, if set. */
155165
tlsOptions: CSFLEKMSTlsOptions;
166+
167+
/** Socket specific options we support. */
168+
socketOptions: ClientEncryptionSocketOptions;
156169
} & Pick<BSONSerializeOptions, 'promoteLongs' | 'promoteValues'>;
157170

158171
/**
@@ -289,10 +302,17 @@ export class StateMachine {
289302
async kmsRequest(request: MongoCryptKMSRequest): Promise<void> {
290303
const parsedUrl = request.endpoint.split(':');
291304
const port = parsedUrl[1] != null ? Number.parseInt(parsedUrl[1], 10) : HTTPS_PORT;
292-
const options: tls.ConnectionOptions & { host: string; port: number } = {
305+
const socketOptions = autoSelectSocketOptions(this.options.socketOptions || {});
306+
const options: tls.ConnectionOptions & {
307+
host: string;
308+
port: number;
309+
autoSelectFamily?: boolean;
310+
autoSelectFamilyAttemptTimeout?: number;
311+
} = {
293312
host: parsedUrl[0],
294313
servername: parsedUrl[0],
295-
port
314+
port,
315+
...socketOptions
296316
};
297317
const message = request.message;
298318
const buffer = new BufferPool();
@@ -351,10 +371,12 @@ export class StateMachine {
351371

352372
try {
353373
if (this.options.proxyOptions && this.options.proxyOptions.proxyHost) {
354-
netSocket.connect({
374+
const netSocketOptions = {
355375
host: this.options.proxyOptions.proxyHost,
356-
port: this.options.proxyOptions.proxyPort || 1080
357-
});
376+
port: this.options.proxyOptions.proxyPort || 1080,
377+
...socketOptions
378+
};
379+
netSocket.connect(netSocketOptions);
358380
await willConnect;
359381

360382
try {

src/cmap/connect.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ export async function performInitialHandshake(
164164
} catch (error) {
165165
if (error instanceof MongoError) {
166166
error.addErrorLabel(MongoErrorLabel.HandshakeError);
167-
if (needsRetryableWriteLabel(error, response.maxWireVersion)) {
167+
if (needsRetryableWriteLabel(error, response.maxWireVersion, conn.description.type)) {
168168
error.addErrorLabel(MongoErrorLabel.RetryableWriteError);
169169
}
170170
}
@@ -269,6 +269,8 @@ export const LEGAL_TLS_SOCKET_OPTIONS = [
269269

270270
/** @public */
271271
export const LEGAL_TCP_SOCKET_OPTIONS = [
272+
'autoSelectFamily',
273+
'autoSelectFamilyAttemptTimeout',
272274
'family',
273275
'hints',
274276
'localAddress',

src/connection_string.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,13 @@ export const OPTIONS = {
740740
autoEncryption: {
741741
type: 'record'
742742
},
743+
autoSelectFamily: {
744+
type: 'boolean',
745+
default: true
746+
},
747+
autoSelectFamilyAttemptTimeout: {
748+
type: 'uint'
749+
},
743750
bsonRegExp: {
744751
type: 'boolean'
745752
},

src/error.ts

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { Document } from './bson';
2+
import type { ServerType } from './sdam/common';
23
import type { TopologyVersion } from './sdam/server_description';
34
import type { TopologyDescription } from './sdam/topology_description';
45

@@ -1158,6 +1159,23 @@ export class MongoServerSelectionError extends MongoSystemError {
11581159
}
11591160
}
11601161

1162+
/**
1163+
* The type of the result property of MongoWriteConcernError
1164+
* @public
1165+
*/
1166+
export interface WriteConcernErrorResult {
1167+
writeConcernError: {
1168+
code: number;
1169+
errmsg: string;
1170+
codeName?: string;
1171+
errInfo?: Document;
1172+
};
1173+
ok: number;
1174+
code?: number;
1175+
errorLabels?: string[];
1176+
[x: string | number]: unknown;
1177+
}
1178+
11611179
/**
11621180
* An error thrown when the server reports a writeConcernError
11631181
* @public
@@ -1178,16 +1196,8 @@ export class MongoWriteConcernError extends MongoServerError {
11781196
*
11791197
* @public
11801198
**/
1181-
constructor(result: {
1182-
writeConcernError: {
1183-
code: number;
1184-
errmsg: string;
1185-
codeName?: string;
1186-
errInfo?: Document;
1187-
};
1188-
errorLabels?: string[];
1189-
}) {
1190-
super({ ...result, ...result.writeConcernError });
1199+
constructor(result: WriteConcernErrorResult) {
1200+
super({ ...result.writeConcernError, ...result });
11911201
this.errInfo = result.writeConcernError.errInfo;
11921202
this.result = result;
11931203
}
@@ -1217,7 +1227,11 @@ const RETRYABLE_READ_ERROR_CODES = new Set<number>([
12171227
// see: https://github.com/mongodb/specifications/blob/master/source/retryable-writes/retryable-writes.rst#terms
12181228
const RETRYABLE_WRITE_ERROR_CODES = RETRYABLE_READ_ERROR_CODES;
12191229

1220-
export function needsRetryableWriteLabel(error: Error, maxWireVersion: number): boolean {
1230+
export function needsRetryableWriteLabel(
1231+
error: Error,
1232+
maxWireVersion: number,
1233+
serverType: ServerType
1234+
): boolean {
12211235
// pre-4.4 server, then the driver adds an error label for every valid case
12221236
// execute operation will only inspect the label, code/message logic is handled here
12231237
if (error instanceof MongoNetworkError) {
@@ -1237,11 +1251,17 @@ export function needsRetryableWriteLabel(error: Error, maxWireVersion: number):
12371251
}
12381252

12391253
if (error instanceof MongoWriteConcernError) {
1240-
return RETRYABLE_WRITE_ERROR_CODES.has(error.result?.code ?? error.code ?? 0);
1254+
if (serverType === 'Mongos' && maxWireVersion < 9) {
1255+
// use original top-level code from server response
1256+
return RETRYABLE_WRITE_ERROR_CODES.has(error.result.code ?? 0);
1257+
}
1258+
return RETRYABLE_WRITE_ERROR_CODES.has(
1259+
error.result.writeConcernError.code ?? Number(error.code) ?? 0
1260+
);
12411261
}
12421262

1243-
if (error instanceof MongoError && typeof error.code === 'number') {
1244-
return RETRYABLE_WRITE_ERROR_CODES.has(error.code);
1263+
if (error instanceof MongoError) {
1264+
return RETRYABLE_WRITE_ERROR_CODES.has(Number(error.code));
12451265
}
12461266

12471267
const isNotWritablePrimaryError = LEGACY_NOT_WRITABLE_PRIMARY_ERROR_MESSAGE.test(error.message);

src/gridfs/download.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,41 @@ export interface GridFSFile {
5656

5757
/** @internal */
5858
export interface GridFSBucketReadStreamPrivate {
59+
/**
60+
* The running total number of bytes read from the chunks collection.
61+
*/
5962
bytesRead: number;
63+
/**
64+
* The number of bytes to remove from the last chunk read in the file. This is non-zero
65+
* if `end` is not equal to the length of the document and `end` is not a multiple
66+
* of the chunkSize.
67+
*/
6068
bytesToTrim: number;
69+
70+
/**
71+
* The number of bytes to remove from the first chunk read in the file. This is non-zero
72+
* if `start` is not equal to the 0 and `start` is not a multiple
73+
* of the chunkSize.
74+
*/
6175
bytesToSkip: number;
76+
77+
files: Collection<GridFSFile>;
6278
chunks: Collection<GridFSChunk>;
6379
cursor?: FindCursor<GridFSChunk>;
80+
81+
/** The running total number of chunks read from the chunks collection. */
6482
expected: number;
65-
files: Collection<GridFSFile>;
83+
84+
/**
85+
* The filter used to search in the _files_ collection (i.e., `{ _id: <> }`)
86+
* This is not the same filter used when reading chunks from the chunks collection.
87+
*/
6688
filter: Document;
89+
90+
/** Indicates whether or not download has started. */
6791
init: boolean;
92+
93+
/** The expected number of chunks to read, calculated from start, end, chunkSize and file length. */
6894
expectedEnd: number;
6995
file?: GridFSFile;
7096
options: {

0 commit comments

Comments
 (0)