mongodb 7.1.1 → 7.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 (102) hide show
  1. package/README.md +11 -0
  2. package/lib/bson.js +26 -5
  3. package/lib/bson.js.map +1 -1
  4. package/lib/change_stream.js +4 -0
  5. package/lib/change_stream.js.map +1 -1
  6. package/lib/client-side-encryption/auto_encrypter.js +19 -10
  7. package/lib/client-side-encryption/auto_encrypter.js.map +1 -1
  8. package/lib/client-side-encryption/client_encryption.js +1 -3
  9. package/lib/client-side-encryption/client_encryption.js.map +1 -1
  10. package/lib/cmap/auth/aws4.js +4 -4
  11. package/lib/cmap/auth/aws4.js.map +1 -1
  12. package/lib/cmap/auth/gssapi.js +3 -6
  13. package/lib/cmap/auth/gssapi.js.map +1 -1
  14. package/lib/cmap/auth/mongodb_aws.js +3 -2
  15. package/lib/cmap/auth/mongodb_aws.js.map +1 -1
  16. package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js +3 -3
  17. package/lib/cmap/auth/mongodb_oidc/azure_machine_workflow.js.map +1 -1
  18. package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js +3 -3
  19. package/lib/cmap/auth/mongodb_oidc/gcp_machine_workflow.js.map +1 -1
  20. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js +3 -3
  21. package/lib/cmap/auth/mongodb_oidc/k8s_machine_workflow.js.map +1 -1
  22. package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js +3 -3
  23. package/lib/cmap/auth/mongodb_oidc/token_machine_workflow.js.map +1 -1
  24. package/lib/cmap/auth/mongodb_oidc.js +4 -4
  25. package/lib/cmap/auth/mongodb_oidc.js.map +1 -1
  26. package/lib/cmap/auth/plain.js +1 -1
  27. package/lib/cmap/auth/plain.js.map +1 -1
  28. package/lib/cmap/auth/scram.js +53 -40
  29. package/lib/cmap/auth/scram.js.map +1 -1
  30. package/lib/cmap/commands.js +46 -39
  31. package/lib/cmap/commands.js.map +1 -1
  32. package/lib/cmap/connect.js +1 -0
  33. package/lib/cmap/connect.js.map +1 -1
  34. package/lib/cmap/connection.js +5 -2
  35. package/lib/cmap/connection.js.map +1 -1
  36. package/lib/cmap/handshake/client_metadata.js +3 -4
  37. package/lib/cmap/handshake/client_metadata.js.map +1 -1
  38. package/lib/cmap/wire_protocol/compression.js +8 -7
  39. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  40. package/lib/cmap/wire_protocol/on_data.js.map +1 -1
  41. package/lib/cmap/wire_protocol/on_demand/document.js +9 -9
  42. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  43. package/lib/connection_string.js +21 -5
  44. package/lib/connection_string.js.map +1 -1
  45. package/lib/gridfs/download.js +2 -1
  46. package/lib/gridfs/download.js.map +1 -1
  47. package/lib/gridfs/upload.js +7 -7
  48. package/lib/gridfs/upload.js.map +1 -1
  49. package/lib/mongo_client.js.map +1 -1
  50. package/lib/operations/execute_operation.js +114 -41
  51. package/lib/operations/execute_operation.js.map +1 -1
  52. package/lib/operations/operation.js +1 -0
  53. package/lib/operations/operation.js.map +1 -1
  54. package/lib/runtime_adapters.js +32 -0
  55. package/lib/runtime_adapters.js.map +1 -0
  56. package/lib/sdam/srv_polling.js +1 -1
  57. package/lib/sdam/srv_polling.js.map +1 -1
  58. package/lib/sdam/topology.js +4 -2
  59. package/lib/sdam/topology.js.map +1 -1
  60. package/lib/sessions.js +124 -79
  61. package/lib/sessions.js.map +1 -1
  62. package/lib/utils.js +10 -33
  63. package/lib/utils.js.map +1 -1
  64. package/mongodb.d.ts +45 -2
  65. package/package.json +30 -21
  66. package/src/bson.ts +28 -5
  67. package/src/change_stream.ts +5 -0
  68. package/src/client-side-encryption/auto_encrypter.ts +17 -11
  69. package/src/client-side-encryption/client_encryption.ts +1 -3
  70. package/src/cmap/auth/auth_provider.ts +1 -1
  71. package/src/cmap/auth/aws4.ts +5 -5
  72. package/src/cmap/auth/gssapi.ts +9 -6
  73. package/src/cmap/auth/mongodb_aws.ts +2 -2
  74. package/src/cmap/auth/mongodb_oidc/azure_machine_workflow.ts +1 -1
  75. package/src/cmap/auth/mongodb_oidc/gcp_machine_workflow.ts +1 -1
  76. package/src/cmap/auth/mongodb_oidc/k8s_machine_workflow.ts +1 -1
  77. package/src/cmap/auth/mongodb_oidc/token_machine_workflow.ts +1 -1
  78. package/src/cmap/auth/mongodb_oidc.ts +4 -4
  79. package/src/cmap/auth/plain.ts +2 -2
  80. package/src/cmap/auth/scram.ts +82 -55
  81. package/src/cmap/commands.ts +70 -51
  82. package/src/cmap/connect.ts +2 -0
  83. package/src/cmap/connection.ts +11 -4
  84. package/src/cmap/handshake/client_metadata.ts +6 -6
  85. package/src/cmap/wire_protocol/compression.ts +18 -14
  86. package/src/cmap/wire_protocol/on_data.ts +5 -5
  87. package/src/cmap/wire_protocol/on_demand/document.ts +12 -14
  88. package/src/connection_string.ts +26 -8
  89. package/src/deps.ts +4 -4
  90. package/src/gridfs/download.ts +2 -2
  91. package/src/gridfs/upload.ts +13 -12
  92. package/src/index.ts +1 -0
  93. package/src/mongo_client.ts +24 -0
  94. package/src/operations/client_bulk_write/command_builder.ts +1 -1
  95. package/src/operations/execute_operation.ts +146 -45
  96. package/src/operations/operation.ts +8 -0
  97. package/src/runtime_adapters.ts +64 -0
  98. package/src/sdam/srv_polling.ts +1 -1
  99. package/src/sdam/topology.ts +10 -7
  100. package/src/sessions.ts +140 -96
  101. package/src/utils.ts +21 -40
  102. package/tsconfig.json +1 -1
@@ -5,11 +5,11 @@ import type { Connection } from '../connection';
5
5
  import { type AuthContext, AuthProvider } from './auth_provider';
6
6
  import type { MongoCredentials } from './mongo_credentials';
7
7
  import { AutomatedCallbackWorkflow } from './mongodb_oidc/automated_callback_workflow';
8
- import { callback as azureCallback } from './mongodb_oidc/azure_machine_workflow';
9
- import { callback as gcpCallback } from './mongodb_oidc/gcp_machine_workflow';
10
- import { callback as k8sCallback } from './mongodb_oidc/k8s_machine_workflow';
8
+ import { azureCallback } from './mongodb_oidc/azure_machine_workflow';
9
+ import { gcpCallback } from './mongodb_oidc/gcp_machine_workflow';
10
+ import { k8sCallback } from './mongodb_oidc/k8s_machine_workflow';
11
11
  import { TokenCache } from './mongodb_oidc/token_cache';
12
- import { callback as testCallback } from './mongodb_oidc/token_machine_workflow';
12
+ import { tokenMachineCallback as testCallback } from './mongodb_oidc/token_machine_workflow';
13
13
 
14
14
  /** Error when credentials are missing. */
15
15
  const MISSING_CREDENTIALS_ERROR = 'AuthContext must provide credentials.';
@@ -1,4 +1,4 @@
1
- import { Binary } from '../../bson';
1
+ import { Binary, ByteUtils } from '../../bson';
2
2
  import { MongoMissingCredentialsError } from '../../error';
3
3
  import { ns } from '../../utils';
4
4
  import { type AuthContext, AuthProvider } from './auth_provider';
@@ -12,7 +12,7 @@ export class Plain extends AuthProvider {
12
12
 
13
13
  const { username, password } = credentials;
14
14
 
15
- const payload = new Binary(Buffer.from(`\x00${username}\x00${password}`));
15
+ const payload = new Binary(ByteUtils.fromUTF8(`\x00${username}\x00${password}`));
16
16
  const command = {
17
17
  saslStart: 1,
18
18
  mechanism: 'PLAIN',
@@ -1,7 +1,6 @@
1
1
  import { saslprep } from '@mongodb-js/saslprep';
2
- import * as crypto from 'crypto';
3
2
 
4
- import { Binary, type Document } from '../../bson';
3
+ import { Binary, ByteUtils, type Document } from '../../bson';
5
4
  import {
6
5
  MongoInvalidArgumentError,
7
6
  MongoMissingCredentialsError,
@@ -65,21 +64,21 @@ function cleanUsername(username: string) {
65
64
  return username.replace('=', '=3D').replace(',', '=2C');
66
65
  }
67
66
 
68
- function clientFirstMessageBare(username: string, nonce: Buffer) {
67
+ function clientFirstMessageBare(username: string, nonce: Uint8Array) {
69
68
  // NOTE: This is done b/c Javascript uses UTF-16, but the server is hashing in UTF-8.
70
69
  // Since the username is not sasl-prep-d, we need to do this here.
71
- return Buffer.concat([
72
- Buffer.from('n=', 'utf8'),
73
- Buffer.from(username, 'utf8'),
74
- Buffer.from(',r=', 'utf8'),
75
- Buffer.from(nonce.toString('base64'), 'utf8')
70
+ return ByteUtils.concat([
71
+ ByteUtils.fromUTF8('n='),
72
+ ByteUtils.fromUTF8(username),
73
+ ByteUtils.fromUTF8(',r='),
74
+ ByteUtils.fromUTF8(ByteUtils.toBase64(nonce))
76
75
  ]);
77
76
  }
78
77
 
79
78
  function makeFirstMessage(
80
79
  cryptoMethod: CryptoMethod,
81
80
  credentials: MongoCredentials,
82
- nonce: Buffer
81
+ nonce: Uint8Array
83
82
  ) {
84
83
  const username = cleanUsername(credentials.username);
85
84
  const mechanism =
@@ -91,7 +90,7 @@ function makeFirstMessage(
91
90
  saslStart: 1,
92
91
  mechanism,
93
92
  payload: new Binary(
94
- Buffer.concat([Buffer.from('n,,', 'utf8'), clientFirstMessageBare(username, nonce)])
93
+ ByteUtils.concat([ByteUtils.fromUTF8('n,,'), clientFirstMessageBare(username, nonce)])
95
94
  ),
96
95
  autoAuthorize: 1,
97
96
  options: { skipEmptyExchange: true }
@@ -136,7 +135,7 @@ async function continueScramConversation(
136
135
  const processedPassword =
137
136
  cryptoMethod === 'sha256' ? saslprep(password) : passwordDigest(username, password);
138
137
 
139
- const payload: Binary = Buffer.isBuffer(response.payload)
138
+ const payload: Binary = ByteUtils.isUint8Array(response.payload)
140
139
  ? new Binary(response.payload)
141
140
  : response.payload;
142
141
 
@@ -157,37 +156,37 @@ async function continueScramConversation(
157
156
 
158
157
  // Set up start of proof
159
158
  const withoutProof = `c=biws,r=${rnonce}`;
160
- const saltedPassword = HI(
159
+ const saltedPassword = await HI(
161
160
  processedPassword,
162
- Buffer.from(salt, 'base64'),
161
+ ByteUtils.fromBase64(salt),
163
162
  iterations,
164
163
  cryptoMethod
165
164
  );
166
165
 
167
- const clientKey = HMAC(cryptoMethod, saltedPassword, 'Client Key');
168
- const serverKey = HMAC(cryptoMethod, saltedPassword, 'Server Key');
169
- const storedKey = H(cryptoMethod, clientKey);
166
+ const clientKey = await HMAC(cryptoMethod, saltedPassword, 'Client Key');
167
+ const serverKey = await HMAC(cryptoMethod, saltedPassword, 'Server Key');
168
+ const storedKey = await H(cryptoMethod, clientKey);
170
169
  const authMessage = [
171
170
  clientFirstMessageBare(username, nonce),
172
171
  payload.toString('utf8'),
173
172
  withoutProof
174
173
  ].join(',');
175
174
 
176
- const clientSignature = HMAC(cryptoMethod, storedKey, authMessage);
175
+ const clientSignature = await HMAC(cryptoMethod, storedKey, authMessage);
177
176
  const clientProof = `p=${xor(clientKey, clientSignature)}`;
178
177
  const clientFinal = [withoutProof, clientProof].join(',');
179
178
 
180
- const serverSignature = HMAC(cryptoMethod, serverKey, authMessage);
179
+ const serverSignature = await HMAC(cryptoMethod, serverKey, authMessage);
181
180
  const saslContinueCmd = {
182
181
  saslContinue: 1,
183
182
  conversationId: response.conversationId,
184
- payload: new Binary(Buffer.from(clientFinal))
183
+ payload: new Binary(ByteUtils.fromUTF8(clientFinal))
185
184
  };
186
185
 
187
186
  const r = await connection.command(ns(`${db}.$cmd`), saslContinueCmd, undefined);
188
187
  const parsedResponse = parsePayload(r.payload);
189
188
 
190
- if (!compareDigest(Buffer.from(parsedResponse.v, 'base64'), serverSignature)) {
189
+ if (!compareDigest(ByteUtils.fromBase64(parsedResponse.v), serverSignature)) {
191
190
  throw new MongoRuntimeError('Server returned an invalid signature');
192
191
  }
193
192
 
@@ -199,7 +198,7 @@ async function continueScramConversation(
199
198
  const retrySaslContinueCmd = {
200
199
  saslContinue: 1,
201
200
  conversationId: r.conversationId,
202
- payload: Buffer.alloc(0)
201
+ payload: ByteUtils.allocate(0)
203
202
  };
204
203
 
205
204
  await connection.command(ns(`${db}.$cmd`), retrySaslContinueCmd, undefined);
@@ -229,31 +228,36 @@ function passwordDigest(username: string, password: string) {
229
228
  throw new MongoInvalidArgumentError('Password cannot be empty');
230
229
  }
231
230
 
232
- let md5: crypto.Hash;
231
+ let nodeCrypto;
233
232
  try {
234
- md5 = crypto.createHash('md5');
233
+ // TODO: NODE-7424 - remove dependency on 'crypto' for SCRAM-SHA-1 authentication
234
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
235
+ nodeCrypto = require('crypto');
236
+ } catch (e) {
237
+ throw new MongoRuntimeError(
238
+ 'Node.js crypto module is required for SCRAM-SHA-1 authentication',
239
+ {
240
+ cause: e
241
+ }
242
+ );
243
+ }
244
+
245
+ try {
246
+ const md5 = nodeCrypto.createHash('md5');
247
+ md5.update(`${username}:mongo:${password}`, 'utf8');
248
+ return md5.digest('hex');
235
249
  } catch (err) {
236
- if (crypto.getFips()) {
250
+ if (nodeCrypto.getFips()) {
237
251
  // This error is (slightly) more helpful than what comes from OpenSSL directly, e.g.
238
252
  // 'Error: error:060800C8:digital envelope routines:EVP_DigestInit_ex:disabled for FIPS'
239
253
  throw new Error('Auth mechanism SCRAM-SHA-1 is not supported in FIPS mode');
240
254
  }
241
255
  throw err;
242
256
  }
243
- md5.update(`${username}:mongo:${password}`, 'utf8');
244
- return md5.digest('hex');
245
257
  }
246
258
 
247
259
  // XOR two buffers
248
- function xor(a: Buffer, b: Buffer) {
249
- if (!Buffer.isBuffer(a)) {
250
- a = Buffer.from(a);
251
- }
252
-
253
- if (!Buffer.isBuffer(b)) {
254
- b = Buffer.from(b);
255
- }
256
-
260
+ function xor(a: Uint8Array, b: Uint8Array) {
257
261
  const length = Math.max(a.length, b.length);
258
262
  const res = [];
259
263
 
@@ -261,19 +265,35 @@ function xor(a: Buffer, b: Buffer) {
261
265
  res.push(a[i] ^ b[i]);
262
266
  }
263
267
 
264
- return Buffer.from(res).toString('base64');
268
+ return ByteUtils.toBase64(ByteUtils.fromNumberArray(res));
265
269
  }
266
270
 
267
- function H(method: CryptoMethod, text: Buffer) {
268
- return crypto.createHash(method).update(text).digest();
271
+ async function H(method: CryptoMethod, text: Uint8Array): Promise<Uint8Array> {
272
+ const buffer = await crypto.subtle.digest(method === 'sha256' ? 'SHA-256' : 'SHA-1', text);
273
+ return new Uint8Array(buffer);
269
274
  }
270
275
 
271
- function HMAC(method: CryptoMethod, key: Buffer, text: Buffer | string) {
272
- return crypto.createHmac(method, key).update(text).digest();
276
+ async function HMAC(
277
+ method: CryptoMethod,
278
+ key: Uint8Array,
279
+ text: Uint8Array | string
280
+ ): Promise<Uint8Array> {
281
+ const keyBuffer = ByteUtils.toLocalBufferType(key);
282
+ const cryptoKey = await crypto.subtle.importKey(
283
+ 'raw',
284
+ keyBuffer,
285
+ { name: 'HMAC', hash: { name: method === 'sha256' ? 'SHA-256' : 'SHA-1' } },
286
+ false,
287
+ ['sign', 'verify']
288
+ );
289
+ const textData: Uint8Array = typeof text === 'string' ? new TextEncoder().encode(text) : text;
290
+ const textBuffer = ByteUtils.toLocalBufferType(textData);
291
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, textBuffer);
292
+ return new Uint8Array(signature);
273
293
  }
274
294
 
275
295
  interface HICache {
276
- [key: string]: Buffer;
296
+ [key: string]: Uint8Array;
277
297
  }
278
298
 
279
299
  let _hiCache: HICache = {};
@@ -288,21 +308,32 @@ const hiLengthMap = {
288
308
  sha1: 20
289
309
  };
290
310
 
291
- function HI(data: string, salt: Buffer, iterations: number, cryptoMethod: CryptoMethod) {
311
+ async function HI(data: string, salt: Uint8Array, iterations: number, cryptoMethod: CryptoMethod) {
292
312
  // omit the work if already generated
293
- const key = [data, salt.toString('base64'), iterations].join('_');
313
+ const key = [data, ByteUtils.toBase64(salt), iterations].join('_');
294
314
  if (_hiCache[key] != null) {
295
315
  return _hiCache[key];
296
316
  }
297
317
 
298
- // generate the salt
299
- const saltedData = crypto.pbkdf2Sync(
300
- data,
301
- salt,
302
- iterations,
303
- hiLengthMap[cryptoMethod],
304
- cryptoMethod
318
+ const keyMaterial = await crypto.subtle.importKey(
319
+ 'raw',
320
+ new TextEncoder().encode(data),
321
+ { name: 'PBKDF2' },
322
+ false,
323
+ ['deriveBits']
305
324
  );
325
+ const params = {
326
+ name: 'PBKDF2',
327
+ salt: salt,
328
+ iterations: iterations,
329
+ hash: { name: cryptoMethod === 'sha256' ? 'SHA-256' : 'SHA-1' }
330
+ };
331
+ const derivedBits = await crypto.subtle.deriveBits(
332
+ params,
333
+ keyMaterial,
334
+ hiLengthMap[cryptoMethod] * 8
335
+ );
336
+ const saltedData = new Uint8Array(derivedBits);
306
337
 
307
338
  // cache a copy to speed up the next lookup, but prevent unbounded cache growth
308
339
  if (_hiCacheCount >= 200) {
@@ -314,15 +345,11 @@ function HI(data: string, salt: Buffer, iterations: number, cryptoMethod: Crypto
314
345
  return saltedData;
315
346
  }
316
347
 
317
- function compareDigest(lhs: Buffer, rhs: Uint8Array) {
348
+ function compareDigest(lhs: Uint8Array, rhs: Uint8Array) {
318
349
  if (lhs.length !== rhs.length) {
319
350
  return false;
320
351
  }
321
352
 
322
- if (typeof crypto.timingSafeEqual === 'function') {
323
- return crypto.timingSafeEqual(lhs, rhs);
324
- }
325
-
326
353
  let result = 0;
327
354
  for (let i = 0; i < lhs.length; i++) {
328
355
  result |= lhs[i] ^ rhs[i];
@@ -1,5 +1,13 @@
1
- import type { BSONSerializeOptions, Document, Long } from '../bson';
2
- import * as BSON from '../bson';
1
+ import {
2
+ BSON,
3
+ type BSONSerializeOptions,
4
+ ByteUtils,
5
+ type Document,
6
+ type Long,
7
+ NumberUtils,
8
+ readInt32LE,
9
+ setUint32LE
10
+ } from '../bson';
3
11
  import { MongoInvalidArgumentError, MongoRuntimeError } from '../error';
4
12
  import { type ReadPreference } from '../read_preference';
5
13
  import type { ClientSession } from '../sessions';
@@ -30,7 +38,7 @@ const QUERY_FAILURE = 2;
30
38
  const SHARD_CONFIG_STALE = 4;
31
39
  const AWAIT_CAPABLE = 8;
32
40
 
33
- const encodeUTF8Into = BSON.BSON.onDemand.ByteUtils.encodeUTF8Into;
41
+ const encodeUTF8Into = ByteUtils.encodeUTF8Into;
34
42
 
35
43
  /** @internal */
36
44
  export type WriteProtocolMessageType = OpQueryRequest | OpMsgRequest;
@@ -182,10 +190,10 @@ export class OpQueryRequest {
182
190
  if (this.batchSize !== this.numberToReturn) this.numberToReturn = this.batchSize;
183
191
 
184
192
  // Allocate write protocol header buffer
185
- const header = Buffer.alloc(
193
+ const header = ByteUtils.allocate(
186
194
  4 * 4 + // Header
187
195
  4 + // Flags
188
- Buffer.byteLength(this.ns) +
196
+ ByteUtils.utf8ByteLength(this.ns) +
189
197
  1 + // namespace
190
198
  4 + // numberToSkip
191
199
  4 // numberToReturn
@@ -256,7 +264,7 @@ export class OpQueryRequest {
256
264
  index = index + 4;
257
265
 
258
266
  // Write collection name
259
- index = index + header.write(this.ns, index, 'utf8') + 1;
267
+ index = index + encodeUTF8Into(header, this.ns, index) + 1;
260
268
  header[index - 1] = 0;
261
269
 
262
270
  // Write header information flags numberToSkip
@@ -290,8 +298,8 @@ export interface MessageHeader {
290
298
  /** @internal */
291
299
  export class OpReply {
292
300
  parsed: boolean;
293
- raw: Buffer;
294
- data: Buffer;
301
+ raw: Uint8Array;
302
+ data: Uint8Array;
295
303
  opts: BSONSerializeOptions;
296
304
  length: number;
297
305
  requestId: number;
@@ -318,9 +326,9 @@ export class OpReply {
318
326
  moreToCome = false;
319
327
 
320
328
  constructor(
321
- message: Buffer,
329
+ message: Uint8Array,
322
330
  msgHeader: MessageHeader,
323
- msgBody: Buffer,
331
+ msgBody: Uint8Array,
324
332
  opts?: BSONSerializeOptions
325
333
  ) {
326
334
  this.parsed = false;
@@ -364,10 +372,10 @@ export class OpReply {
364
372
  this.index = 20;
365
373
 
366
374
  // Read the message body
367
- this.responseFlags = this.data.readInt32LE(0);
368
- this.cursorId = new BSON.Long(this.data.readInt32LE(4), this.data.readInt32LE(8));
369
- this.startingFrom = this.data.readInt32LE(12);
370
- this.numberReturned = this.data.readInt32LE(16);
375
+ this.responseFlags = readInt32LE(this.data, 0);
376
+ this.cursorId = new BSON.Long(readInt32LE(this.data, 4), readInt32LE(this.data, 8));
377
+ this.startingFrom = readInt32LE(this.data, 12);
378
+ this.numberReturned = readInt32LE(this.data, 16);
371
379
 
372
380
  if (this.numberReturned < 0 || this.numberReturned > 2 ** 32 - 1) {
373
381
  throw new RangeError(
@@ -433,7 +441,7 @@ export class DocumentSequence {
433
441
  documents: Document[];
434
442
  serializedDocumentsLength: number;
435
443
  private chunks: Uint8Array[];
436
- private header: Buffer;
444
+ private header: Uint8Array;
437
445
 
438
446
  /**
439
447
  * Create a new document sequence for the provided field.
@@ -446,7 +454,7 @@ export class DocumentSequence {
446
454
  this.serializedDocumentsLength = 0;
447
455
  // Document sequences starts with type 1 at the first byte.
448
456
  // Field strings must always be UTF-8.
449
- const buffer = Buffer.allocUnsafe(1 + 4 + this.field.length + 1);
457
+ const buffer = ByteUtils.allocateUnsafe(1 + 4 + this.field.length + 1);
450
458
  buffer[0] = 1;
451
459
  // Third part is the field name at offset 5 with trailing null byte.
452
460
  encodeUTF8Into(buffer, `${this.field}\0`, 5);
@@ -473,7 +481,13 @@ export class DocumentSequence {
473
481
  // Push the document raw bson.
474
482
  this.chunks.push(buffer);
475
483
  // Write the new length.
476
- this.header?.writeInt32LE(4 + this.field.length + 1 + this.serializedDocumentsLength, 1);
484
+ if (this.header) {
485
+ NumberUtils.setInt32LE(
486
+ this.header,
487
+ 1,
488
+ 4 + this.field.length + 1 + this.serializedDocumentsLength
489
+ );
490
+ }
477
491
  return this.serializedDocumentsLength + this.header.length;
478
492
  }
479
493
 
@@ -482,7 +496,7 @@ export class DocumentSequence {
482
496
  * @returns The section bytes.
483
497
  */
484
498
  toBin(): Uint8Array {
485
- return Buffer.concat(this.chunks);
499
+ return ByteUtils.concat(this.chunks);
486
500
  }
487
501
  }
488
502
 
@@ -531,8 +545,8 @@ export class OpMsgRequest {
531
545
  typeof options.exhaustAllowed === 'boolean' ? options.exhaustAllowed : false;
532
546
  }
533
547
 
534
- toBin(): Buffer[] {
535
- const buffers: Buffer[] = [];
548
+ toBin(): Uint8Array[] {
549
+ const buffers: Uint8Array[] = [];
536
550
  let flags = 0;
537
551
 
538
552
  if (this.checksumPresent) {
@@ -547,7 +561,7 @@ export class OpMsgRequest {
547
561
  flags |= OPTS_EXHAUST_ALLOWED;
548
562
  }
549
563
 
550
- const header = Buffer.alloc(
564
+ const header = ByteUtils.allocate(
551
565
  4 * 4 + // Header
552
566
  4 // Flags
553
567
  );
@@ -558,11 +572,13 @@ export class OpMsgRequest {
558
572
  const command = this.command;
559
573
  totalLength += this.makeSections(buffers, command);
560
574
 
561
- header.writeInt32LE(totalLength, 0); // messageLength
562
- header.writeInt32LE(this.requestId, 4); // requestID
563
- header.writeInt32LE(0, 8); // responseTo
564
- header.writeInt32LE(OP_MSG, 12); // opCode
565
- header.writeUInt32LE(flags, 16); // flags
575
+ NumberUtils.setInt32LE(header, 0, totalLength); // messageLength
576
+ NumberUtils.setInt32LE(header, 4, this.requestId); // requestID
577
+ NumberUtils.setInt32LE(header, 8, 0); // responseTo
578
+ NumberUtils.setInt32LE(header, 12, OP_MSG); // opCode
579
+ // The OP_MSG spec calls out that flags is uint32:
580
+ // https://github.com/mongodb/specifications/blob/master/source/message/OP_MSG.md#op_msg-1
581
+ setUint32LE(header, 16, flags); // flags
566
582
  return buffers;
567
583
  }
568
584
 
@@ -571,7 +587,7 @@ export class OpMsgRequest {
571
587
  */
572
588
  makeSections(buffers: Uint8Array[], document: Document): number {
573
589
  const sequencesBuffer = this.extractDocumentSequences(document);
574
- const payloadTypeBuffer = Buffer.allocUnsafe(1);
590
+ const payloadTypeBuffer = ByteUtils.allocateUnsafe(1);
575
591
  payloadTypeBuffer[0] = 0;
576
592
 
577
593
  const documentBuffer = this.serializeBson(document);
@@ -606,11 +622,11 @@ export class OpMsgRequest {
606
622
  }
607
623
  }
608
624
  if (chunks.length > 0) {
609
- return Buffer.concat(chunks);
625
+ return ByteUtils.concat(chunks);
610
626
  }
611
627
  // If we have no document sequences we return an empty buffer for nothing to add
612
628
  // to the payload.
613
- return Buffer.alloc(0);
629
+ return ByteUtils.allocate(0);
614
630
  }
615
631
 
616
632
  serializeBson(document: Document): Uint8Array {
@@ -630,8 +646,8 @@ export class OpMsgRequest {
630
646
  /** @internal */
631
647
  export class OpMsgResponse {
632
648
  parsed: boolean;
633
- raw: Buffer;
634
- data: Buffer;
649
+ raw: Uint8Array;
650
+ data: Uint8Array;
635
651
  opts: BSONSerializeOptions;
636
652
  length: number;
637
653
  requestId: number;
@@ -652,9 +668,9 @@ export class OpMsgResponse {
652
668
  sections: Uint8Array[] = [];
653
669
 
654
670
  constructor(
655
- message: Buffer,
671
+ message: Uint8Array,
656
672
  msgHeader: MessageHeader,
657
- msgBody: Buffer,
673
+ msgBody: Uint8Array,
658
674
  opts?: BSONSerializeOptions
659
675
  ) {
660
676
  this.parsed = false;
@@ -676,7 +692,7 @@ export class OpMsgResponse {
676
692
  this.fromCompressed = msgHeader.fromCompressed;
677
693
 
678
694
  // Read response flags
679
- this.responseFlags = msgBody.readInt32LE(0);
695
+ this.responseFlags = readInt32LE(msgBody, 0);
680
696
  this.checksumPresent = (this.responseFlags & OPTS_CHECKSUM_PRESENT) !== 0;
681
697
  this.moreToCome = (this.responseFlags & OPTS_MORE_TO_COME) !== 0;
682
698
  this.exhaustAllowed = (this.responseFlags & OPTS_EXHAUST_ALLOWED) !== 0;
@@ -700,9 +716,12 @@ export class OpMsgResponse {
700
716
  this.index = 4;
701
717
 
702
718
  while (this.index < this.data.length) {
703
- const payloadType = this.data.readUInt8(this.index++);
719
+ const payloadType = this.data[this.index++];
704
720
  if (payloadType === 0) {
705
- const bsonSize = this.data.readUInt32LE(this.index);
721
+ // BSON spec specifies that this is a 32-bit signed integer: https://bsonspec.org/spec.html#:~:text=%3A%3A%3D-,int32,-e_list%20unsigned_byte(0
722
+ // While allowing negative sizes seems odd, in practice we never expect a negative size. Also, the server's 16mb limit for BSON documents leaves plenty
723
+ // of room in an int32 to store a document of the max BSON size that the server supports
724
+ const bsonSize = readInt32LE(this.data, this.index);
706
725
  const bin = this.data.subarray(this.index, this.index + bsonSize);
707
726
 
708
727
  this.sections.push(bin);
@@ -758,31 +777,31 @@ export class OpCompressedRequest {
758
777
  return !uncompressibleCommands.has(commandName);
759
778
  }
760
779
 
761
- async toBin(): Promise<Buffer[]> {
762
- const concatenatedOriginalCommandBuffer = Buffer.concat(this.command.toBin());
780
+ async toBin(): Promise<Uint8Array[]> {
781
+ const concatenatedOriginalCommandBuffer = ByteUtils.concat(this.command.toBin());
763
782
  // otherwise, compress the message
764
783
  const messageToBeCompressed = concatenatedOriginalCommandBuffer.slice(MESSAGE_HEADER_SIZE);
765
784
 
766
785
  // Extract information needed for OP_COMPRESSED from the uncompressed message
767
- const originalCommandOpCode = concatenatedOriginalCommandBuffer.readInt32LE(12);
786
+ const originalCommandOpCode = readInt32LE(concatenatedOriginalCommandBuffer, 12);
768
787
 
769
788
  // Compress the message body
770
789
  const compressedMessage = await compress(this.options, messageToBeCompressed);
771
790
  // Create the msgHeader of OP_COMPRESSED
772
- const msgHeader = Buffer.alloc(MESSAGE_HEADER_SIZE);
773
- msgHeader.writeInt32LE(
774
- MESSAGE_HEADER_SIZE + COMPRESSION_DETAILS_SIZE + compressedMessage.length,
775
- 0
791
+ const msgHeader = ByteUtils.allocate(MESSAGE_HEADER_SIZE);
792
+ NumberUtils.setInt32LE(
793
+ msgHeader,
794
+ 0,
795
+ MESSAGE_HEADER_SIZE + COMPRESSION_DETAILS_SIZE + compressedMessage.length
776
796
  ); // messageLength
777
- msgHeader.writeInt32LE(this.command.requestId, 4); // requestID
778
- msgHeader.writeInt32LE(0, 8); // responseTo (zero)
779
- msgHeader.writeInt32LE(OP_COMPRESSED, 12); // opCode
780
-
797
+ NumberUtils.setInt32LE(msgHeader, 4, this.command.requestId); // requestID
798
+ NumberUtils.setInt32LE(msgHeader, 8, 0); // responseTo (zero)
799
+ NumberUtils.setInt32LE(msgHeader, 12, OP_COMPRESSED); // opCode
781
800
  // Create the compression details of OP_COMPRESSED
782
- const compressionDetails = Buffer.alloc(COMPRESSION_DETAILS_SIZE);
783
- compressionDetails.writeInt32LE(originalCommandOpCode, 0); // originalOpcode
784
- compressionDetails.writeInt32LE(messageToBeCompressed.length, 4); // Size of the uncompressed compressedMessage, excluding the MsgHeader
785
- compressionDetails.writeUInt8(Compressor[this.options.agreedCompressor], 8); // compressorID
801
+ const compressionDetails = ByteUtils.allocate(COMPRESSION_DETAILS_SIZE);
802
+ NumberUtils.setInt32LE(compressionDetails, 0, originalCommandOpCode); // originalOpcode
803
+ NumberUtils.setInt32LE(compressionDetails, 4, messageToBeCompressed.length); // Size of the uncompressed compressedMessage, excluding the MsgHeader
804
+ compressionDetails[8] = Compressor[this.options.agreedCompressor]; // compressorID
786
805
  return [msgHeader, compressionDetails, compressedMessage];
787
806
  }
788
807
  }
@@ -224,6 +224,7 @@ export interface HandshakeDocument extends Document {
224
224
  compression: string[];
225
225
  saslSupportedMechs?: string;
226
226
  loadBalanced?: boolean;
227
+ backpressure: true;
227
228
  }
228
229
 
229
230
  /**
@@ -241,6 +242,7 @@ export async function prepareHandshakeDocument(
241
242
 
242
243
  const handshakeDoc: HandshakeDocument = {
243
244
  [serverApi?.version || options.loadBalanced === true ? 'hello' : LEGACY_HELLO_COMMAND]: 1,
245
+ backpressure: true,
244
246
  helloOk: true,
245
247
  client: clientMetadata,
246
248
  compression: compressors
@@ -3,6 +3,7 @@ import { clearTimeout, setTimeout } from 'timers';
3
3
 
4
4
  import {
5
5
  type BSONSerializeOptions,
6
+ ByteUtils,
6
7
  deserialize,
7
8
  type DeserializeOptions,
8
9
  type Document,
@@ -35,6 +36,7 @@ import { type MongoClientAuthProviders } from '../mongo_client_auth_providers';
35
36
  import { MongoLoggableComponent, type MongoLogger, SeverityLevel } from '../mongo_logger';
36
37
  import { type Abortable, type CancellationToken, TypedEventEmitter } from '../mongo_types';
37
38
  import { ReadPreference, type ReadPreferenceLike } from '../read_preference';
39
+ import { type Runtime } from '../runtime_adapters';
38
40
  import { ServerType } from '../sdam/common';
39
41
  import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions';
40
42
  import { type TimeoutContext, TimeoutError } from '../timeout';
@@ -143,6 +145,8 @@ export interface ConnectionOptions
143
145
  metadata: Promise<ClientMetadata>;
144
146
  /** @internal */
145
147
  mongoLogger?: MongoLogger | undefined;
148
+ /** @internal */
149
+ runtime: Runtime;
146
150
  }
147
151
 
148
152
  /** @public */
@@ -174,7 +178,7 @@ function streamIdentifier(stream: Stream, options: ConnectionOptions): string {
174
178
  return HostAddress.fromHostPort(remoteAddress, remotePort).toString();
175
179
  }
176
180
 
177
- return uuidV4().toString('hex');
181
+ return ByteUtils.toHex(uuidV4());
178
182
  }
179
183
 
180
184
  /** @internal */
@@ -204,7 +208,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
204
208
  private lastUseTime: number;
205
209
  private clusterTime: Document | null = null;
206
210
  private error: Error | null = null;
207
- private dataEvents: AsyncGenerator<Buffer, void, void> | null = null;
211
+ private dataEvents: AsyncGenerator<Uint8Array, void, void> | null = null;
208
212
 
209
213
  private readonly socketTimeoutMS: number;
210
214
  private readonly monitorCommands: boolean;
@@ -582,6 +586,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
582
586
  this.throwIfAborted();
583
587
  }
584
588
  } catch (error) {
589
+ if (options.session != null && !(error instanceof MongoServerError)) {
590
+ updateSessionFromResponse(options.session, MongoDBResponse.empty);
591
+ }
585
592
  if (this.shouldEmitAndLogCommand) {
586
593
  this.emitAndLogCommand(
587
594
  this.monitorCommands,
@@ -696,7 +703,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
696
703
  zlibCompressionLevel: options.zlibCompressionLevel ?? 0
697
704
  });
698
705
 
699
- const buffer = Buffer.concat(await finalCommand.toBin());
706
+ const buffer = ByteUtils.concat(await finalCommand.toBin());
700
707
 
701
708
  if (options.timeoutContext?.csotEnabled()) {
702
709
  if (
@@ -794,7 +801,7 @@ export class SizedMessageTransform extends Transform {
794
801
  this.connection = connection;
795
802
  }
796
803
 
797
- override _transform(chunk: Buffer, encoding: unknown, callback: TransformCallback): void {
804
+ override _transform(chunk: Uint8Array, encoding: unknown, callback: TransformCallback): void {
798
805
  if (this.connection.delayedTimeoutId != null) {
799
806
  clearTimeout(this.connection.delayedTimeoutId);
800
807
  this.connection.delayedTimeoutId = null;