mongodb 6.5.0-dev.20240406.sha.62ea94b → 6.5.0-dev.20240411.sha.ddd1e81

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 (49) hide show
  1. package/lib/bson.js.map +1 -1
  2. package/lib/cmap/auth/gssapi.js +2 -1
  3. package/lib/cmap/auth/gssapi.js.map +1 -1
  4. package/lib/cmap/commands.js +24 -111
  5. package/lib/cmap/commands.js.map +1 -1
  6. package/lib/cmap/connect.js +1 -1
  7. package/lib/cmap/connect.js.map +1 -1
  8. package/lib/cmap/connection.js +46 -31
  9. package/lib/cmap/connection.js.map +1 -1
  10. package/lib/cmap/wire_protocol/compression.js +2 -2
  11. package/lib/cmap/wire_protocol/compression.js.map +1 -1
  12. package/lib/cmap/wire_protocol/on_demand/document.js.map +1 -1
  13. package/lib/cmap/wire_protocol/responses.js +88 -0
  14. package/lib/cmap/wire_protocol/responses.js.map +1 -0
  15. package/lib/collection.js.map +1 -1
  16. package/lib/cursor/abstract_cursor.js +1 -0
  17. package/lib/cursor/abstract_cursor.js.map +1 -1
  18. package/lib/db.js +2 -1
  19. package/lib/db.js.map +1 -1
  20. package/lib/gridfs/download.js.map +1 -1
  21. package/lib/gridfs/index.js.map +1 -1
  22. package/lib/gridfs/upload.js.map +1 -1
  23. package/lib/index.js.map +1 -1
  24. package/lib/operations/execute_operation.js +3 -0
  25. package/lib/operations/execute_operation.js.map +1 -1
  26. package/lib/operations/operation.js.map +1 -1
  27. package/lib/sessions.js +2 -1
  28. package/lib/sessions.js.map +1 -1
  29. package/mongodb.d.ts +19 -3
  30. package/package.json +1 -1
  31. package/src/bson.ts +1 -0
  32. package/src/cmap/auth/gssapi.ts +3 -5
  33. package/src/cmap/commands.ts +28 -132
  34. package/src/cmap/connect.ts +1 -1
  35. package/src/cmap/connection.ts +86 -38
  36. package/src/cmap/wire_protocol/compression.ts +4 -6
  37. package/src/cmap/wire_protocol/on_demand/document.ts +1 -0
  38. package/src/cmap/wire_protocol/responses.ts +109 -0
  39. package/src/collection.ts +2 -0
  40. package/src/cursor/abstract_cursor.ts +3 -0
  41. package/src/db.ts +4 -1
  42. package/src/gridfs/download.ts +2 -0
  43. package/src/gridfs/index.ts +2 -0
  44. package/src/gridfs/upload.ts +2 -0
  45. package/src/index.ts +3 -2
  46. package/src/mongo_client.ts +3 -1
  47. package/src/operations/execute_operation.ts +5 -0
  48. package/src/operations/operation.ts +3 -0
  49. package/src/sessions.ts +9 -2
@@ -54,7 +54,7 @@ import {
54
54
  OpMsgRequest,
55
55
  type OpMsgResponse,
56
56
  OpQueryRequest,
57
- type OpQueryResponse,
57
+ type OpReply,
58
58
  type WriteProtocolMessageType
59
59
  } from './commands';
60
60
  import type { Stream } from './connect';
@@ -62,6 +62,7 @@ import type { ClientMetadata } from './handshake/client_metadata';
62
62
  import { StreamDescription, type StreamDescriptionOptions } from './stream_description';
63
63
  import { type CompressorName, decompressResponse } from './wire_protocol/compression';
64
64
  import { onData } from './wire_protocol/on_data';
65
+ import { MongoDBResponse, type MongoDBResponseConstructor } from './wire_protocol/responses';
65
66
  import { getReadPreference, isSharded } from './wire_protocol/shared';
66
67
 
67
68
  /** @internal */
@@ -412,7 +413,11 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
412
413
  return message;
413
414
  }
414
415
 
415
- private async *sendWire(message: WriteProtocolMessageType, options: CommandOptions) {
416
+ private async *sendWire(
417
+ message: WriteProtocolMessageType,
418
+ options: CommandOptions,
419
+ responseType?: MongoDBResponseConstructor
420
+ ): AsyncGenerator<MongoDBResponse> {
416
421
  this.throwIfAborted();
417
422
 
418
423
  if (typeof options.socketTimeoutMS === 'number') {
@@ -428,7 +433,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
428
433
  });
429
434
 
430
435
  if (options.noResponse) {
431
- yield { ok: 1 };
436
+ yield MongoDBResponse.empty;
432
437
  return;
433
438
  }
434
439
 
@@ -436,21 +441,9 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
436
441
 
437
442
  for await (const response of this.readMany()) {
438
443
  this.socket.setTimeout(0);
439
- response.parse(options);
440
-
441
- const [document] = response.documents;
444
+ const bson = response.parse();
442
445
 
443
- if (!Buffer.isBuffer(document)) {
444
- const { session } = options;
445
- if (session) {
446
- updateSessionFromResponse(session, document);
447
- }
448
-
449
- if (document.$clusterTime) {
450
- this.clusterTime = document.$clusterTime;
451
- this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
452
- }
453
- }
446
+ const document = new (responseType ?? MongoDBResponse)(bson, 0, false);
454
447
 
455
448
  yield document;
456
449
  this.throwIfAborted();
@@ -469,7 +462,8 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
469
462
  private async *sendCommand(
470
463
  ns: MongoDBNamespace,
471
464
  command: Document,
472
- options: CommandOptions = {}
465
+ options: CommandOptions,
466
+ responseType?: MongoDBResponseConstructor
473
467
  ) {
474
468
  const message = this.prepareCommand(ns.db, command, options);
475
469
 
@@ -485,19 +479,41 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
485
479
  );
486
480
  }
487
481
 
488
- let document;
482
+ // If `documentsReturnedIn` not set or raw is not enabled, use input bson options
483
+ // Otherwise, support raw flag. Raw only works for cursors that hardcode firstBatch/nextBatch fields
484
+ const bsonOptions =
485
+ options.documentsReturnedIn == null || !options.raw
486
+ ? options
487
+ : {
488
+ ...options,
489
+ raw: false,
490
+ fieldsAsRaw: { [options.documentsReturnedIn]: true }
491
+ };
492
+
493
+ /** MongoDBResponse instance or subclass */
494
+ let document: MongoDBResponse | undefined = undefined;
495
+ /** Cached result of a toObject call */
496
+ let object: Document | undefined = undefined;
489
497
  try {
490
498
  this.throwIfAborted();
491
- for await (document of this.sendWire(message, options)) {
492
- if (!Buffer.isBuffer(document) && document.writeConcernError) {
493
- throw new MongoWriteConcernError(document.writeConcernError, document);
499
+ for await (document of this.sendWire(message, options, responseType)) {
500
+ object = undefined;
501
+ if (options.session != null) {
502
+ updateSessionFromResponse(options.session, document);
503
+ }
504
+
505
+ if (document.$clusterTime) {
506
+ this.clusterTime = document.$clusterTime;
507
+ this.emit(Connection.CLUSTER_TIME_RECEIVED, document.$clusterTime);
494
508
  }
495
509
 
496
- if (
497
- !Buffer.isBuffer(document) &&
498
- (document.ok === 0 || document.$err || document.errmsg || document.code)
499
- ) {
500
- throw new MongoServerError(document);
510
+ if (document.has('writeConcernError')) {
511
+ object ??= document.toObject(bsonOptions);
512
+ throw new MongoWriteConcernError(object.writeConcernError, object);
513
+ }
514
+
515
+ if (document.isError) {
516
+ throw new MongoServerError((object ??= document.toObject(bsonOptions)));
501
517
  }
502
518
 
503
519
  if (this.shouldEmitAndLogCommand) {
@@ -509,14 +525,19 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
509
525
  new CommandSucceededEvent(
510
526
  this,
511
527
  message,
512
- options.noResponse ? undefined : document,
528
+ options.noResponse ? undefined : (object ??= document.toObject(bsonOptions)),
513
529
  started,
514
530
  this.description.serverConnectionId
515
531
  )
516
532
  );
517
533
  }
518
534
 
519
- yield document;
535
+ if (responseType == null) {
536
+ yield (object ??= document.toObject(bsonOptions));
537
+ } else {
538
+ yield document;
539
+ }
540
+
520
541
  this.throwIfAborted();
521
542
  }
522
543
  } catch (error) {
@@ -530,7 +551,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
530
551
  new CommandSucceededEvent(
531
552
  this,
532
553
  message,
533
- options.noResponse ? undefined : document,
554
+ options.noResponse ? undefined : (object ??= document?.toObject(bsonOptions)),
534
555
  started,
535
556
  this.description.serverConnectionId
536
557
  )
@@ -555,13 +576,27 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
555
576
  }
556
577
  }
557
578
 
579
+ public async command<T extends MongoDBResponseConstructor>(
580
+ ns: MongoDBNamespace,
581
+ command: Document,
582
+ options: CommandOptions | undefined,
583
+ responseType: T | undefined
584
+ ): Promise<typeof responseType extends undefined ? Document : InstanceType<T>>;
585
+
586
+ public async command(
587
+ ns: MongoDBNamespace,
588
+ command: Document,
589
+ options?: CommandOptions
590
+ ): Promise<Document>;
591
+
558
592
  public async command(
559
593
  ns: MongoDBNamespace,
560
594
  command: Document,
561
- options: CommandOptions = {}
595
+ options: CommandOptions = {},
596
+ responseType?: MongoDBResponseConstructor
562
597
  ): Promise<Document> {
563
598
  this.throwIfAborted();
564
- for await (const document of this.sendCommand(ns, command, options)) {
599
+ for await (const document of this.sendCommand(ns, command, options, responseType)) {
565
600
  return document;
566
601
  }
567
602
  throw new MongoUnexpectedServerResponseError('Unable to get response from server');
@@ -622,7 +657,7 @@ export class Connection extends TypedEventEmitter<ConnectionEvents> {
622
657
  *
623
658
  * Note that `for-await` loops call `return` automatically when the loop is exited.
624
659
  */
625
- private async *readMany(): AsyncGenerator<OpMsgResponse | OpQueryResponse> {
660
+ private async *readMany(): AsyncGenerator<OpMsgResponse | OpReply> {
626
661
  try {
627
662
  this.dataEvents = onData(this.messageStream);
628
663
  for await (const message of this.dataEvents) {
@@ -687,11 +722,24 @@ export class CryptoConnection extends Connection {
687
722
  this.autoEncrypter = options.autoEncrypter;
688
723
  }
689
724
 
690
- /** @internal @override */
691
- override async command(
725
+ public override async command<T extends MongoDBResponseConstructor>(
726
+ ns: MongoDBNamespace,
727
+ command: Document,
728
+ options: CommandOptions | undefined,
729
+ responseType: T
730
+ ): Promise<InstanceType<T>>;
731
+
732
+ public override async command(
733
+ ns: MongoDBNamespace,
734
+ command: Document,
735
+ options?: CommandOptions
736
+ ): Promise<Document>;
737
+
738
+ override async command<T extends MongoDBResponseConstructor>(
692
739
  ns: MongoDBNamespace,
693
740
  cmd: Document,
694
- options: CommandOptions
741
+ options?: CommandOptions,
742
+ responseType?: T | undefined
695
743
  ): Promise<Document> {
696
744
  const { autoEncrypter } = this;
697
745
  if (!autoEncrypter) {
@@ -705,7 +753,7 @@ export class CryptoConnection extends Connection {
705
753
  const serverWireVersion = maxWireVersion(this);
706
754
  if (serverWireVersion === 0) {
707
755
  // This means the initial handshake hasn't happened yet
708
- return await super.command(ns, cmd, options);
756
+ return await super.command<T>(ns, cmd, options, responseType);
709
757
  }
710
758
 
711
759
  if (serverWireVersion < 8) {
@@ -739,7 +787,7 @@ export class CryptoConnection extends Connection {
739
787
  }
740
788
  }
741
789
 
742
- const response = await super.command(ns, encrypted, options);
790
+ const response = await super.command<T>(ns, encrypted, options, responseType);
743
791
 
744
792
  return await autoEncrypter.decrypt(response, options);
745
793
  }
@@ -8,7 +8,7 @@ import {
8
8
  type MessageHeader,
9
9
  OpCompressedRequest,
10
10
  OpMsgResponse,
11
- OpQueryResponse,
11
+ OpReply,
12
12
  type WriteProtocolMessageType
13
13
  } from '../commands';
14
14
  import { OP_COMPRESSED, OP_MSG } from './constants';
@@ -163,9 +163,7 @@ export async function compressCommand(
163
163
  *
164
164
  * This method does not parse the response's BSON.
165
165
  */
166
- export async function decompressResponse(
167
- message: Buffer
168
- ): Promise<OpMsgResponse | OpQueryResponse> {
166
+ export async function decompressResponse(message: Buffer): Promise<OpMsgResponse | OpReply> {
169
167
  const messageHeader: MessageHeader = {
170
168
  length: message.readInt32LE(0),
171
169
  requestId: message.readInt32LE(4),
@@ -174,7 +172,7 @@ export async function decompressResponse(
174
172
  };
175
173
 
176
174
  if (messageHeader.opCode !== OP_COMPRESSED) {
177
- const ResponseType = messageHeader.opCode === OP_MSG ? OpMsgResponse : OpQueryResponse;
175
+ const ResponseType = messageHeader.opCode === OP_MSG ? OpMsgResponse : OpReply;
178
176
  const messageBody = message.subarray(MESSAGE_HEADER_SIZE);
179
177
  return new ResponseType(message, messageHeader, messageBody);
180
178
  }
@@ -189,7 +187,7 @@ export async function decompressResponse(
189
187
  const compressedBuffer = message.slice(MESSAGE_HEADER_SIZE + 9);
190
188
 
191
189
  // recalculate based on wrapped opcode
192
- const ResponseType = header.opCode === OP_MSG ? OpMsgResponse : OpQueryResponse;
190
+ const ResponseType = header.opCode === OP_MSG ? OpMsgResponse : OpReply;
193
191
  const messageBody = await decompress(compressorID, compressedBuffer);
194
192
  if (messageBody.length !== header.length) {
195
193
  throw new MongoDecompressionError('Message body and message header must be the same length');
@@ -23,6 +23,7 @@ const enum BSONElementOffset {
23
23
  length = 4
24
24
  }
25
25
 
26
+ /** @internal */
26
27
  export type JSTypeOf = {
27
28
  [BSONType.null]: null;
28
29
  [BSONType.undefined]: null;
@@ -0,0 +1,109 @@
1
+ import { type BSONSerializeOptions, BSONType, type Document, type Timestamp } from '../../bson';
2
+ import { type ClusterTime } from '../../sdam/common';
3
+ import { OnDemandDocument } from './on_demand/document';
4
+
5
+ /** @internal */
6
+ export type MongoDBResponseConstructor = {
7
+ new (bson: Uint8Array, offset?: number, isArray?: boolean): MongoDBResponse;
8
+ };
9
+
10
+ /** @internal */
11
+ export class MongoDBResponse extends OnDemandDocument {
12
+ // {ok:1}
13
+ static empty = new MongoDBResponse(new Uint8Array([13, 0, 0, 0, 16, 111, 107, 0, 1, 0, 0, 0, 0]));
14
+
15
+ /** Indicates this document is a server error */
16
+ public get isError() {
17
+ let isError = this.ok === 0;
18
+ isError ||= this.has('errmsg');
19
+ isError ||= this.has('code');
20
+ isError ||= this.has('$err'); // The '$err' field is used in OP_REPLY responses
21
+ return isError;
22
+ }
23
+
24
+ /**
25
+ * Drivers can safely assume that the `recoveryToken` field is always a BSON document but drivers MUST NOT modify the
26
+ * contents of the document.
27
+ */
28
+ get recoveryToken(): Document | null {
29
+ return (
30
+ this.get('recoveryToken', BSONType.object)?.toObject({
31
+ promoteValues: false,
32
+ promoteLongs: false,
33
+ promoteBuffers: false
34
+ }) ?? null
35
+ );
36
+ }
37
+
38
+ /**
39
+ * The server creates a cursor in response to a snapshot find/aggregate command and reports atClusterTime within the cursor field in the response.
40
+ * For the distinct command the server adds a top-level atClusterTime field to the response.
41
+ * The atClusterTime field represents the timestamp of the read and is guaranteed to be majority committed.
42
+ */
43
+ public get atClusterTime(): Timestamp | null {
44
+ return (
45
+ this.get('cursor', BSONType.object)?.get('atClusterTime', BSONType.timestamp) ??
46
+ this.get('atClusterTime', BSONType.timestamp)
47
+ );
48
+ }
49
+
50
+ public get operationTime(): Timestamp | null {
51
+ return this.get('operationTime', BSONType.timestamp);
52
+ }
53
+
54
+ public get ok(): 0 | 1 {
55
+ return this.getNumber('ok') ? 1 : 0;
56
+ }
57
+
58
+ public get $err(): string | null {
59
+ return this.get('$err', BSONType.string);
60
+ }
61
+
62
+ public get errmsg(): string | null {
63
+ return this.get('errmsg', BSONType.string);
64
+ }
65
+
66
+ public get code(): number | null {
67
+ return this.getNumber('code');
68
+ }
69
+
70
+ private clusterTime?: ClusterTime | null;
71
+ public get $clusterTime(): ClusterTime | null {
72
+ if (!('clusterTime' in this)) {
73
+ const clusterTimeDoc = this.get('$clusterTime', BSONType.object);
74
+ if (clusterTimeDoc == null) {
75
+ this.clusterTime = null;
76
+ return null;
77
+ }
78
+ const clusterTime = clusterTimeDoc.get('clusterTime', BSONType.timestamp, true);
79
+ const signature = clusterTimeDoc.get('signature', BSONType.object)?.toObject();
80
+ // @ts-expect-error: `signature` is incorrectly typed. It is public API.
81
+ this.clusterTime = { clusterTime, signature };
82
+ }
83
+ return this.clusterTime ?? null;
84
+ }
85
+
86
+ public override toObject(options: BSONSerializeOptions = {}): Record<string, any> {
87
+ const exactBSONOptions = {
88
+ useBigInt64: options.useBigInt64,
89
+ promoteLongs: options.promoteLongs,
90
+ promoteValues: options.promoteValues,
91
+ promoteBuffers: options.promoteBuffers,
92
+ bsonRegExp: options.bsonRegExp,
93
+ raw: options.raw ?? false,
94
+ fieldsAsRaw: options.fieldsAsRaw ?? {},
95
+ validation: this.parseBsonSerializationOptions(options)
96
+ };
97
+ return super.toObject(exactBSONOptions);
98
+ }
99
+
100
+ private parseBsonSerializationOptions({ enableUtf8Validation }: BSONSerializeOptions): {
101
+ utf8: { writeErrors: false } | false;
102
+ } {
103
+ if (enableUtf8Validation === false) {
104
+ return { utf8: false };
105
+ }
106
+
107
+ return { utf8: { writeErrors: false } };
108
+ }
109
+ }
package/src/collection.ts CHANGED
@@ -106,6 +106,8 @@ export interface CollectionOptions extends BSONSerializeOptions, WriteConcernOpt
106
106
  readConcern?: ReadConcernLike;
107
107
  /** The preferred read preference (ReadPreference.PRIMARY, ReadPreference.PRIMARY_PREFERRED, ReadPreference.SECONDARY, ReadPreference.SECONDARY_PREFERRED, ReadPreference.NEAREST). */
108
108
  readPreference?: ReadPreferenceLike;
109
+ /** @internal TODO(NODE-5688): make this public */
110
+ timeoutMS?: number;
109
111
  }
110
112
 
111
113
  /** @internal */
@@ -109,6 +109,8 @@ export interface AbstractCursorOptions extends BSONSerializeOptions {
109
109
  */
110
110
  awaitData?: boolean;
111
111
  noCursorTimeout?: boolean;
112
+ /** @internal TODO(NODE-5688): make this public */
113
+ timeoutMS?: number;
112
114
  }
113
115
 
114
116
  /** @internal */
@@ -184,6 +186,7 @@ export abstract class AbstractCursor<
184
186
  : ReadPreference.primary,
185
187
  ...pluckBSONSerializeOptions(options)
186
188
  };
189
+ this[kOptions].timeoutMS = options.timeoutMS;
187
190
 
188
191
  const readConcern = ReadConcern.fromOptions(options);
189
192
  if (readConcern) {
package/src/db.ts CHANGED
@@ -66,7 +66,8 @@ const DB_OPTIONS_ALLOW_LIST = [
66
66
  'enableUtf8Validation',
67
67
  'promoteValues',
68
68
  'compression',
69
- 'retryWrites'
69
+ 'retryWrites',
70
+ 'timeoutMS'
70
71
  ];
71
72
 
72
73
  /** @internal */
@@ -94,6 +95,8 @@ export interface DbOptions extends BSONSerializeOptions, WriteConcernOptions {
94
95
  readConcern?: ReadConcern;
95
96
  /** Should retry failed writes */
96
97
  retryWrites?: boolean;
98
+ /** @internal TODO(NODE-5688): make this public */
99
+ timeoutMS?: number;
97
100
  }
98
101
 
99
102
  /**
@@ -28,6 +28,8 @@ export interface GridFSBucketReadStreamOptions {
28
28
  * to be returned by the stream. `end` is non-inclusive
29
29
  */
30
30
  end?: number;
31
+ /** @internal TODO(NODE-5688): make this public */
32
+ timeoutMS?: number;
31
33
  }
32
34
 
33
35
  /** @public */
@@ -36,6 +36,8 @@ export interface GridFSBucketOptions extends WriteConcernOptions {
36
36
  chunkSizeBytes?: number;
37
37
  /** Read preference to be passed to read operations */
38
38
  readPreference?: ReadPreference;
39
+ /** @internal TODO(NODE-5688): make this public */
40
+ timeoutMS?: number;
39
41
  }
40
42
 
41
43
  /** @internal */
@@ -36,6 +36,8 @@ export interface GridFSBucketWriteStreamOptions extends WriteConcernOptions {
36
36
  * @deprecated Will be removed in the next major version. Add an aliases field to the metadata document instead.
37
37
  */
38
38
  aliases?: string[];
39
+ /** @internal TODO(NODE-5688): make this public */
40
+ timeoutMS?: number;
39
41
  }
40
42
 
41
43
  /**
package/src/index.ts CHANGED
@@ -264,8 +264,7 @@ export type {
264
264
  OpMsgResponse,
265
265
  OpQueryOptions,
266
266
  OpQueryRequest,
267
- OpQueryResponse,
268
- OpResponseOptions,
267
+ OpReply,
269
268
  WriteProtocolMessageType
270
269
  } from './cmap/commands';
271
270
  export type { HandshakeDocument } from './cmap/connect';
@@ -290,6 +289,8 @@ export type { ClientMetadata, ClientMetadataOptions } from './cmap/handshake/cli
290
289
  export type { ConnectionPoolMetrics } from './cmap/metrics';
291
290
  export type { StreamDescription, StreamDescriptionOptions } from './cmap/stream_description';
292
291
  export type { CompressorName } from './cmap/wire_protocol/compression';
292
+ export type { JSTypeOf, OnDemandDocument } from './cmap/wire_protocol/on_demand/document';
293
+ export type { MongoDBResponse, MongoDBResponseConstructor } from './cmap/wire_protocol/responses';
293
294
  export type { CollectionOptions, CollectionPrivate, ModifyResult } from './collection';
294
295
  export type {
295
296
  COMMAND_FAILED,
@@ -120,7 +120,7 @@ export type SupportedNodeConnectionOptions = SupportedTLSConnectionOptions &
120
120
  export interface MongoClientOptions extends BSONSerializeOptions, SupportedNodeConnectionOptions {
121
121
  /** Specifies the name of the replica set, if the mongod is a member of a replica set. */
122
122
  replicaSet?: string;
123
- /** @internal This option is in development and currently has no behaviour. */
123
+ /** @internal TODO(NODE-5688): This option is in development and currently has no behaviour. */
124
124
  timeoutMS?: number;
125
125
  /** Enables or disables TLS/SSL for the connection. */
126
126
  tls?: boolean;
@@ -897,4 +897,6 @@ export interface MongoOptions
897
897
  * TODO: NODE-5671 - remove internal flag
898
898
  */
899
899
  mongodbLogPath?: 'stderr' | 'stdout' | MongoDBLogWritable;
900
+ /** @internal TODO(NODE-5688): make this public */
901
+ timeoutMS?: number;
900
902
  }
@@ -110,6 +110,11 @@ export async function executeOperation<
110
110
  } else if (session.client !== client) {
111
111
  throw new MongoInvalidArgumentError('ClientSession must be from the same MongoClient');
112
112
  }
113
+ if (session.explicit && session?.timeoutMS != null && operation.options.timeoutMS != null) {
114
+ throw new MongoInvalidArgumentError(
115
+ 'Do not specify timeoutMS on operation if already specified on an explicit session'
116
+ );
117
+ }
113
118
 
114
119
  const readPreference = operation.readPreference ?? ReadPreference.primary;
115
120
  const inTransaction = !!session?.inTransaction();
@@ -34,6 +34,9 @@ export interface OperationOptions extends BSONSerializeOptions {
34
34
  /** @internal Hints to `executeOperation` that this operation should not unpin on an ended transaction */
35
35
  bypassPinningCheck?: boolean;
36
36
  omitReadPreference?: boolean;
37
+
38
+ /** @internal TODO(NODE-5688): make this public */
39
+ timeoutMS?: number;
37
40
  }
38
41
 
39
42
  /** @internal */
package/src/sessions.ts CHANGED
@@ -4,6 +4,7 @@ import { promisify } from 'util';
4
4
  import { Binary, type Document, Long, type Timestamp } from './bson';
5
5
  import type { CommandOptions, Connection } from './cmap/connection';
6
6
  import { ConnectionPoolMetrics } from './cmap/metrics';
7
+ import { type MongoDBResponse } from './cmap/wire_protocol/responses';
7
8
  import { isSharded } from './cmap/wire_protocol/shared';
8
9
  import { PINNED, UNPINNED } from './constants';
9
10
  import type { AbstractCursor } from './cursor/abstract_cursor';
@@ -60,6 +61,9 @@ export interface ClientSessionOptions {
60
61
  snapshot?: boolean;
61
62
  /** The default TransactionOptions to use for transactions started on this session. */
62
63
  defaultTransactionOptions?: TransactionOptions;
64
+ /** @internal
65
+ * The value of timeoutMS used for CSOT. Used to override client timeoutMS */
66
+ defaultTimeoutMS?: number;
63
67
 
64
68
  /** @internal */
65
69
  owner?: symbol | AbstractCursor;
@@ -130,6 +134,8 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
130
134
  [kPinnedConnection]?: Connection;
131
135
  /** @internal */
132
136
  [kTxnNumberIncrement]: number;
137
+ /** @internal */
138
+ timeoutMS?: number;
133
139
 
134
140
  /**
135
141
  * Create a client session.
@@ -172,6 +178,7 @@ export class ClientSession extends TypedEventEmitter<ClientSessionEvents> {
172
178
  this.sessionPool = sessionPool;
173
179
  this.hasEnded = false;
174
180
  this.clientOptions = clientOptions;
181
+ this.timeoutMS = options.defaultTimeoutMS ?? client.options?.timeoutMS;
175
182
 
176
183
  this.explicit = !!options.explicit;
177
184
  this[kServerSession] = this.explicit ? this.sessionPool.acquire() : null;
@@ -1040,7 +1047,7 @@ export function applySession(
1040
1047
  return;
1041
1048
  }
1042
1049
 
1043
- export function updateSessionFromResponse(session: ClientSession, document: Document): void {
1050
+ export function updateSessionFromResponse(session: ClientSession, document: MongoDBResponse): void {
1044
1051
  if (document.$clusterTime) {
1045
1052
  _advanceClusterTime(session, document.$clusterTime);
1046
1053
  }
@@ -1056,7 +1063,7 @@ export function updateSessionFromResponse(session: ClientSession, document: Docu
1056
1063
  if (session?.[kSnapshotEnabled] && session[kSnapshotTime] == null) {
1057
1064
  // find and aggregate commands return atClusterTime on the cursor
1058
1065
  // distinct includes it in the response body
1059
- const atClusterTime = document.cursor?.atClusterTime || document.atClusterTime;
1066
+ const atClusterTime = document.atClusterTime;
1060
1067
  if (atClusterTime) {
1061
1068
  session[kSnapshotTime] = atClusterTime;
1062
1069
  }